Map JSON primitive to object value in Java - java

I have following JSON
{
id: 1234,
code: "N/A"
}
I would like to transform it to object of such classes:
class MySweetObject {
int id;
SomeStringWrapper code;
}
class SomeStringWrapper {
String code;
Boolean isNotAvailable() {
return "N/A".equals(code);
}
}
In short - I want to wrap simple property value from JSON in object to add some business functionality. Null value is not an option as code is a required property.
Is it possible to do it in Jackson?

You could use JsonSetter annotation in setCode method:
From the documentation:
Setter means that when a property with matching name is encountered in
JSON content, this method will be used to set value of the property
This way you could check if the value is null before setting it, and if it is null then set a default value (or do whatever you want).

You need to modify your JSON string, its wrong as per your classes. As the value "N/A" jackson will assume that to be a string, but that is the instance of SomeStringWrapper class.
Your JSON should look like as below in order to deserialize to your class structure:
{
id: 1234,
code: {
code : "N/A"
}
}

Try the following code:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.javafx.fxml.PropertyNotFoundException;
class Main {
public static void main(String[] args) throws Exception {
String jsonString = "{\"id\":\"1234\"}";
ObjectMapper mapper = new ObjectMapper();
MySweetObject obj = mapper.readValue(jsonString, MySweetObject.class);
System.out.println(mapper.writeValueAsString(obj));
}
}
class MySweetObject {
private String id;
private String code;
public String getId() {
return id;
}
public String getCode() {
if (code == null) {
throw new PropertyNotFoundException("Property code cannot be null");
} else {
return code;
}
}
}
Normally #JsonProperty(required = true) would throw an exception, but It is not working, instead you can manually throw an exception in the getter if property is null or empty. check my code above, it will throw JsonMappingException if null.

Related

How to auto detect concrete class based on the JSON provided using Spring/Jackson

I have 2 implementations of Value interface, RangeValue, FileValue.
RangeValue looks like below:
public class RangeValue implements Value {
private int min;
private int max;
public RangeValue(int min, int max) {
this.min = min;
this.max = max;
}
public int getMin() {
return min;
}
public int getMax() {
return max;
}
}
FileValue looks like below:
public class FileValue implements Value {
private String contentType;
private String value;
public FileValue(String contentType, String value) {
this.contentType = contentType;
this.value = value;
}
public String getContentType() {
return contentType;
}
public String getValue() {
return value;
}
}
the json for RangeValue looks like :
{
"min": 200,
"max": 300
}
The json for FileValue looks:
{
"contentType": "application/octet-stream",
"value": "fileValue"
}
Now I want the RequestType parameter for these json to be of type Value only, I can't change the JSON files i.e. the json would look like the same and user should use the same JSON in request body as stated above.
I solved this by using #JsonTypeInfo & #JsonSubTypes by adding extra attributes to the above JSON i.e. type but the spec doesn't allow me to add that.
How can the appropriate concrete class could be instantiated based on the JSON above without altering?
Option 1: custom deserializer. Algorithm can be as follows:
Parse to JsonNode.
Use the properties in the node to find the correct class to deserialize into.
Convert the node to instance of the actual class.
Simplified example:
public class ValueDeserializer extends StdDeserializer<Value> {
public ValueDeserializer() {
super(Value.class);
}
#Override
public Value deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode root = parser.readValueAsTree();
if (root instanceof ObjectNode objectNode) {
JsonNode valueNode = objectNode.get("somePropertyName");
Class<? extends Value> clazz = valueNode == null ? RangeValue.class : FileValue.class;
return context.readTreeAsValue(objectNode, clazz);
}
throw new JsonParseException(parser, "not an object");
//handling the case, when json is json array
//or something else which can't be deserialized into object
}
}
Register the deserializer with JsonDeserialize on the interface:
#JsonDeserialize(using = ValueDeserializer.class)
Put the same annotation on RangeValue and FileValue, without specifying a deserializer, otherwise you will get StackOverflowError.
Option 2: use JsonTypeInfo.Id.DEDUCTION
#JsonSubTypes({
#JsonSubTypes.Type(FileValue.class),
#JsonSubTypes.Type(RangeValue.class)
})
#JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
public interface Value {
}
Jackson will deduce the correct class using the property names. Keep in mind exception will be thrown if it fails deduction.
Means that no serialized typing-property is used. Types are deduced based on the fields available. Deduction is limited to the names of fields (not their values or, consequently, any nested descendants). Exceptions will be thrown if not enough unique information is present to select a single subtype.
If deduction is being used annotation properties visible, property and include are ignored.

