Can I create a data class at run-time?

Hello,

Can I create a totally new Kotlin (data) class type at run-time, dynamically ?
By this I mean that I want to create a totally new type dynamically, I am not asking about creating a new instance of a known type.

Basically I want to do at run-time what Kotlin compiler does at compile-time for serializable data classes.

Example of a serializable data class I want to create:

@Serializable
data class MyData( val f1: String, val f2: Boolean)

The reason I wanted to do this is so that I can create these data classes which will be used to serialize and de-serialize to/from Json (using Kotlinx Json library).
Right now there is IDEA plug-in which does it statically inside editor IDE, i.e. generating boiler-plate code. This approach/plug-in works well but it is static in nature.
Having an option of creating new data class types at run-time may be even better (?).

thank you

1 Like

You canā€™t create any class in runtime. You can use dynamic structures like maps or JsonObject to store dynamically created data.

In fact, there are ways in JVM involving runtime use of compiler or bytecode weather, but you should never use it.

1 Like

Funny. Actually you CAN. For example, you can generate&run kotlin script|java script|python etc from your ā€˜nativeā€™ code and in it you can define new type and produce its instances. But what will you do with those instances out of this script? Every type in your ā€˜nativeā€™ code must be resolved in compile-time, for casting you also use resolved types. Even using reflection you manipulate already resolved types.

Standard way to map dynamic (badly engeneered) json|xml api payload is to build hierarchical structure. For example JsonObject tree which is from-the-box solution in jdk. Or you can use one of mapping solutions like Gson|Jackson. They gives you ā€œadaptersā€ for serializing|deserializing specified nodes to minify brain damage from case-sensitive node types. For example i have ā€œstatusā€ field in server payload which can be array or integer. I define status array in my target type and use adapter which read integer or array from json field and produces array always. If you have more complex case you can use abstract class|interface and produce itā€™s children for different node types.

When you have already known static typed json payload - you just define your dataclass and produce itā€™s instance from payload using Gson|Jackson. And yes, you canā€™t work with dynamic types in static typed languages. Actually you canā€™t work with dynamic types in dynamic typed languages - they just decorate hierarchical structures which consist of primitive types for you. But itā€™s another story.

1 Like

There is no straightforward case where dynamically generating serialized classes actually has benefits. In case you need specific class details you need things to be statically available. In case you donā€™t care about content, you donā€™t need the serialization framework (just the parser or generic structures such as JsonObject).

It sounds like what @yuribudilov wants is to be able to construct JSON (de)serializers at runtime. That seems valuable to meā€¦ Iā€™d like to be able to dynamically accumulate json structure into some kind of JsonSpec and then be able to serialize/deserialize from that.

// dynamically build hierarchical json definitions
val dogSpec = JsonSpec() {
    addStringField("name")
}

val personSpec = JsonSpec() {
    addStringField("firstName")
    addStringField("lastName")
    addArrayField("dogs", dogSpec)
}

// create serializer customized to a definition
val serializer = personSpec.getSerializer() // KSerializer<JsonData>

// set values and stringify
val personData = personSpec.makeInstance() { // JsonData
    putStringValue("firstName", "Chip")
    putStringValue("lastName", "Douglas")
    putArrayValue("dogs", listOf(
        dogSpec.makeInstance() {
            putStringValue("name", "Fido")
        }
    ))
}

val jsonString = Json.stringify(serializer, personData)

// parse and retrieve values
val personParsed = Json.parse(serializer, jsonString) // JsonData

personParsed.getArrayValue("dogs").forEach {
    print(it.getStringValue("name"))
}

@nolanamy It is possible to to do such a thing. You would need to have a custom serializer/deserializer. This would then directly use the encoder/decoder to interact with the serialized form and then store it in whatever format is desired. It is even possible to special case certain encoders/decoders to get at their state for more power (the XML encoder/decoder library I have written allows you to do it so I can serialize/deserialize arbitrary xml into a buffer).

@pdvrieze Thanks, thatā€™s what I thought.

But I really shouldnā€™t be implementing my own json serializer/deserializer. The logic already exists in the kotlinx-serialization pluginā€¦

Why force us to define at compile time all types we want to serialize? It seems like Kotlin Serialization is really aimed at serializing and deserializing objects, not for building/parsing json for API communication. Is that right?

I was looking for a multiplatform solution for the latter ā€“ building/parsing json network payloads ā€“ and thought Kotlin Serialization was the preferred multiplatform solution.

But my json payloads are rather dynamic. And I donā€™t want to deserialize objects ā€“ I want to parse values from json payloads and use them to update existing objects.

Correct me if Iā€™m wrong: For multiplatform dynamic json building and parsing, I shouldnā€™t try to use Kotlin Serialization.

Dynamic serialization requires reflection.

The kotlinx-serialization library is specifically designed to work with code generation instead of reflection. Without knowing the types at compile time it cannot generate the code.

There are other libraries that serialize with reflection.

In our multiplatform project we are using kotlinx-serialization for network payloads. You just have to statically define your payloads. Dynamic parts can go into properties of type Map. If you really want everything to be dynamic, just serialize a map only.

The only downside of using Map is the syntactic overhead of filling the map instead of using the dot syntax for assigning values to properties.

So what you are really asking for is a nicer syntax for creating maps. If you are on JavaScript platform you could use the dynamic type to do that. On JVM platform Kotlin does not support it.

So I can build a json string from a Map? And parse a json string into a Map? I canā€™t seem to figure out how to do that ā€“ can you point me to an example?

It is indeed hard to find online.

For example, this will create you a serializer for a Map<String, String>:

val serializer = Pair(StringSerializer, StringSerializer).map

You can use arbitrary serializers for the key and value part. You could even built serializers for maps of maps, but I wouldnā€™t recommend that (in that case it is just easier to work with JsonObject).

No doubt that you will immediately see the downside of this though. You cannot serialize a Map<Any, Any> out of the box. The types for the key and value usually need to be known statically.

In my experience you donā€™t need any of this dynamic capabilities though. If you can provide a small example that uses dynamic payloads, I am pretty sure I could show a nice way of doing it with statically known types.