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

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


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\"}"}


I also couldn't find built-in solution and ended up writing custom converter:

public class ObjectToJsonStringConverter extends StdConverter<Object, String> {    private final ObjectMapper objectMapper = new ObjectMapper();    @Override    public String convert(Object value) {        try {            return objectMapper.writeValueAsString(value);        } catch (JsonProcessingException e) {            throw new IllegalStateException(e);        }    }}

usage:

@Valueprivate static class Message {    private final String type;    @JsonSerialize(converter = ObjectToJsonStringConverter.class)    private final MyType message;}