how to make java class behave like a normal object and list-of-object at runtime and as per need?

I have a simple class in java like below:
class Simple {
private String name;
private String email;
}
I want to have behaviour of java.util.List<Simple> and Simple both according to input data that my program receives.
i.e.
Case 1::
if my program receives below kind of json-array input
{"simple" : [ {"name":"a", "email" : "a#z.com"}, {"name":"b", "email" : "b#z.com"} ]}
I need to parse it using List<Simple>
Case 2::
if my program receives below kind of json-object input
{"simple" : {"name":"c", "email" : "c#z.com"} }
I need to parse it using Simple
Note: I have tried using JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, but the problem is it is basically converting single value also into json-array at the time of writing json.
I need to persist json as it is, is there any other way to achieve this?
To avoid any Jackson customisation I would create wrapper class with an Object simple property. We can add two extra checking methods and two extra casting methods. It will allow Jackson to do it's logic and in runtime we can check what actually we have:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Collections;
import java.util.List;
public class DateApp {
private final static JsonMapper JSON_MAPPER = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
public static void main(String[] args) throws Exception {
Simple object = new Simple("John", "john#doe.com");
SimpleWrapper wrapper = new SimpleWrapper();
wrapper.setSimple(object);
serializeAndDeserialize(wrapper);
wrapper.setSimple(Collections.singletonList(object));
serializeAndDeserialize(wrapper);
}
private static void serializeAndDeserialize(SimpleWrapper wrapper) throws JsonProcessingException {
String json = JSON_MAPPER.writeValueAsString(wrapper);
System.out.println("JSON:");
System.out.println(json);
wrapper = JSON_MAPPER.readValue(json, SimpleWrapper.class);
System.out.println("Wrapper:");
System.out.println(wrapper);
}
}
#Data
class SimpleWrapper {
private Object simple;
#JsonIgnore
public boolean isSimpleObject() {
return simple instanceof Simple;
}
#JsonIgnore
public boolean isSimpleList() {
return simple instanceof List;
}
#JsonIgnore
public Simple getSimpleAsObject() {
return (Simple) simple;
}
#JsonIgnore
public List<Simple> getSimpleAsList() {
return (List<Simple>) simple;
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
class Simple {
private String name;
private String email;
}
Above code prints:
JSON:
{
"simple" : {
"name" : "John",
"email" : "john#doe.com"
}
}
Wrapper:
SimpleWrapper(simple={name=John, email=john#doe.com})
JSON:
{
"simple" : [ {
"name" : "John",
"email" : "john#doe.com"
} ]
}
Wrapper:
SimpleWrapper(simple=[{name=John, email=john#doe.com}])
You can use JsonNode.isArray() (or JsonNode.isObject()) to perform this check.
Then you can parse the node into a list with ObjectReader.readValue() or into a POJO using ObjectMapper.treeToValue().
String myJson = """
{"simple" : {"name":"c", "email" : "c#z.com"} }
""";
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(myJson);
if (node.isArray()) {
ObjectReader reader = mapper.readerFor(new TypeReference<List<Simple>>() {});
List<Simple> list = reader.readValue(node);
// do something with a list
} else {
Simple pojo = mapper.treeToValue(node, Simple.class);
// do something else with a single object
}
Jackson is able to parse any json into a map where value is any object. you can then inquire on the type of the map value
Map<String, Object> map = new ObjectMapper().readValue(jsonInput, Map.class);
Object value = map.get("simple");
if (value instanceof Collection) { // will return false for null
Collection<Simple> simples = (Collection<Simple>)value;
}
else if (value instanceof Simple) {
Simple simple = (Simple)value;
}
else {
System.err.println("unrecognized");
}
You only need to read the first node, simple and check if it is an array - using isArray() method.
public class Main{
public static void main(String args[]) {
//String inputString = [your input];
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(inputString);
JsonNode simpleNode = root.findPath("simple");
if(simpleNode.isArray()) {
//you have an array
} else {
// you have an element
}
}
}

How do I create Json Array in Java8 and only print json if I set value for it?

I am trying to generate the following using fasterxml jackson.. but I am stuck. I can't seem to work out how to create arrays.
{
"setAccId":"12345",
"groupOf":null,
"isEnabled":false,
"list":[
{
"student":"jim",
"type":"S_A",
"retro":null
},
{
"student":"bob",
"type":"S_A",
"retro":null
}
],
"sort":[]
}
I have two classes. One has the Json properties, and the other is where I print it.
Below class (DynamicJsonHelper) is where I have all the json properties
package com.company.jsonfc;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#JsonPropertyOrder({
"accId",
"groupOf",
"isEnabled"
})
public class DynamicJsonHelper {
public String accId;
public String groupOf;
public List studentList;
#JsonProperty("accId")
public void setAccId(String accId) {
this.accId = accId;
}
#JsonProperty("groupOf")
public void setGroupOf(String groupOf) {
this.groupOf = groupOf;
}
#JsonProperty("isEnabled")
public boolean isEnabled() {
return false;
}
#JsonProperty("studentList")
public List<StudentList> studentList() {
return studentList;
}
}
Student List Class (as suggested)
class StudentList {
String student;
String type;
String retro;
}
And here is class (PrintJson) where I call it.
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.company.jsonfc.DynamicJsonHelper;
#JsonInclude(JsonInclude.Include.NON_NULL)
public class PrintJson {
#Test
public void create_json() throws JsonProcessingException {
final JsonNodeFactory factory = JsonNodeFactory.instance;
DynamicJsonHelper dynamicJsonHelper = new DynamicJsonHelper();
String jsonString;
ObjectMapper mapper = new ObjectMapper();
dynamicJsonHelper.setAccId("12345");
jsonString = mapper.writeValueAsString(dynamicJsonHelper);
System.out.println(jsonString);
}
}
This results in printing the following:
{
"setAccId":"12345",
"groupOf":null,
"isEnabled":false
"studentList":null
}
1) How do I add the list:[ ... ] array & the sort: [ ] at the end too?
2) In class PrintJson, I don't set value for groupOf but it is still created in Json. How do I set it so if I set value, it is printed.. otherwise it is not included in the json being printed.
I would appreciate if you took my code and gave me example based on it for better understanding please
1) How do I add the list:[ ... ] array & the sort: [ ] at the end too?
Answer: You can create two more classes one for list and one for sort. Now in the class DynamicJsonHelper you can add both of them like you have added accId or isEnabled
and they will be printed. Make sure to add the fields you want in them as instance variables in both of the classes. For example for list you can have a class like:
class StudentList{
String student;
String type;
String retro;
}
Now add a field in your class DynamicJsonHelper as List<StudentList>. Similarly you can do for sort.
2) In class PrintJson, I don't set value for groupOf but it is still
created in Json. How do I set it so if I set value, it is printed..
otherwise it is not included in the json being printed.
Answer: You can either use Object mapper and set it to ignore the null fields during serialization. For eg: mapper.setSerializationInclusion(Include.NON_NULL);
Or you can set it at class level to ignore null values if any. For eg:
#JsonInclude(Include.NON_NULL)
class Test
{
String t;
}
As mentioned in the comments by aBnormaLz above doesn't work if the type is primitive like you have for isEnabled. So consider changing it to Boolean and ensure the same for other fields also.
Edit:
#JsonPropertyOrder({
"accId",
"groupOf",
"isEnabled"
})
#JsonInclude(JsonInclude.Include.NON_NULL)
public class DynamicJsonHelper {
public String accId;
public String groupOf;
public List<Student> studentList;
#JsonProperty("accId")
public void setAccId(String accId) {
this.accId = accId;
}
#JsonProperty("groupOf")
public void setGroupOf(String groupOf) {
this.groupOf = groupOf;
}
#JsonProperty("isEnabled")
public boolean isEnabled() {
return false;
}
#JsonProperty("studentList")
public void setStudentList(List<Student> list) {
this.studentList = list;
}
}
class Student {
private String student;
private String type;
private String retro;
public Student(String student, String type, String retro) {
this.student = student;
this.type = type;
this.retro = retro;
}
public String getStudent() {
return student;
}
public void setStudent(String student) {
this.student = student;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getRetro() {
return retro;
}
public void setRetro(String retro) {
this.retro = retro;
}
}
class HelperTest{
public static void main(String[] args) throws JsonProcessingException {
DynamicJsonHelper dynamicJsonHelper = new DynamicJsonHelper();
String jsonString;
ObjectMapper mapper = new ObjectMapper();
dynamicJsonHelper.setAccId("12345");
List<Student> list = Arrays.asList(new Student("s1", "t1", "r1"), new Student("s2", "t2", "r2"));
dynamicJsonHelper.setStudentList(list);
jsonString = mapper.writeValueAsString(dynamicJsonHelper);
System.out.println(jsonString);
}
}
After executing the program the output is as shown below:
{
"accId": "12345",
"isEnabled": false,
"studentList": [
{
"student": "s1",
"type": "t1",
"retro": "r1"
},
{
"student": "s2",
"type": "t2",
"retro": "r2"
}
]
}
You missed a point that you have to have third class and it must have property of type java.util.List or array and name list for your example.
e.g.
public class JsonHolder {
// appropriate Json/Jackson annotations ommitted
private String setAccId;
private String groupOf;
private boolean isEnabled;
private List<DynamicJsonHelper> list;
private String[] sort;
// .. getter/setters ...
}
then you have to create that object and when you created your DynamicJsonHelper put it into list or array.
After all you can serialize JsonHolder object and you will see your java List or Array as JSON array.
UPD: just note that in JSON structure like { ... } is an object and in Java there must be a class for it.
So, starting with JSON structure you posted
{
"setAccId":"12345",
"groupOf":null,
"isEnabled":false,
"list":[
{
"student":"jim",
"type":"S_A",
"retro":null
},
{
"student":"bob",
"type":"S_A",
"retro":null
}
],
"sort":[]
}
it is an object (let name it JsonHolder) with properties named setAcctId, groupOf, isEnabled, list, sort
So you have to have a Java class for it (similar as you did for your DynamicJsonHelper. You can use any #Json annotations you'd like to the same way (I omitted them and left for you). Even you will not have them Jackson or any other JSON serialiser will use property names in Java class by as default.
I mean as example your #JsonProperty("accId")
#JsonProperty("accId")
public void setAccId(String accId)
is not required as long as getter/setter/property named also getAcctId, setAcctId, acctId. Jackson will use that if there is no #JsonProperty annotation.
BTW it is better to do it as you did for code readability. :-)
Per your question:
list and sort properties in JSON are arrays. Jackson parses Java collections classes like List, Set or Arrays to JSON arrays.
Then according to required JSON structure list property is a such collection of DynamicJsonHelper objects you created. in Java class, List or Array is just your choice - use what is more suitable for you. I recommend to use a List rather than Array. Work with arrays in Java is not a good idea. :-)
So far you just created a Java class DynamicJsonHelper only for objects which must be in list property of JsonHolder object. What's left is to create that "JsonHolder" class and give that object to Jackson to serialize it into required JSON structure.
There are bunch of #Json annotations you can use to allow or not null or empty values, change property names, exclude Java class properties to be serialized and so, and so... All is up to you... good luck!

