Given these classes:
@Value
private static class Message {
private final String type;
private final MyType message;
}
@Value
public class MyType {
private final String foo;
}
Jackson will produce:
{
"Type" : "Test",
"Message" : {"foo" : "bar"}
}
Is there some type of annotation or instruction I can give to Jackson to ask it to serialize the nested complex type as a string, e.g. the desired JSON would be:
{
"Type" : "Test",
"Message" : "{\"foo\" : \"bar\"}"
}
I tried both of these annotations on the message field:
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JsonSerialize(as=String.class)
Neither has the desired impact. For now my "hack" is to do this at construction time:
return new Message("Test", mapper.writeValueAsString(new MyType("bar")));
I guess I could write a custom serializer, but I wondered if this is some type of standard behaviour that is built in. My use case is that I'm constructing a JSON
payload which is expected to have a string message contained within it that itself contains JSON
.
Jackson version is 2.9.0 using Spring Boot 2 on Java 10.
Mapping With Annotations To map the nested brandName property, we first need to unpack the nested brand object to a Map and extract the name property. To map ownerName, we unpack the nested owner object to a Map and extract its name property.
A JsonNode is Jackson's tree model for JSON and it can read JSON into a JsonNode instance and write a JsonNode out to JSON. To read JSON into a JsonNode with Jackson by creating ObjectMapper instance and call the readValue() method. We can access a field, array or nested object using the get() method of JsonNode class.
Jackson is a solid and mature JSON serialization/deserialization library for Java. The ObjectMapper API provides a straightforward way to parse and generate JSON response objects with a lot of flexibility. This article discussed the main features that make the library so popular.
Jackson can serialize and deserialize polymorphic data structures very easily. The CarTransporter can itself carry another CarTransporter as a vehicle: that's where the tree structure is! Now, you know how to configure Jackson to serialize and deserialize objects being represented by their interface.
let's serialize a java object to a json file and then read that json file to get the object back. In this example, we've created Student class. We'll create a student.json file which will have a json representation of Student object. Create a java class file named JacksonTester in C:\>Jackson_WORKSPACE. File: JacksonTester.java
Serialize List to XML The XmlMapper is able to serialize an entire Java bean into a document. To convert Java object to XML, we'll take a simple example with the nested object and arrays. Our intent is to serialize a Person object, along with its composed Address object, into XML.
Create a java class file named JacksonTester in C:\>Jackson_WORKSPACE. File: JacksonTester.java Compile the classes using javac compiler as follows:
Then to map ownerName, we unpack the nested owner object to a Map and extract its name property. We can instruct Jackson to unpack the nested property by using a combination of @JsonProperty and some custom logic that we add to our Product class:
It can be done with custom serializer:
class EscapedJsonSerializer extends StdSerializer<Object> {
public EscapedJsonSerializer() {
super((Class<Object>) null);
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
StringWriter str = new StringWriter();
JsonGenerator tempGen = new JsonFactory().setCodec(gen.getCodec()).createGenerator(str);
if (value instanceof Collection || value.getClass().isArray()) {
tempGen.writeStartArray();
if (value instanceof Collection) {
for (Object it : (Collection) value) {
writeTree(gen, it, tempGen);
}
} else if (value.getClass().isArray()) {
for (Object it : (Object[]) value) {
writeTree(gen, it, tempGen);
}
}
tempGen.writeEndArray();
} else {
provider.defaultSerializeValue(value, tempGen);
}
tempGen.flush();
gen.writeString(str.toString());
}
@Override
public void serializeWithType(Object value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException {
StringWriter str = new StringWriter();
JsonGenerator tempGen = new JsonFactory().setCodec(gen.getCodec()).createGenerator(str);
writeTree(gen, value, tempGen);
tempGen.flush();
gen.writeString(str.toString());
}
private void writeTree(JsonGenerator gen, Object it, JsonGenerator tempGen) throws IOException {
ObjectNode tree = ((ObjectMapper) gen.getCodec()).valueToTree(it);
tree.set("@class", new TextNode(it.getClass().getName()));
tempGen.writeTree(tree);
}
}
and deserializer:
class EscapedJsonDeserializer extends JsonDeserializer<Object> implements ContextualDeserializer {
private final Map<JavaType, JsonDeserializer<Object>> cachedDeserializers = new HashMap<>();
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
throw new IllegalArgumentException("EscapedJsonDeserializer should delegate deserialization for concrete class");
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
JavaType type = (ctxt.getContextualType() != null) ?
ctxt.getContextualType() : property.getMember().getType();
return cachedDeserializers.computeIfAbsent(type, (a) -> new InnerDeserializer(type));
}
private class InnerDeserializer extends JsonDeserializer<Object> {
private final JavaType javaType;
private InnerDeserializer(JavaType javaType) {
this.javaType = javaType;
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String string = p.readValueAs(String.class);
return ((ObjectMapper) p.getCodec()).readValue(string, javaType);
}
@Override
public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
throws IOException {
String str = p.readValueAs(String.class);
TreeNode root = ((ObjectMapper) p.getCodec()).readTree(str);
Class clz;
try {
clz = Class.forName(((TextNode) root.get("@class")).asText());
Object newJsonNode = p.getCodec().treeToValue(root, clz);
return newJsonNode;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}
The field should be annotated with @JsonSerialize and @JsonDeserialize (if needed)
class Outer {
@JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.CLASS)
@JsonSerialize(using = EscapedJsonSerializer.class)
@JsonDeserialize(using = EscapedJsonDeserializer.class)
public Foo val;
}
It works well with simple collections (list, arrays) and to some extent with polymorphism, although more elaborate solution may be needed for specific polymorphism related issues. Example output looks like this:
{"val":"{\"foo\":\"foo\",\"@class\":\"org.test.Foo\"}"}
{"val":"{\"foo\":\"foo\",\"bar\":\"bar\",\"@class\":\"org.test.Bar\"}"}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With