Это можно сделать с помощью специального сериализатора:
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);
}
}
и десериализатор:
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);
}
}
}
}
Поле должно быть аннотировано @JsonSerialize и @JsonDeserialize (при необходимости)
class Outer {
@JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.CLASS)
@JsonSerialize(using = EscapedJsonSerializer.class)
@JsonDeserialize(using = EscapedJsonDeserializer.class)
public Foo val;
}
Он хорошо работает с простыми коллекциями (списками, массивами) и в некоторой степени с полиморфизмом, хотя для конкретных проблем, связанных с полиморфизмом, может потребоваться более сложное решение.
Пример вывода выглядит так:
{"val":"{\"foo\":\"foo\",\"@class\":\"org.test.Foo\"}"}
{"val":"{\"foo\":\"foo\",\"bar\":\"bar\",\"@class\":\"org.test.Bar\"}"}