【问题标题】:Issue when serializing Map.Entry using jackson使用杰克逊序列化 Map.Entry 时的问题
【发布时间】:2018-01-14 01:15:10
【问题描述】:

如果我尝试反序列化以下存储为字符串的类型:

 List<Entry<String, String>> entryList;

entryList 包含的位置:

[{"dummyKey1":"dummyValue1"}]

我收到以下错误

 Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.util.Map$Entry, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information.

在junit中运行测试用例时出现上述错误,但如果我删除测试用例,那么在部署后一切运行正常:

运行 junit 测试用例时出现上述错误,因为 Entry 中没有 NoArgsConstructor。所以,我创建了一个带有 NoArgsConstructor 的 DummyEntry,它以 null 为参数调用 Entry。

   DummyEntry<K, V> extends SimpleEntry<K, V>

进行此更改后,没有出现上述错误,但在部署更改后我开始出现以下错误。

  Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
  Unrecognized field "dummyKey1", not marked as ignorable (2 known properties: "value", "key"]).

为什么一种方法不适用于junit,但在生产中有效,而另一种方法在junit中有效但在生产中无效。

另外,我注意到另外一件事:在生产中, Map.Entry 被序列化为

  {'dummyKey1':'dummyValue1'}

而 junit 中的测试用例序列化与

相同的字符串
 {'key':'dummyKey1', 'value':'dummyValue1'}

这种奇怪行为的原因是什么?我怎样才能让这件事对双方都有效?

【问题讨论】:

  • 在 (a) 生产和 (b) 您的测试用例中使用的是什么版本的 jackson-databind 库?

标签: serialization junit jackson deserialization junit4


【解决方案1】:

我怀疑您可能会遇到Map.Entry 的不同序列化策略的问题。

在 jackson-databind Map.Entry was supported as a 'known type' 的 v2.5.0 (IIRC) 中。在此版本之前,Map.Entry 的 keyvalue 属性将出现在序列化的 Map.Entry 中。在这个版本之后,情况就不再如此了。

这里有一些示例测试用例说明了我的意思:

@Test
public void mapSerialisationPreJackson2_5_0() throws IOException {
  Map<String, String> aMap = Maps.newHashMap();
  aMap.put("dummyKey1", "dummyValue1");

  Set<Map.Entry<String, String>> incoming = aMap.entrySet();

  ObjectMapper objectMapper = new ObjectMapper();

  String serialised = objectMapper.writeValueAsString(incoming);

  // prints: [{"key":"dummyKey1","value":"dummyValue1"}]
  System.out.println(serialised);

  Set<Map.Entry<String, String>> deserialised = objectMapper.readValue(serialised, Set.class);

  // prints: [{key=dummyKey1, value=dummyValue1} (just like you posted in your question) whereas for versions > 2.5.0 the serialised form is ]
  System.out.println(deserialised);
}

@Test
public void mapSerialisationPostJackson2_5_0() throws IOException {
  Map<String, String> aMap = Maps.newHashMap();
  aMap.put("dummyKey1", "dummyValue1");

  Set<Map.Entry<String, String>> incoming = aMap.entrySet();

  ObjectMapper objectMapper = new ObjectMapper();

  String serialised = objectMapper.writeValueAsString(incoming);

  // prints: [{"dummyKey1":"dummyValue1"}]
  System.out.println(serialised);

  Set<Map.Entry<String, String>> deserialised = objectMapper.readValue(serialised, Set.class);

  // prints: [{dummyKey1=dummyValue1}]
  System.out.println(deserialised);
}

在 v2.5.0 之前,Map.Entry 将被序列化为 {key=dummyKey1, value=dummyValue1}(就像您在问题中发布的一样),而对于 > 2.5.0 的版本,序列化形式为 {dummyKey1=dummyValue1}

我认为您在测试上下文中使用的是 2.5.0 的 jackson-databind 版本

【讨论】:

  • 我正在使用 jackson 2.8 。我没有为测试上下文添加以前版本的杰克逊。我之前看到了这个链接github.com/fasterxml/jackson-databind/issues/565,和你的信息一样。当我指定对杰克逊 2.8 的依赖时,我无法弄清楚为什么 junit 选择旧版本的杰克逊
  • 您如何管理依赖项?你在使用 Maven 吗?摇篮?听起来您的测试类路径中有不同的 jackson-databind。
【解决方案2】:

为了能够将[{"dummyKey1":"dummyValue1"}] 反序列化为List&lt;Entry&lt;String, String&gt;&gt; 变量,您可以:

  1. 使用 Jackson 的参数名称模块。阅读更多here。它基本上允许使用带参数的非注释、非默认构造函数来反序列化类。在这种情况下,Map.Entry 的各种实现的构造函数。如果您仍然使用 Java 8,这是一个非常简单的解决方案。
  2. 如果您不能使用参数名称模块(例如 Java 7),您可以考虑使用 mixins 来注释类的构造函数,而无需修改其源代码。我尝试过,这很棘手。对于HashMap,例如Map.Entry 的实现是Node,它具有包私有可见性。

【讨论】:

    猜你喜欢
    • 2020-12-24
    • 1970-01-01
    • 1970-01-01
    • 2014-12-18
    • 2016-04-18
    • 1970-01-01
    • 2019-10-14
    • 1970-01-01
    • 2018-12-12
    相关资源
    最近更新 更多