Writing a Simple User Defined Type System in Kotlin — Part 1: Defining Types
Tags: Kotlin, User Defined Types

Writing a Simple User Defined Type System in Kotlin — Part 1: Defining Types James Follow Oct 15, 2020 · 4 min read

Eventually, most developers end up working on a project with User Defined Types. Often it’s the basis of a customisable forms system. For me, it was a school record system in which different schools tracked different pupil statistics.

When working with UDTs, it can be tempting to reach for a dynamically typed language, such as Javascript, and relying on the lack of strong typing to handle the changing type schema. However, a strongly typed UDT can be made in very few (<500) lines of kotlin, taking advantage of some of its features.

For a UDT, we need to be able to serialize not just the instances, but the type information too. For inspiration, we can look at Google’s Protobuf Struct types. Google have already worked out the hard details of how to structure a Type message — we’re going to adapt it slightly.

The first thing we’re going to need is 2 classes and an enum. I’m going to define them all in the same file, as the 2 classes are mutually dependant.

First, we have our DType class. This represents our UDT. I’ve not included the U in the name, as it’s going to be possible for us to manually create instances of DType too. (Type would possibly be a better name, but there are lots of packages that use Type and I don’t want to clash with them).

Kotlin’s data classes are one of its easiest to use features. For free, we get sensible toString, copy, componentN, and equals/hashCode methods, based on its properties. For properties, we’ve got a small set. An identifier — this should be validated to be something sensible for identifying your type in a database e.g. “pupil_record”. We have a name too, which can be more human readable, to be displayed in your frontend (matching example, “Pupil Record”). For my own laziness, I’ve set that to default to the identifier capitalized. Lastly we have a list of Fields (we’ll define field in a moment). As when defining a class, we need to tell our type what its structure is.

Inside the class, we have a secondary constructor using varags for the fields. This is just a convenience to make manually declared instances a bit more readable. We also have an invoke operator. This will allow us to call instances of DType as though they were a class constructor, and create a new instance.

e.g. if we have a pupil record with no fields (yet) we can do this:

not much different work to defining a regular Kotlin class, but we could draw the DType’s properties from any source we want, pretty neat.

Next, we Have the Fields. We’re going to use a similar model to Protobuf’s field, but make it a little simpler. We don’t need to account for Union Types or optional properties (we’ll make them all mandatory for now). We end up with something a little like this:

It’s again, very simple. A field has a Kind — as when you define a regular class the field has to be typed. I’ve selected a few Types as our “primitives”. you could add more, or fewer, whatever you want your basic building blocks to be. (note that for each kind, you’ll need to add to the serialization methods when saving and loading instances, which we’ll get to in Part 3).

A field also has an identifier and a name, just like a type. and finally, we set a flag for if this field is a list or not. This way we can define lists of any of the primitives without having to delve too deeply into generic typing.

This allows us to make basic structures, but what if we want to nest our DTypes? E.g. if we want to make a DateTime type, having date and time components, and then reuse it across our DTypes. We’ll extend our field a bit to do that.

We’ve added a new kind, Custom, that allows us to say this field is of a DType. We’ve also added an optional customKind field to specify which DType goes in this field. finally, we’ve added a little validation logic to the field to allow us to validate that either the field type is custom and a customKind is set, or the field type is something else and no custom kind is set.

With this, we have everything we need to start building custom types. A full example:

We’ve defined a pupil type, having an id, name and date of birth. and we’ve defined a type to hold a list of pupils.

That’s it for defining types. In Part 2, we’ll look at creating instances of those types.