Как повторно сериализовать список со ссылками на объект JsonIdentityInfo Джексона в Kotlin? - PullRequest
0 голосов
/ 10 января 2019

Допустим, вы получаете список объектов из вызова 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, поэтому они уже будут сериализованы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...