【问题标题】:Deserialize Scala map field from JSON using Jackson使用 Jackson 从 JSON 反序列化 Scala 映射字段
【发布时间】:2019-01-20 09:59:50
【问题描述】:

我正在尝试使用 Jackson ObjectMapper 将 Scala 类序列化/反序列化为 JSON。序列化工作正常,但我在尝试重新读取 JSON 时遇到类型异常。我通过添加适当的注释修复了大多数异常,但它不适用于我的 Map 成员......似乎杰克逊正在尝试处理键在 JSON 对象中作为类中的属性而不是映射中的键。 (我相信这与其他类似的问题不同,因为他们直接在地图内容上调用readValue。)

这是我的 ObjectMapper 设置:

val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)

这是我带注释的类和成员的样子:

@JsonInclude(JsonInclude.Include.NON_DEFAULT)
class MyClass extends Serializable {
  @JsonDeserialize(
    as = classOf[mutable.HashMap[String, Long]],
    keyAs = classOf[java.lang.String],
    contentAs = classOf[java.lang.Long]
  )
  val counts = mutable.Map.empty[String, Long]
}

如果我给它一些 JSON,例如:

{"counts":{"foo":1,"bar":2}}

然后用mapper.readValue[MyClass](jsonString)阅读它

我得到了一个像UnrecognizedPropertyException: Unrecognized field "foo" (class mutable.HashMap), not marked as ignorable 这样的异常。

我尝试将 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES 添加到我的映射器配置中,但在这种情况下似乎没有任何作用,而且我不确定这种全局设置是否可取。

如何说服 Jackson 将字符串“foo”和“bar”视为映射成员字段中的键,而不是 HashMap 类中的属性?它似乎做了正确的事情,自动把它写出来。

另外值得注意的是:反序列化在对临时文件或字符串变量的快速输出/输入单元测试中似乎可以正常工作,但当我尝试运行整个应用程序并且它读取之前编写的 JSON 时却不行。我不知道为什么它似乎在测试中起作用,据我所知,它正在拨打相同的readValue 电话。

【问题讨论】:

    标签: json scala jackson


    【解决方案1】:

    我做了一个这样的简单测试:

    case class TestClass (counts: mutable.HashMap[String, Long])
    

    我把它转换成这样:

    val objectMapper = new ObjectMapper() with ScalaObjectMapper
    objectMapper.registerModule(DefaultScalaModule)
    
    val r3 = objectMapper.readValue("{\"counts\":{\"foo\":1,\"bar\":2}}", classOf[TestClass])
    

    显然它对我有用。也许这与您使用的 Jackson 或 Scala 版本有关。例如,您是否尝试过不同版本的 Jackson?

    【讨论】:

    • 我使用的是fastxml(不是codehaus)包命名中的2.8.x。
    • 也许可以试试 "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.4",或者尝试使用案例类,或者代替 mapper.readValue[MyClass ](jsonString),尝试使用 mapper.readValue(jsonString, classOf[MyClass])。
    • 我认为我的映射器中存在竞争条件(我是从单例中使用的)...请注意,我在存储后在映射器上设置了功能;我切换到链接集合并添加了忽略:mapper = new ObjectMapper().registerModule(DefaultScalaModule).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
    【解决方案2】:

    试试 jsoniter-scala,你会喜欢这些天使用 Scala 解析和序列化 JSON 的便捷、安全和高效:https://github.com/plokhotnyuk/jsoniter-scala

    其中一个关键功能是能够在编译时生成编解码器,甚至打印其源代码。您将不会有任何运行时魔法,如反射或字节码替换会影响您的代码。

    【讨论】:

      【解决方案3】:

      我的问题是由于未在我的映射器配置单例中使用链接而导致的竞争条件。

      我的旧代码更像这样:

      private var mapper: ObjectMapper with ScalaObjectMapper = _
      
      def getMapper: ObjectMapper with ScalaObjectMapper = {
        if (mapper == null) {
          mapper = new ObjectMapper() with ScalaObjectMapper
          mapper.registerModule(DefaultScalaModule)
          mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
        }
        mapper
      }
      

      如您所见,如果一个线程初始化映射器,但尚未禁用未知属性故障,则第二个线程可能会返回并使用尚未设置该标志的映射器,这解释了我看到的原因仅在某些时候出现错误。

      正确的代码使用链接,以便映射器单例设置所有配置:

      private var mapper: ObjectMapper = _
      
       def getMapper: ObjectMapper = {
         if (mapper == null) {
           mapper = new ObjectMapper()
             .registerModule(DefaultScalaModule)
             .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
         }
         mapper
       }
      

      (我还删除了实验性的ScalaObjectMapper mix-in。)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-06-23
        • 2018-02-28
        • 2021-06-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-28
        • 2017-09-08
        相关资源
        最近更新 更多