Допустим, вы получаете список объектов из вызова API в формате JSON, и этот объект ссылается на аннотированный класс JsonIdentityInfo. Если вы пересериализуете список с большим количеством объектов, вы получите 2 определения объекта JSON для аннотированного объекта JsonIdentityInfo, если он повторяется в добавленных элементах списка (что вызывает InvalidDefinitionException при попытке его десериализации снова). Похоже, что при десериализации задействовано некоторое кэширование, поэтому предыдущее значение было использовано повторно. Можно ли избежать такого поведения?
Я разрабатываю для Android с Kotlin, что означает, что я использую Kotlin Jackson.
Я сделал следующий код для иллюстрации происходящего:
fun test_jsonIdentityInfo_reserialization(){
val bar = Bar(1, "bar")
val f1 = Foo("firstFoo", bar)
val f2 = Foo("secondFoo", bar)
val l1 = listOf(f1, f2)
val firstListString = JsonMapper.serialize(l1) ?: ""
println("First list serialized:")
println(firstListString)
println()
val firstListDeserialized = JsonMapper.deserialize<Collection<Foo>>(firstListString)?.toList() ?: emptyList()
println("First list desserialized:")
println(firstListDeserialized)
println()
val f3 = Foo("thirdFoo", bar)
val f4 = Foo("fortyFoo", bar)
val l2 = ... // See bellow what I tried
val secondListString = JsonMapper.serialize(l2) ?: ""
println("Second list serialized:")
println(secondListString)
println()
val secondListDeserialized = JsonMapper.deserialize<Collection<Foo>>(secondListString) ?: emptyList()
println("Second list desserialized:")
println(secondListDeserialized)
println()
}
data class Foo(
@JsonProperty("name")
val name: String = "",
@JsonProperty("bar")
val bar: Bar)
@JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator::class, scope = Bar::class)
data class Bar(
@JsonProperty("id")
val id: Int = -1,
@JsonProperty("name")
val name: String = "")
object JsonMapper {
private val mapper: ObjectMapper by lazy {
ObjectMapper().registerKotlinModule().apply{
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
}
}
inline fun <reified T> reify(): TypeReference<T> =
object : TypeReference<T>(){}
fun <T> serialize(data: T, ref: TypeReference<T>): String? =
mapper.writerFor(ref).writeValueAsString(data)
inline fun <reified T> serialize(data: T): String? =
serialize(data, reify())
fun <T> deserialize(data: String, type: TypeReference<T>): T? =
mapper.readValue(data, type)
inline fun <reified T> deserialize(data: String): T? =
deserialize(data, reify<T>())
}
Я попробовал следующее, изменив значение 'l2'.
когда:
val l2 = listOf(f1, f2, f3, f4)
или
val l2 = l1 + listOf(f3, f4)
из
First list serialized:
[{"name":"firstFoo","bar":{"id":1,"name":"bar"}},{"name":"secondFoo","bar":1}]
First list desserialized:
[Foo(name=firstFoo, bar=Bar(id=1, name=bar)), Foo(name=secondFoo, bar=Bar(id=1, name=bar))]
Second list serialized:
[{"name":"firstFoo","bar":{"id":1,"name":"bar"}},{"name":"secondFoo","bar":1},{"name":"thirdFoo","bar":1},{"name":"fortyFoo","bar":1}]
Second list desserialized:
[Foo(name=firstFoo, bar=Bar(id=1, name=bar)), Foo(name=secondFoo, bar=Bar(id=1, name=bar)), Foo(name=thirdFoo, bar=Bar(id=1, name=bar)), Foo(name=fortyFoo, bar=Bar(id=1, name=bar))]
Это желаемый результат, где 'bar' определяется как объект только первый раз при второй десериализации.
когда:
val l2 = firstListDeserialized + listOf(f3, f4)
из
First list serialized:
[{"name":"firstFoo","bar":{"id":1,"name":"bar"}},{"name":"secondFoo","bar":1}]
First list desserialized:
[Foo(name=firstFoo, bar=Bar(id=1, name=bar)), Foo(name=secondFoo, bar=Bar(id=1, name=bar))]
Second list serialized:
[{"name":"firstFoo","bar":{"id":1,"name":"bar"}},{"name":"secondFoo","bar":1},{"name":"thirdFoo","bar":{"id":1,"name":"bar"}},{"name":"fortyFoo","bar":1}]
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `io.ubivis.ubivismobile.ExampleUnitTest$Bar`, problem: Already had POJO for id (java.lang.Integer) [[ObjectId: key=1, type=com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator, scope=io.ubivis.ubivismobile.ExampleUnitTest$Bar]]
at [Source: (String)"[{"name":"firstFoo","bar":{"id":1,"name":"bar"}},{"name":"secondFoo","bar":1},{"name":"thirdFoo","bar":{"id":1,"name":"bar"}},{"name":"fortyFoo","bar":1}]"; line: 1, column: 124] (through reference chain: java.util.ArrayList[2]->io.ubivis.ubivismobile.ExampleUnitTest$Foo["bar"])
Это то, что я получаю. Обратите внимание, что 'bar' определяется как объект в 'firstFoo' и 'thirdFoo' во второй сериализации. Также обратите внимание, что firstListDeserialized - это другой список, который не содержит «f1» и «f2» (у них просто есть объекты с одинаковыми значениями), и является результатом десериализации «l1». Я не могу использовать первое решение, потому что 'f1' и 'f2' будут получены из вызова API, поэтому они уже будут сериализованы.