Non null val initialization: alternative to lateinit/Delegates.notNull()

I’m trying to deserialize a JSON using Jackson and JsonSubTypes. I have a parent class that should be abstract and contain all common properties. A solution could be:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonSubTypes(
    JsonSubTypes.Type(value = Child1::class, name = "child1"),
    JsonSubTypes.Type(value = Child2::class, name = "child2")
)
abstract class Parent {
	val type: String? = null
	val commonProperty1: Any? = null
	val commonProperty2: Any? = null
	/* and so on with all common properties */
}

class Child1(val prop1: Any, val prop2: Any /*...*/) : Parent() {
	/* ... */
}

class Child2(val prop1: Any, val prop2: Any /*...*/) : Parent() {
	/* ... */
}

The reason why I’m not using a constructor in the Parent class is because it has too many properties and too many children.

Now, my problem with this solution is that certain properties of the Parent class are known to be not null, so I’m forced to use !! everywhere.
Delegates.notNull() does not work, when the property is accessed, IllegalStateException: Property {insert_property_name_here} should be initialized before is thrown.
Using lateinit var wouldn’t be correct, because I don’t want them to be able to be reassigned.

Another solution I found is using an extra property:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonSubTypes(
    JsonSubTypes.Type(value = Child1::class, name = "child1"),
    JsonSubTypes.Type(value = Child2::class, name = "child2")
)
abstract class Parent {
	val type: String? = null
	
    val commonNullableProperty: Any? = null
	
    @JsonProperty("commonNotNullableProperty") private val _commonNotNullableProperty: Any? = null
    val commonNotNullableProperty: Any get() = _commonNotNullableProperty!!

        /* ... */
}

But needless to say, this is awful

Is there another way of solving this?

Unless I am missing something (and I was based on my next reply), in your example can’t you just use interface for Parent:

interface Parent {
	val type: String
	val commonProperty1: Any
	val commonProperty2: Any
	/* and so on with all common properties */
}

or if it really needs to be an abstract class just make the properties abstract in the parent:

abstract class Parent {
	abstract val type: String
	abstract val commonProperty1: Any
	abstract val commonProperty2: Any
	/* and so on with all common properties */
}

OK, went back to look again and realized that you are wanting the parent to actually declare then instances.

This is what lateinit is designed for.

Yes, that’s true. But there’s no “lateinit val”, and I would like to avoid var properties

Some mocking libraries exploit a loop-hole in the kotlin compiler.

This does not throw a runtime exception:

class MyClass {
    fun <T> castedNull(): T = null as T
    val nonNull: String = castedNull()
}

Allowing “null as T” may require an annotation in future kotlin compiler versions.
Issue to remove loop-hole: https://youtrack.jetbrains.com/issue/KT-8135
Issue to keep loop-hole with annotation: https://youtrack.jetbrains.com/issue/KT-26946

1 Like

Wow, ok, that works. That should do the trick for now. Does anyone know if there’s a plan to implement something like a lateinit val or why is it not possible?

Thanks! :slight_smile:

I think the problem with lateinit val is that the compiler can’t know whether it’s the first time a value is set or a later time.

lateinit val foo: Foo

fun set1() {
    foo = Foo(1)
}
fun set2() {
    foo = Foo(2)
}

At that point all the compiler can do is one of 2 things. Either you don’t allow foo to be set in more than one place in the entire project (which is so limiting you might as well use lateinit var instead) or it can add a check whenever the property is used (basically simulating the delegate). I actually use a Delegate which does just that in most of my projects.
I’m not sure how well delegation works with serialization libraries. I guess not good but maybe that’s something those libraries could improve. It feels like an easier fix to me, instead of adding a language feature which can already be build inside of kotlin.

1 Like