Предположим, я получил ошибку от сервера как 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 annotaiton, но, кажется, я могу использовать его, если я хочу проанализировать конкретный тип:
@JsonDeserialize(`as` = MultiError::class)
Любая помощь?
3 ответа
Индивидуальный подход десериализатора:
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
}
}
Насколько я знаю, по крайней мере, я знал, что вы не можете назначать абстрактный тип методу readValue, вы должны указать его конкретный тип как в параметре readValue parameterizedType, так и в его свойствах.
jacksonObjectMapper().readValue<Error>(response.body)
В строке выше вы должны заменить Error одним из его подтипов, который вам нужен, и в следующем вы должны заменить Map
на HashedMap
data class MultiError(val errorMessage: Map<String, String>) : Error()
Другой способ заключается в использовании @JsonTypeInfo в объекте json для уведомления JackSon о включении точного типа объекта в качестве метаданных в поток json. Это решение требует исправления кода как на стороне сервера, так и на стороне клиента. Обратитесь к следующей ссылке:
Проблема в том, что Джексону нужен JsonCreator или пустой конструктор.
Классы данных не имеют пустых конструкторов. В любом случае, если вам нужен пустой конструктор, вы можете использовать Плагин Kotlin No-Args.
Gradle:
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
*
Похожие вопросы
Новые вопросы
java
Java - это язык программирования высокого уровня. Используйте этот тег, если у вас возникли проблемы с использованием или пониманием самого языка. Этот тег редко используется отдельно и чаще всего используется вместе с [spring], [spring-boot], [jakarta-ee], [android], [javafx], [hadoop], [gradle] и [maven].