Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to make Jackson serialize a nested object as a string

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.

Environment

Jackson version is 2.9.0 using Spring Boot 2 on Java 10.

like image 206
David Avatar asked Jul 02 '18 17:07

David


People also ask

How do I map nested values with Jackson?

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.

How does Jackson read nested JSON?

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.

What is Jackson object serialization?

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.

Can Jackson serialize interface?

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.

How to serialize a Java object to a JSON file?

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

How do I serialize a list in Java to XML?

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.

How to create a jacksontester class in Java?

Create a java class file named JacksonTester in C:\>Jackson_WORKSPACE. File: JacksonTester.java Compile the classes using javac compiler as follows:

How to unpack the nested owner property in Jackson?

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:


1 Answers

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\"}"}
like image 115
vinga Avatar answered Oct 29 '22 13:10

vinga