杰克逊:反序列化为每个值都具有正确类型的 Map<String, Object>

Posted

技术标签:

【中文标题】杰克逊:反序列化为每个值都具有正确类型的 Map<String, Object>【英文标题】:Jackson: Deserialize to a Map<String, Object> with correct type for each value 【发布时间】:2015-03-08 09:41:00 【问题描述】:

我有一个如下所示的类

public class MyClass 
   private String val1;
   private String val2;
   private Map<String,Object> context;
   // Appropriate accessors removed for brevity.
   ...

我希望能够与 Jackson 一起从 object 到 JSON 再返回。我可以很好地序列化上面的对象并接收以下输出:


    "val1": "foo",
    "val2": "bar",
    "context": 
        "key1": "enumValue1",
        "key2": "stringValue1",
        "key3": 3.0
    

我遇到的问题是,由于序列化映射中的值没有任何类型信息,因此它们没有正确反序列化。例如,在上面的示例中,应该将 enumValue1 反序列化为枚举值,但反序列化为字符串。我已经看到了基于各种事物的类型的示例,但在我的场景中,我不知道类型是什么(它们将是用户生成的对象,我不会提前知道)所以我需要能够用键值对序列化类型信息。我如何与 Jackson 一起完成这项工作?

郑重声明,我使用的是 Jackson 2.4.2 版。我用来测试往返的代码如下:

@Test
@SuppressWarnings("unchecked")
public void testJsonSerialization() throws Exception 
    // Get test object to serialize
    T serializationValue = getSerializationValue();
    // Serialize test object
    String json = mapper.writeValueAsString(serializationValue);
    // Test that object was serialized as expected
    assertJson(json);
    // Deserialize to complete round trip
    T roundTrip = (T) mapper.readValue(json, serializationValue.getClass());
    // Validate that the deserialized object matches the original one
    assertObject(roundTrip);

由于这是一个基于 Spring 的项目,映射器的创建方式如下:

@Configuration
public static class SerializationConfiguration 

    @Bean
    public ObjectMapper mapper() 
        Map<Class<?>, Class<?>> mixins = new HashMap<Class<?>, Class<?>>();
        // Add unrelated MixIns
        .. 

        return new Jackson2ObjectMapperBuilder()
                .featuresToDisable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS)
                .dateFormat(new ISO8601DateFormatWithMilliSeconds())
                .mixIns(mixins)
                .build();
    

【问题讨论】:

好吧,Object 当然不会有任何类型信息;除了 1. 为您的 context 实例变量创建一个 POJO(首选)之外,别无他法;或 2. 不要使用 Map&lt;String, Object&gt;,而是使用 ObjectNode。无论如何,这是代码异味的主要示例。 context 变量的使用方式与 HttpSession 类似(准确地说,它代表 Spring Batch 的 ExecutionContext),所以我不会认为它是一种气味……就是这样Map 用于。我不明白的是为什么杰克逊不能添加指示类型的字段。如果它有值,它就有类,因此可以确定类型。我错过了什么? 它会在哪里添加这个字段?您只需告诉它反序列化为Object。至少,ObjectNode 的优势在于您可以直接获取 JSON;因此,您可以使用 JsonNode 提供的所有帮助查询字段的类型。 【参考方案1】:

我认为实现你想要的最简单的方法是使用:

ObjectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

这将在序列化的 json 中添加类型信息。

这是一个正在运行的示例,您需要适应 Spring:

public class Main 

    public enum MyEnum 
        enumValue1
    

    public static void main(String[] args) throws IOException 
        ObjectMapper mapper = new ObjectMapper();

        MyClass obj = new MyClass();
        obj.setContext(new HashMap<String, Object>());

        obj.setVal1("foo");
        obj.setVal2("var");
        obj.getContext().put("key1", "stringValue1");
        obj.getContext().put("key2", MyEnum.enumValue1);
        obj.getContext().put("key3", 3.0);

        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);

        System.out.println(json);

        MyClass readValue = mapper.readValue(json, MyClass.class);
        //Check the enum value was correctly deserialized
        Assert.assertEquals(readValue.getContext().get("key2"), MyEnum.enumValue1);
    


该对象将被序列化为类似于:

[ "so_27871226.MyClass", 
  "val1" : "foo",
  "val2" : "var",
  "context" : [ "java.util.HashMap", 
    "key3" : 3.0,
    "key2" : [ "so_27871226.Main$MyEnum", "enumValue1" ],
    "key1" : "stringValue1"
   ]
 ]

并且会被正确反序列化回来,断言也会通过。

顺便说一句,还有更多方法可以做到这一点,请查看https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization了解更多信息。

我希望它会有所帮助。

【讨论】:

这正是我正在寻找的。一个小问题。这是在全球范围内。有没有办法在字段级别指定这一点(只有一个字段我有这个问题,所以最好不要在 JSON 中到处乱扔类型)。没什么大不了的,但问一下也无妨。 我相信启用此选项可能会意外影响您的对象序列化生成的 JSON。所以我不支持为这种情况启用全局选项。我期望的是,当我将多态实例添加到 Map/Collection 中时,不会忽略类型序列化选项。但令人惊讶的是,当多态对象存在于容器/聚合对象下时,序列化选项会按预期应用。 我创建了一个支持我的语句的单元测试:gist.github.com/sermojohn/9e16e240aee733d9ad8318443576a696 所以...没问题,是吗?请注意,我指出还有其他方法可以解决这个问题。我认为您的解决方案在文档中被描述为“1.2. Per-class annotations”。 另请注意,到目前为止,Jackson 中大约 1/3 的已发布安全漏洞与默认类型有关。也许它们都已经找到并修复了,但也许还剩下一些。如果您正在处理不受信任的输入(例如,您从用户的 Web 浏览器获取 json 数据),我建议您寻找其他解决方案。

以上是关于杰克逊:反序列化为每个值都具有正确类型的 Map<String, Object>的主要内容,如果未能解决你的问题,请参考以下文章

反序列化的改造/杰克逊错误

杰克逊,反序列化具有私有字段的类和没有注释的 arg-constructor

杰克逊:将纪元反序列化为 LocalDate

杰克逊未能将字符串反序列化为 Joda-Time

C# XMLSerializer 将错误的类型反序列化为 List

如何将 JSON 反序列化为正确类型的对象,而无需事先定义类型?