使用 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<Error<String>>
您还可以在 Error
中为 when
中的逻辑创建一个方法
* 根据您的具体情况,可能会有更优雅或更有用的解决方案。
【讨论】:
以上是关于使用 jacksonObjectMapper 将 JSON 反序列化为不同类型的主要内容,如果未能解决你的问题,请参考以下文章
如何获取 Spring 4.1 使用的 Jackson ObjectMapper?
Spring Boot 不使用配置的 Jackson ObjectMapper 和 @EnableWebMvc
如何使用 Spring Cloud Netflix Feign 设置自定义 Jackson ObjectMapper