【问题标题】:Deserialize JSON to Different types with jacksonObjectMapper使用 jacksonObjectMapper 将 JSON 反序列化为不同类型
【发布时间】:2019-01-27 08:20:57
【问题描述】:

假设我收到来自服务器的错误 JSON,它可以是两种不同的结构:

{"errorMessage": "This action is unauthorized."}

{"errorMessage":{"changePassword":"Old password is incorrect."}}

如何反序列化这种 json?

我尝试了什么

我试图让抽象类“错误”和两个孩子:

abstract class Error() {}

data class SingleError(val errorMessage: String) : Error() 

data class MultiError(val errorMessage: Map<String, String>) : Error() 

那我试试:

jacksonObjectMapper().readValue<Error>(response.body)

要反序列化,但我有例外:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.fifth_llc.siply.main.request.Forbidden$Error` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
     at [Source: (String)"{"errorMessage":{"changePassword":"Old password is incorrect."}}"; line: 1, column: 1]

我也尝试过 JsonDeserialize 注释,但如果我想解析为具体类型,似乎可以使用它:

@JsonDeserialize(`as` = MultiError::class)

有什么帮助吗?

【问题讨论】:

    标签: java android json kotlin jackson


    【解决方案1】:

    自定义反序列化方法:

    sealed class Error() {
        data class SingleError(val errorMessage: String) : Error()
        data class MultiError(val errorMessage: Map<String, String>) : Error()
    }
    ...
    class ErrorDeserializer : StdDeserializer<Error>(Error::class.java) {
    
        companion object {
            private val MAP_TYPE_REFERENCE = object : TypeReference<Map<String, String>>() {}
        }
    
        @Throws(IOException::class, JsonProcessingException::class)
        override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): Error {
            val mapper = jp.codec as ObjectMapper
            val node: JsonNode = mapper.readTree(jp)
            val msgNode = node.get("errorMessage")
            if (msgNode.isValueNode) {
                val errorMsg = msgNode.asText()
                return Error.SingleError(errorMsg)
            } else {
                val errorMsgs = mapper.readValue<Map<String, String>>(msgNode.toString(), 
                        MAP_TYPE_REFERENCE)
                return Error.MultiError(errorMsgs)
            }
        }
    }
    

    用法:

    val mapper = ObjectMapper()
    val module = SimpleModule().addDeserializer(Error::class.java, ErrorDeserializer())
    mapper.registerModule(module)
    
    val error = mapper.readValue<Error>("json content", Error::class.java)
    when (error) {
        is Error.SingleError -> {
            // error.errorMessage
        }
        is Error.MultiError -> {
            // error.errorMessage
        }
    }
    

    【讨论】:

    • 如果Error 用于另一个类,你会怎么做? data class Message(val error: Error)
    【解决方案2】:

    据我所知,至少我经历过,你不能将抽象类型分配给 readValue 方法,你必须在 readValue parameterizedType 和它的属性中指定它的具体类型。

    jacksonObjectMapper().readValue<Error>(response.body)
    

    在上面的行中,您必须将 Error 替换为您希望的子类型之一,然后您必须替换 Map bye HashedMap

    data class MultiError(val errorMessage: Map<String, String>) : Error()
    

    另一种方法是在 json 对象中使用 @JsonTypeInfo 来通知 JackSon 将确切的对象类型作为元数据包含到 json 流中。此解决方案需要在服务器端和客户端应用程序中进行代码更正。参考以下链接:

    https://fasterxml.github.io/jackson-annotations/javadoc/2.4/com/fasterxml/jackson/annotation/JsonTypeInfo.html

    【讨论】:

      【解决方案3】:

      问题在于 Jackson 要么需要 JsonCreator,要么需要一个空的构造函数。

      数据类没有空的构造函数。如果你想要一个空的构造函数,你可以使用Kotlin No-Args plugin.

      分级:

       dependencies {
       classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlinVersion"
           ... 
       }
       ...
       apply plugin: "kotlin-noarg"
       ...
       noArg {
          annotation("com.your.package.NoArgs")
       }
      

      现在,创建自定义注释:

      package com.your.package
      
      annotation class NoArgs
      

      现在最简单的 * 方法是使用泛型类型作为您的错误消息并使用自定义注释:

      @NoArgs
      data class Error<out T>(val errorMessage: T)
      
      val x = ObjectMapper().readValue<Error<*>>(json1)
      val y = ObjectMapper().readValue<Error<*>>(json2)
      
      when(y.errorMessage) {
        is String -> println("I'm a String!")
        is Map<*,*> -> println("I'm a Map")
        else -> println("I'm something else!")
      }
      

      您也可以在readValue 上显式定义类型,如果您提前知道的话:readValue&lt;Error&lt;String&gt;&gt;

      您还可以在 Error 中为 when 中的逻辑创建一个方法


      * 根据您的具体情况,可能会有更优雅或更有用的解决方案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-20
        • 2023-04-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多