question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

deduction based polymorphism

See original GitHub issue

I would like to communicate the need of the deduction based polymorphism feature, that has been recently implemented in jackson. With the help of jackson-module-kotlin it’s possible to de-serialize the following jsons with no need of configuring/transmitting/maintaining additional type information.

@JsonTypeInfo(use=Id.DEDUCTION) class Shape

data class Polygon(val sides: Int, val sideLength: Int) : Shape

data class Circle(val radius: Int) : Shape

{"sides" : 3, "sideLength" : 10 } {"radius": 5 }

ObjectMapper mapper = new ObjectMapper().registerModule(new KotlinModule()); mapper.registerModule(KotlinModule());

mapper.readValue(" {\"radius\": 5 } ", Shape.class); mapper.readValue(" {\"sides\" : 3, \"sideLength\" : 10 } ", Shape.class)

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
lukaszgendekcommented, Nov 16, 2021

This array of sealedSubclasses

private val sealedSubclasses: Array<KSerializer<out T>> 

you’ve mentioned above is tricky to pass, since there is more than just 1st level of subclasses, there is the whole sealed class hierarchy to pass.

So needed to try out something different and taken look at JsonTransformingSerializer which seems to be a good fit. Suppose we have a Project and the ProjectSerializer.

@Serializable(with = ProjectSerializer::class)
sealed class Project

object ProjectSerializer : DeductionSealedClassDeserializer<Project>(Project.serializer())

open class DeductionSealedClassDeserializer<T : Any>(private val tSerializer: KSerializer<T>) :
    JsonTransformingSerializer<T>(tSerializer) {

    override fun transformDeserialize(element: JsonElement): JsonElement {
		// there is enough information to implement logic
		// i.e. information about all the fields of T sealed subclasses in tSerializer.descriptor
		// so we are able to pick up the right subclass and inject the relevant "type" field
		...
	}
}

I’ve noticed that the moment we annotate the Project to use a serializer, the compiler plugin stops generating the serialization code, but only plugs in the specified serializer, which prevents me from providing the correct input for the JsonTransformingSerializer. Please take a look below at the generated code.

// The generated code
public abstract class Project {
	//...
  public static final class Companion {
    private Companion() {}
        // ...
    @NotNull
    public final KSerializer<Project> serializer() {
      return ProjectSerializer.INSTANCE;		// annotating the class to use the given serializer
							// prevents compiler from generating the original serializer code
    }
  }
}

I am able to see the workaround. That is that we don’t need to annotate the Project class itself to use the ProjectSerializer, but we can annotate all the referencing properties instead, e.g.

@Serializable
@JvmInline
value class ProjectWrapper(val project: @Serializable(ProjectSerializer::class) Project)

then assuming we use the ProjectWrapper, it works.

However I don’t like this approach of annotating the references instead of the main class. My particular case is that the Project class will always be deserialized in one specified way. It’s hard to ensure that all references to the Project class will be annotated. So this approach is error prone.

I don’t like the kotlinx serialization library being designed the way, that we can bind a class to a serializer, only if the serialize is built from scratch and not when it’s composed from the serializer generated by the compiler plugin. Overall I believe that the kotlinx serialization is a very interesting and inspiring library. Please allow me to share the idea how to extend it with new function.

My Proposition

I imagine that instead of providing KSerializer<T> object, we could extend the functionality of the library to be able to provide a function (KSerializer<T>) -> KSerializer<T> e.g.

@Serializable(with = ProjectSerializerFunc::class)
sealed class Project

object ProjectSerializerFunc : DeductionSealedClassSerializer<Project>()
open class DeductionSealedClassSerializer<T> : (KSerializer<T>) -> KSerializer<T> {
    override fun invoke(originalSerializer: KSerializer<T>): KSerializer<T> {
        TODO("Here we can use an original serializer generated by the compiler plugin")
    }
}

that takes the compiler-generated serializer and produces a new one (e.g. based on the JsonTransformingSerializer transformation). So the compiler could still generate the original serializer, but on top of it would also apply the function above.

Could you please tell me what do you think about it?

0reactions
sandwwraithcommented, Nov 11, 2021

In Example 19, Internal API is used to provide the correct descriptor. If you do not need a descriptor (it is not used in JSON serialization for now), you can leave the default one.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Deduction-Based Polymorphism in Jackson 2.12 - Baeldung
In this tutorial, we'll explore how we can use the deduction-based polymorphism feature from the Jackson library.
Read more >
Jackson 2.12 Most Wanted (1/5) - cowtowncoder - Medium
Deduction -Based Polymorphism ... The oldest open feature request jackson-databind#43 — filed in 2012 — was to support polymorphic types that do not...
Read more >
Polymorphic subtype deduction based on 'presence ... - GitHub
Polymorphic subtype deduction based on 'presence' of fields does not appear to take 'absence' of fields into account #2976.
Read more >
JsonTypeInfo.Id (Jackson-annotations 2.12.0 API) - FasterXML
Means that no serialized typing-property is used. Types are deduced based on the fields available. Deduction is limited to the names of fields...
Read more >
Handling Polymorphism in Automated Deduction | SpringerLink
Polymorphism has become a common way of designing short and reusable programs by abstracting generic definitions from type-specific ones.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found