jackson-module-kotlin: Using Kotlin Default Parameter Values when JSON value is null and Kotlin parameter type is Non-Nullable

I’ve got the following simplified JSON example, which I’m trying to decode into the simplified Kotlin Data Class below.

{ 
   "boolField": null,
    "stringField": null
}
data class TestObject(
        val boolField: Boolean = true,
        val stringField: String = "default"
)

The key thing here is that the Kotlin properties are not nullable, but there is a known default value for them. However, the JSON sometimes contains null for those fields.

I am trying to get the example JSON to decode using the default values in place of the nulls since the type is non-nullable. However, this doesn’t work out of the box, instead throwing a MissingKotlinParameterException.

I had a look at modifying the code with a feature flag to behave the way I wanted. This was easy enough to do with some minor alterations to createFromObjectWith() in KotlinValueInstantiator for the String case. However, for the Boolean case it does not work, as in Java, that non-optional Boolean becomes a boolean primitive type, which cannot take null and thus Jackson Data Binding sets it with the default value of false.

So, assuming I haven’t missed the point completely with this, I’m wondering if there’s a way in the KotlinValueInstantiator to know that the primitive types were set with their default values by Jackson Data Binding in order to make this work for primitive types too?

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 27
  • Comments: 40 (13 by maintainers)

Commits related to this issue

Most upvoted comments

There are conflicts with various settings and picking one model.

Kotlin itself does not treat null and as the same. Neither does Jackson by default which would throw an exception for null being set into a primitive depending on the setting of the FAIL_ON_NULL_FOR_PRIMITIVES feature switch.

For a non nullable type should we treat null as when there is a default value available in the method signature? what about a nullable type? how do we know when null is intentional versus meaning ?

expected behavior (at least for me):

  • trying to set null to a non-nullable variable (e.g. String) if variable has default value, use it. otherwise throw exception
  • trying to set null to a nullable variable (e,g, ‘String?’) set to null regardless if there is a default value or not (as the developer has explicitly declared that null is acceptable).

Your workaround for now is to remove the null values from your JSON completely to mean “absent” instead of using null

not a good solution as many developers are using 3rd party APIs which they have no control over

Branch 2.10 now allows null to be treated as “use the default” value, but requires that you set a flag when creating the Kotlin module, otherwise the behavior stays as it was as the default behavior.

ObjectMapper().registerModule(KotlinModule(nullisSameAsDefault = true)

I’ve also noticed that, an Int field, regardless of whether or not it has a default value, will be set to 0 if the parsed JSON field is null.

I think, if the Int field is non-null, then an exception is more appropriate, as null and zero are not the same thing. Perhaps the MissingKotlinParameterException should be the default behaviour, and some kind of annotation could set an option to allow either converting nulls to 0, false, “” etc, or to allow replacing the input null with the default value if a default value is defined.

I think that in some cases, null is a perfectly valid value to use instead of the default value, and the absence of a field in the JSON we are deserialising is the only time where the default value always makes sense.

I’m facing this issue in its more basic form. I have a non nullable no default value Kotlin Boolean value. Since Kotlin’s Boolean is an alias of the primitive, like Int, the module doesn’t require the value in the JSON input.

I totally aggree with both @crypticmind and @gerob311. If I write a Kotlin data class this way : data class TestObject( val myInteger: Int ) i need myInteger to be present, not default to 0. It should throw MissingKotlinParameterException if absent.

If i wanted myInteger to default to 0 in case of missing, i would have written : data class TestObject( val myInteger: Int? ) and then later testObject.myInteger ?: 0 in my code. This way, I can default to 1 or -1 if I prefer

Please remove || paramDef.isPrimitive() here : https://github.com/FasterXML/jackson-module-kotlin/blob/c8d88c664731f31cb2cc00ed08f9d6b98197a8a9/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt#L45

also curious about this. i am using a third party REST api which i have no control over and theoretically everything could be null… i have 2 possible solutions/workarounds in mind but neither of them is good enough…

  1. use custom setter such as:
class Person(var name:String)
{
    @jsonSetter("name")
    fun setName(value: String?)
    {
        field = value ?: "default value"
    }
}

the problem with this solution is a lot of boilerplate!. i gave a very simple example with only 1 variable, but in reality there are many more…

  1. make all fields nullable:
class Person(var name: String?, var lastName: String?, var middleName:String?, var isMale:Boolean?, var mom:Person?, var dad:Person?, var friends: List<Person?>? /*and so on and so on...*/)

obviously the problem with this is that its kind of stupid to make every single variable nullable, expecially given kotlins null-safety “feature”. also this would lead to a lot of null-handling everytime i want to access a variable

val user:Person = getUser()
tvName.text = user.name ?: "default value"
tvLastName.text = user.lastName ?: "default value"
tvMom.text = user.mom?.name ?: "default value"

//and so on and so on...

ideally i would have this:

class Person(name: String = "default value" /*other variables with default values*/)

and if in the json i get from the server name is null, then “default value” would be assigned name

how can i go about achieving this?

In my travels, I have discovered that enabling DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES will give a MissingKotlinParameterException if the input value is null. I think this isn’t a bad workaround, or maybe this is what was always intended in this module?

I’ve also discovered a slightly related case, which I’m not sure if it should be a separate ticket or not.

If I declare something like class TestObject(val timesOfDay: List<LocalTime>), Jackson allows null values to be deserialised into the list. For example, there are no exceptions if I deserialise {"timesOfDay": ["08:00", null, "09:00"]}. I would expect this should only be allowed if I declared class TestObject(val timesOfDay: List<LocalTime?>). Though, I’m not sure if type erasure means that this is impossible to fix.

It would be possible for Kotlin-module-provided AnnotationIntrospector to indicate that “null setter” logic was “use empty”. But someone has to provide the PR to do that, including tests. Asking or demanding that someone fix this will not help if no one is working on it. I do not work on Kotlin module since I don’t have enough Kotlin knowledge and since there is plenty of other necessary work (including support for modules lie Kotlin). I can help get things merged, answer questions on databind core logic. But not do fixes here.

I am also facing the same issue with deserialization of csv data containing null/empty values. Is there any update on this issue ?

On 2.13.0, I met the same issue with @alturkovic. It seems the feature doesn’t still work when using

val kotlinModule = KotlinModule.Builder()
            .configure(KotlinFeature.NullIsSameAsDefault, true)
            .build()
ObjectMapper()
        .registerKotlinModule()
        .registerModules(
            Builder()
                .enable(NullIsSameAsDefault)
                .build()
        )

@cowtowncoder That does not work with Kotlin’s data classes. I see an error being thrown saying a non-null value is being assigned to null.

I tried with DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES and it works as expected. I’m using Jackson 2.8.0 and jackson-module-kotlin 2.9.4.1 (I don’t know if these should match).