kotlinx.serialization icon indicating copy to clipboard operation
kotlinx.serialization copied to clipboard

Add option to encode only non-null default values in encodeDefaults

Open jschneider opened this issue 2 months ago • 9 comments

Currently, encodeDefaults = true writes all default values, while encodeDefaults = false omits all of them. There is no way to include only non-null defaults.

Motivation

In many API contexts, it is desirable to serialize default values that carry meaningful information but skip those that are null by default. This keeps payloads compact and avoids sending meaningless null entries while still preserving useful defaults.

@Serializable
data class UserProfile(
  val id: String,
  val nickname: String?,
  val displayName: String? = null,
  val country: String = "DE",
  val avatarUrl: String? = null,
)

Rationale:

This would allow developers to keep meaningful defaults (like "DE") while automatically skipping null defaults, avoiding the need for custom serializers or post-processing.

jschneider avatar Oct 22 '25 13:10 jschneider

Does

Json {
    encodeDefaults = true
    explicitNulls = false
}

achieve what you want?

sandwwraith avatar Oct 29 '25 11:10 sandwwraith

~~Of course! Thanks!~~ ~~Didn't even try this combination. Just assumed that the default nulls are also serialized!~~

~~But of course it works.~~

jschneider avatar Oct 29 '25 12:10 jschneider

I am wrong! It does not work in every case. I want to add "null" for all values that do not have a default value set.

Unfortunately I am not able to reopen the issue...

jschneider avatar Oct 29 '25 12:10 jschneider

I want to add "null" for all values that do not have a default value set.

Isn't this what explicitNulls do?

during decoding, the absence of a field value is treated as null for nullable properties without a default value.

https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/explicit-nulls.html

sandwwraith avatar Oct 29 '25 12:10 sandwwraith

The problem is my TypeScript client.

For TypeScript there are these possible types:

  • Required (must be defined, must not be null)
  • Optional (might be undefined - but not null)
  • Nullable (might be null - but not undefined)
  • Optional and Nullable (might be null or undefined).

Unfortunately I can not match these exact cases at the same time.

At the moment I have this mapping:

TS optional <-> Kotlin has default value TS nullable <-> Kotlin nullable TS optional + nullable <-> Kotlin nullable + has default value

jschneider avatar Oct 29 '25 12:10 jschneider

Can you show the TS example, please?

sandwwraith avatar Oct 30 '25 14:10 sandwwraith

Hi, @sandwwraith ! I encountered the same problem with:

  • kotlinx-serialization-json v1.9.0
  • kotlin v2.2.21

And I prepared a unit test for it:

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class JsonTest {
    private val GlobalJson = Json {
        explicitNulls = true
        encodeDefaults = true
    }

    @Serializable
    data class TestData(
        val name: String,
        val data: String? = null,
    )

    @Test
    fun json1() {
        assertEquals(
            "{\"name\":\"test\"}",
            GlobalJson.encodeToString(TestData(
                name = "test"
            ))
        )
    }

    @Test
    fun json2() {
        assertEquals(
            "{\"name\":\"test\"}",
            GlobalJson.encodeToString(TestData(
                name = "test",
                data = null
            ))
        )
    }
}

Then I got the following result:

Expected :{"name":"test"}
Actual   :{"name":"test","data":null}

Expected :{"name":"test"}
Actual   :{"name":"test","data":null}

sgpublic avatar Nov 14 '25 02:11 sgpublic

private val GlobalJson = Json {
    explicitNulls = true
    encodeDefaults = true
}

This configuration means that the encoder should write both default values and null values, so the actual outputs are what should be expected. Would you mean to have explicitNulls = false?

pdvrieze avatar Nov 14 '25 12:11 pdvrieze

Would you mean to have explicitNulls = false?

Oh, thanks for the clarification. I completely misread the meaning of explicitNulls = true — I wasn’t paying enough attention and assumed it excluded nulls instead of outputting them.

That one’s on me. Thanks for pointing it out, and sorry for the noise. Everything makes sense now.

sgpublic avatar Nov 14 '25 12:11 sgpublic