Way to transform class fields from/to nullables


#1

First, let’s thank you for Kotlin that we find really great. Now our question :

Let’s say I have a class Person ( val firstName:String, val lastName:String, val optionalSomething: String? )
Using this class with all fields set to not-null is great, evertyhing is under strict null control… while the software doesn’t interact with outer environment

In some scenarios ( filtering user input for example, i/o, etc.) we want to use the same class, maybe with a higher order definition, but find a way to indicate that all fields are at some point nullable.

For example it could be something like Partial where we would end up with it being defined as ( val firstName:String?, val lastName:String?, val optionalSomething: String? )

We agree that we could create manually another class like PartialPerson, but in large domain-driven cases, code is automatically generated, and in our case we end up with 500+ such objects with an average of 20+ (sometimes 50+) fields for each.
So it’s impossible to create manually new classes and their adapters just to “nullify” fields. It would be a burden to make code generators generate partials and also would be complicated for developer.

Is there something we missed in language features or does somebody has an idea on how to do that ?

Thank you all


#2

Would lateinit be an option? See https://kotlinlang.org/docs/reference/properties.html#late-initialized-properties-and-variables


#3

@Varia Unfortunatly not, the goal is not the check “inside” an instance if the field is null, it’s from outside the instance.

data class Person(val name:String)
val personAllFieldNotNull = Person("name")
val personNameSafe = personAllFieldNotNull.name // shall lend to String
val personSomeFieldsNullables = Partial<Person>(null)
val personNameUnsafe = personSomeFieldsNullables.name // shall lend to String?

#4

Somehow you will need to have multiple types. What you could do if you don’t want to share multiple implementations is something like

interface Person { val name:String }
interface NullablePerson { 
  val nullableName:String?
  fun asPerson():Person
}
internal data class PersonImpl(override val nullableName:String?)
    : Person, NullablePerson {
  val name:String get() = nullableName!!
  fun asPerson(): Person = apply {
    if(nullableName==null) throw NullPointerException("nullable name is null")
  }
}

The different property names are needed because the different types.


#5

@pdvrieze this is what we want to avoid
we don’t want to rewrite the classes because of nullability attibutes changes, we need a system to take the base class (Person) and transform it as a NullableFields . I understand your answser considering that nullable to not nullable fields means that the class is not the same, and when we can we work like this, but in this case this is just not possible to demultiply the number of classes and properties.
We have 500+ business classes like that and hundred of thousands of lines of code using them.


#6

@sebastienjust I wonder what the aims are. In IO like scenarios what you normally want is some sort of builder that doesn’t have integrity checks, but will be validated on build. This means that you either have a builder class or do a trick as I showed (you can use var instead of val) where you validate on transition. My trick is still typesafe, but can have less overhead.

To create these types, as long as they are well-behaved it would be possible to use annotation processors/codegenerators to create it. Builders in particular should just be POJOs/POKOs. There is no language feature where nullability is configured as a property of the type usage (perhaps except using generics, but that becomes unmanageable very quickly).


#7

Well you could use annotation processing to generate the nullable version of a class.