是否可以让杰克逊将嵌套对象序列化为字符串

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;

【讨论】:

以上是关于是否可以让杰克逊将嵌套对象序列化为字符串的主要内容,如果未能解决你的问题,请参考以下文章

将JSON对象反序列化为嵌套的C#对象

将嵌套的 JSON 字符串反序列化为 Java 对象

System.Text.Json - 将嵌套对象反序列化为字符串

Json.NET:反序列化嵌套字典

将 JSON 反序列化为 C# 对象以在网格中将嵌套数组显示为字符串

杰克逊 - 序列化日期对象