Kotlin / kotlinx.serialization

Kotlin multiplatform / multi-format serialization
Apache License 2.0
5.38k stars 620 forks source link

Give more information for custom Deserialization #2832

Open brh opened 6 days ago

brh commented 6 days ago

What is your use-case and why do you need this feature? This feature is needed to make migration easier. We are using Retrofit for now to make network calls and are looking to use kotlinx.serialization as it converter factory. I am a part of a team where we have a large model set that was haphazardly migrated from Java. So a small portion of the models look like this NewsFeedItem <| ---- Post <| ------ Video Post <| ------ Gallery NewsFeedItem <| ---- Section <| ------ VideoSection
NewsFeedItem has just one property, Post has 22 properties, Video 6, Story 1.

NewsFeedItem is an abstract class, but Post is not.

So all of this is wrapped in a NewsFeed which has properties that are List<NewsFeedItem>and also List<Post>. That being said when you display this on screen it can be a list of Videos or a news stories, followed by a Video, which is then followed by a Section that is a group of Videos.

So far what we see is that when we deserialize (using Polymorphic without a lot of changes to our models) is that Video would be derialize and it's properties would be set, but the data that is defined in Post and NewsFeedItem it's superclass are not set they are all null.

We tried a custom KSerializer, but there seems to be no way to know what is in the Json being processed, decoder provides no key names and the order in the json coming from the server is not guaranteed.

Describe the solution you'd like Pass the JsonElement (same one that is passed into JsonContentPolymorphicSerializer.selectDeserializer) as a parameter to KSerializer.deserialize then at least we can query the json (element.jsonObject["kind"]?.jsonPrimitive?.content) and set the superclass values as needed. As going to all our classes and making each subclass override all the properties of it's superclass is not practical for this project.

brh commented 6 days ago

So for instance this in TreeJsonDecoder.kt:

@JsonFriendModuleApi
public fun <T> readJson(json: Json, element: JsonElement, deserializer: DeserializationStrategy<T>): T {
    val input = when (element) {
        is JsonObject -> JsonTreeDecoder(json, element)
        is JsonArray -> JsonTreeListDecoder(json, element)
        is JsonLiteral, JsonNull -> JsonPrimitiveDecoder(json, element as JsonPrimitive)
    }
    return input.decodeSerializableValue(deserializer)
}

Would change to this possibly:

@JsonFriendModuleApi
public fun <T> readJson(json: Json, element: JsonElement, deserializer: DeserializationStrategy<T>): T {
    val input = when (element) {
        is JsonObject -> JsonTreeDecoder(json, element)
        is JsonArray -> JsonTreeListDecoder(json, element)
        is JsonLiteral, JsonNull -> JsonPrimitiveDecoder(json, element as JsonPrimitive)
    }
    return input.decodeSerializableValue(deserializer, json)   <----------------- add param which needs to be pass to subsequent calls
}
brh commented 6 days ago

After inspecting the code a little more seems this may not be possible, if not could JsonTransformingSerializer be augmented with a post object creation processor that also has the JsonElement as a parameter.

protected open fun transformDeserializedObject(object: T, element: JsonElement): JsonElement