是否可以让杰克逊将嵌套对象序列化为字符串
Posted
技术标签:
【中文标题】是否可以让杰克逊将嵌套对象序列化为字符串【英文标题】:Is it possible to make Jackson serialize a nested object as a string 【发布时间】:2018-12-10 23:52:36 【问题描述】:鉴于这些类:
@Value
private static class Message
private final String type;
private final MyType message;
@Value
public class MyType
private final String foo;
杰克逊将产生:
"Type" : "Test",
"Message" : "foo" : "bar"
我可以给 Jackson 提供某种类型的注释或指令,要求它将嵌套的复杂类型序列化为字符串,例如所需的 JSON 将是:
"Type" : "Test",
"Message" : "\"foo\" : \"bar\""
我在消息字段上尝试了这两个注释:
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JsonSerialize(as=String.class)
两者都没有预期的影响。现在我的“hack”是在施工时做到这一点:
return new Message("Test", mapper.writeValueAsString(new MyType("bar")));
我想我可以编写一个自定义序列化程序,但我想知道这是否是某种内置的标准行为。我的用例是我正在构建一个 JSON
有效负载,它应该有一个字符串消息包含在它本身包含JSON
。
环境
Jackson 版本是 2.9.0,在 Java 10 上使用 Spring Boot 2。
【问题讨论】:
【参考方案1】:可以使用自定义序列化器来完成:
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\""
【讨论】:
【参考方案2】:我也找不到内置解决方案,最终编写了自定义转换器:
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);
用法:
@Value
private static class Message
private final String type;
@JsonSerialize(converter = ObjectToJsonStringConverter.class)
private final MyType message;
【讨论】:
以上是关于是否可以让杰克逊将嵌套对象序列化为字符串的主要内容,如果未能解决你的问题,请参考以下文章
System.Text.Json - 将嵌套对象反序列化为字符串