Jackson: Deserializing enums

I'm using Jackson + Spring in a REST API. One of the API calls has JSON that looks like this:
{
"status": "Suspended"
}
where the value of "status" is mapped to a Java enum, like so:
public enum FeatureStatus {
Activated(0),
Inactivated(1),
Suspended(2),
Deleted(3);
private FeatureStatus(int id) {
this.id = id;
}
private int id;
public int getId() {
return id;
}
public FeatureStatus valueOf(int id) {
switch(id) {
case 1: return Inactivated;
case 2: return Suspended;
case 3: return Deleted;
default: return Activated;
}
}
#JsonCreator
public static FeatureStatus fromValue(String status) {
if(status != null) {
for(FeatureStatus featureStatus : FeatureStatus.values()) {
if(featureStatus.toString().equals(status)) {
return featureStatus;
}
}
throw new IllegalArgumentException(status + " is an invalid value.");
}
throw new IllegalArgumentException("A value was not provided.");
}
}
However, this exception gets thrown: java.lang.IllegalArgumentException: { is an invalid value. It looks like it's not deserializing the JSON at all. My controller method has this definition:
public #ResponseBody void updateFeatureStatusById(#PathVariable long featureId, #RequestBody FeatureStatus updated) {
Any other controller automatically deserializes the JSON fine as expected using this format. How can I deserialize this JSON into my enum?
Put this class next to your controller:
final class FeatureStatusJsonObject {
public FeatureStatus status;
}
And then in the controller method use this:
#RequestBody FeatureStatusJsonObject updated
and get the real FeatureStatus by
updated.status
This makes the conversion from a JSON object to an Enum literal explicit in your code, and allows you to use the regular Enum <-> String (de)serialization elsewhere (if necessary).
On an unrelated note, this looks a bit funky:
#ResponseBody void
I would just make it void.
Is the exact JSON you pass an Object? Enums except either JSON String or number, not Object.
If you need to give an object, you should map it to something like:
public class EnumWrapper {
public FeatureStatus status;
}
and it'll work.

How can I include raw JSON in an object using Jackson?

I am trying to include raw JSON inside a Java object when the object is (de)serialized using Jackson. In order to test this functionality, I wrote the following test:
public static class Pojo {
public String foo;
#JsonRawValue
public String bar;
}
#Test
public void test() throws JsonGenerationException, JsonMappingException, IOException {
String foo = "one";
String bar = "{\"A\":false}";
Pojo pojo = new Pojo();
pojo.foo = foo;
pojo.bar = bar;
String json = "{\"foo\":\"" + foo + "\",\"bar\":" + bar + "}";
ObjectMapper objectMapper = new ObjectMapper();
String output = objectMapper.writeValueAsString(pojo);
System.out.println(output);
assertEquals(json, output);
Pojo deserialized = objectMapper.readValue(output, Pojo.class);
assertEquals(foo, deserialized.foo);
assertEquals(bar, deserialized.bar);
}
The code outputs the following line:
{"foo":"one","bar":{"A":false}}
The JSON is exactly how I want things to look. Unfortunately, the code fails with an exception when attempting to read the JSON back in to the object. Here is the exception:
org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: java.io.StringReader#d70d7a; line: 1, column: 13] (through reference chain: com.tnal.prism.cobalt.gather.testing.Pojo["bar"])
Why does Jackson function just fine in one direction but fail when going the other direction? It seems like it should be able to take its own output as input again. I know what I'm trying to do is unorthodox (the general advice is to create an inner object for bar that has a property named A), but I don't want to interact with this JSON at all. My code is acting as a pass-through for this code -- I want to take in this JSON and send it back out again without touching a thing, because when the JSON changes I don't want my code to need modifications.
Thanks for the advice.
EDIT: Made Pojo a static class, which was causing a different error.
#JsonRawValue is intended for serialization-side only, since the reverse direction is a bit trickier to handle. In effect it was added to allow injecting pre-encoded content.
I guess it would be possible to add support for reverse, although that would be quite awkward: content will have to be parsed, and then re-written back to "raw" form, which may or may not be the same (since character quoting may differ).
This for general case. But perhaps it would make sense for some subset of problems.
But I think a work-around for your specific case would be to specify type as 'java.lang.Object', since this should work ok: for serialization, String will be output as is, and for deserialization, it will be deserialized as a Map. Actually you might want to have separate getter/setter if so; getter would return String for serialization (and needs #JsonRawValue); and setter would take either Map or Object. You could re-encode it to a String if that makes sense.
Following #StaxMan answer, I've made the following works like a charm:
public class Pojo {
Object json;
#JsonRawValue
public String getJson() {
// default raw value: null or "[]"
return json == null ? null : json.toString();
}
public void setJson(JsonNode node) {
this.json = node;
}
}
And, to be faithful to the initial question, here is the working test:
public class PojoTest {
ObjectMapper mapper = new ObjectMapper();
#Test
public void test() throws IOException {
Pojo pojo = new Pojo("{\"foo\":18}");
String output = mapper.writeValueAsString(pojo);
assertThat(output).isEqualTo("{\"json\":{\"foo\":18}}");
Pojo deserialized = mapper.readValue(output, Pojo.class);
assertThat(deserialized.json.toString()).isEqualTo("{\"foo\":18}");
// deserialized.json == {"foo":18}
}
}
I was able to do this with a custom deserializer (cut and pasted from here)
package etc;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
/**
* Keeps json value as json, does not try to deserialize it
* #author roytruelove
*
*/
public class KeepAsJsonDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
TreeNode tree = jp.getCodec().readTree(jp);
return tree.toString();
}
}
Use it by annotating the desired member like this:
#JsonDeserialize(using = KeepAsJsonDeserializer.class)
private String value;
#JsonSetter may help. See my sample ('data' is supposed to contain unparsed JSON):
class Purchase
{
String data;
#JsonProperty("signature")
String signature;
#JsonSetter("data")
void setData(JsonNode data)
{
this.data = data.toString();
}
}
This is a problem with your inner classes. The Pojo class is a non-static inner class of your test class, and Jackson cannot instantiate that class. So it can serialize, but not deserialize.
Redefine your class like this:
public static class Pojo {
public String foo;
#JsonRawValue
public String bar;
}
Note the addition of static
Adding to Roy Truelove's great answer, this is how to inject the custom deserialiser in response to appearance of #JsonRawValue:
import com.fasterxml.jackson.databind.Module;
#Component
public class ModuleImpl extends Module {
#Override
public void setupModule(SetupContext context) {
context.addBeanDeserializerModifier(new BeanDeserializerModifierImpl());
}
}
import java.util.Iterator;
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
public class BeanDeserializerModifierImpl extends BeanDeserializerModifier {
#Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
Iterator<SettableBeanProperty> it = builder.getProperties();
while (it.hasNext()) {
SettableBeanProperty p = it.next();
if (p.getAnnotation(JsonRawValue.class) != null) {
builder.addOrReplaceProperty(p.withValueDeserializer(KeepAsJsonDeserialzier.INSTANCE), true);
}
}
return builder;
}
}
This easy solution worked for me:
public class MyObject {
private Object rawJsonValue;
public Object getRawJsonValue() {
return rawJsonValue;
}
public void setRawJsonValue(Object rawJsonValue) {
this.rawJsonValue = rawJsonValue;
}
}
So I was able to store raw value of JSON in rawJsonValue variable and then it was no problem to deserialize it (as object) with other fields back to JSON and send via my REST. Using #JsonRawValue didnt helped me because stored JSON was deserialized as String, not as object, and that was not what I wanted.
This even works in a JPA entity:
private String json;
#JsonRawValue
public String getJson() {
return json;
}
public void setJson(final String json) {
this.json = json;
}
#JsonProperty(value = "json")
public void setJsonRaw(JsonNode jsonNode) {
// this leads to non-standard json, see discussion:
// setJson(jsonNode.toString());
StringWriter stringWriter = new StringWriter();
ObjectMapper objectMapper = new ObjectMapper();
JsonGenerator generator =
new JsonFactory(objectMapper).createGenerator(stringWriter);
generator.writeTree(n);
setJson(stringWriter.toString());
}
Ideally the ObjectMapper and even JsonFactory are from the context and are configured so as to handle your JSON correctly (standard or with non-standard values like 'Infinity' floats for example).
Here is a full working example of how to use Jackson modules to make #JsonRawValue work both ways (serialization and deserialization):
public class JsonRawValueDeserializerModule extends SimpleModule {
public JsonRawValueDeserializerModule() {
setDeserializerModifier(new JsonRawValueDeserializerModifier());
}
private static class JsonRawValueDeserializerModifier extends BeanDeserializerModifier {
#Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
builder.getProperties().forEachRemaining(property -> {
if (property.getAnnotation(JsonRawValue.class) != null) {
builder.addOrReplaceProperty(property.withValueDeserializer(JsonRawValueDeserializer.INSTANCE), true);
}
});
return builder;
}
}
private static class JsonRawValueDeserializer extends JsonDeserializer<String> {
private static final JsonDeserializer<String> INSTANCE = new JsonRawValueDeserializer();
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return p.readValueAsTree().toString();
}
}
}
Then you can register the module after creating the ObjectMapper:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonRawValueDeserializerModule());
String json = "{\"foo\":\"one\",\"bar\":{\"A\":false}}";
Pojo deserialized = objectMapper.readValue(json, Pojo.class);
I had the exact same issue.
I found the solution in this post :
Parse JSON tree to plain class using Jackson or its alternatives
Check out the last answer.
By defining a custom setter for the property that takes a JsonNode as parameter and calls the toString method on the jsonNode to set the String property, it all works out.
Using an object works fine both ways... This method has a bit of overhead deserializing the raw value in two times.
ObjectMapper mapper = new ObjectMapper();
RawJsonValue value = new RawJsonValue();
value.setRawValue(new RawHello(){{this.data = "universe...";}});
String json = mapper.writeValueAsString(value);
System.out.println(json);
RawJsonValue result = mapper.readValue(json, RawJsonValue.class);
json = mapper.writeValueAsString(result.getRawValue());
System.out.println(json);
RawHello hello = mapper.readValue(json, RawHello.class);
System.out.println(hello.data);
RawHello.java
public class RawHello {
public String data;
}
RawJsonValue.java
public class RawJsonValue {
private Object rawValue;
public Object getRawValue() {
return rawValue;
}
public void setRawValue(Object value) {
this.rawValue = value;
}
}
I had a similar problem, but using a list with a lot of JSON itens (List<String>).
public class Errors {
private Integer status;
private List<String> jsons;
}
I managed the serialization using the #JsonRawValue annotation. But for deserialization I had to create a custom deserializer based on Roy's suggestion.
public class Errors {
private Integer status;
#JsonRawValue
#JsonDeserialize(using = JsonListPassThroughDeserialzier.class)
private List<String> jsons;
}
Below you can see my "List" deserializer.
public class JsonListPassThroughDeserializer extends JsonDeserializer<List<String>> {
#Override
public List<String> deserialize(JsonParser jp, DeserializationContext cxt) throws IOException, JsonProcessingException {
if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
final List<String> list = new ArrayList<>();
while (jp.nextToken() != JsonToken.END_ARRAY) {
list.add(jp.getCodec().readTree(jp).toString());
}
return list;
}
throw cxt.instantiationException(List.class, "Expected Json list");
}
}

Categories

Resources