使用 jacksonObjectMapper 将 JSON 反序列化为不同类型

Posted

技术标签:

【中文标题】使用 jacksonObjectMapper 将 JSON 反序列化为不同类型【英文标题】:Deserialize JSON to Different types with jacksonObjectMapper 【发布时间】: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)

有什么帮助吗?

【问题讨论】:

【参考方案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 中的逻辑创建一个方法


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

【讨论】:

以上是关于使用 jacksonObjectMapper 将 JSON 反序列化为不同类型的主要内容,如果未能解决你的问题,请参考以下文章

如何获取 Spring 4.1 使用的 Jackson ObjectMapper?

Spring Boot 不使用配置的 Jackson ObjectMapper 和 @EnableWebMvc

如何使用 Spring Cloud Netflix Feign 设置自定义 Jackson ObjectMapper

Jackson ObjectMapper类使用解析

无法使用 Jackson ObjectMapper 将 Json 序列化为 Java 对象

Jackson ObjectMapper - 指定对象属性的序列化顺序