In this article, we will learn about the major new features that kotlinx.serialization 1.3 brings for developers to manage JSON parsing more efficiently.
Java IO stream-based JSON serialization
We can now read and write JSON directly to network streams or files.
IO stream serialization is currently available only on the JVM platform and for the JSON format.
API includes two main methods:
- Json.decodeFromStream()
- Json.encodeToStream()
@Serializable | |
data class DataModel( // properties ) | |
@OptIn(ExperimentalSerializationApi::class) | |
fun main() { | |
URL("https://api.plos.org/search?q=title:DNA").openStream().use { | |
val jsonData = Json.decodeFromStream<DataModel>(it) // read JSON from a URL | |
println(jsonData) | |
FileOutputStream(File("dataModel.json")).use { // save to a file | |
Json.encodeToStream(jsonData, it) | |
} | |
} | |
} |
Java IO stream-based JSON serialization.kt
Property-level control over default value encoding
We can force the library to encode the default values
by setting the encodeDefaults
property of a Json
instance to true
:
val format = Json { encodeDefaults = true } // false by default
- In
1.3.0 we can control it at the property level by using the experimental
@EncodeDefault annotation.
- It has a higher priority level than the
encodeDefaults
property.
It has two possible values:
The default value is ALWAYS
- ALWAYS: encodes a property’s default value.
- NEVER: doesn’t
encode the default value
regardless of theJson
configuration.
@OptIn(ExperimentalSerializationApi::class) | |
@Serializable | |
data class Expert( | |
val id: Int, | |
val name: String, | |
val category: Category, | |
val publishedArticles: Int?, | |
// @EncodeDefault default ALWAYS | |
@EncodeDefault val recentEventLink: String? = null | |
) | |
enum class Category { | |
ANDROID, KOTLIN, DART, FLUTTER | |
} |
Property-level control over default value encoding.kt
Excluding null values from serialization
explicitNulls
: Another way to reduce the size of the generatedJSON Strings
is by omittingnull
values.- It defines whether
null property values should be included in the
S
erialized JSON String. - It’s
true
by default, so allnulls
are stored as the values of their corresponding properties.
val format = Json { explicitNulls = false } | |
val flutterExpert = Expert(id = 1, "Nav", Category.FLUTTER, null) | |
val jsonFlutterExpert = format.encodeToString(flutterExpert) | |
println("EncodedResult: $jsonFlutterExpert") | |
println("DecodedResult: ${format.decodeFromString<Expert>(jsonFlutterExpert)}") | |
/* | |
EncodedResult: | |
{ | |
"id":1, | |
"name":"Nav", | |
"category":"FLUTTER" | |
} | |
DecodedResult: | |
Expert( | |
id=1, | |
name=Nav, | |
category=FLUTTER, | |
publishedArticles=null, | |
recentEventLink=null | |
) | |
*/ |
Excluding null values from serialization.kt
- Missing field exception 🥵
To deserialize objects from JSON with omitted nulls, we need to make sure that
Json instance
withexplicitNulls == false
is used.It sets all omitted nullable properties to
null
unless they havedefault values
.
- Try to decode a
JSON string
with omitted nulls withexplicitNulls == true
(default
) it will
throw a MissingFieldException
val formatExplicitExcludeNullsFalse = Json { explicitNulls = false } | |
val flutterExpert = Expert(id = 1, "Roman", Category.FLUTTER, null) | |
val jsonFlutterExpert = formatExplicitExcludeNullsFalse.encodeToString(flutterExpert) | |
println("Encoded FlutterExpert result: $jsonFlutterExpert") | |
val formatExplicitExcludeNullsTrue = Json { explicitNulls = true } | |
println("Decoded FlutterExpert: ${formatExplicitExcludeNullsTrue.decodeFromString<Expert>(jsonFlutterExpert)}") | |
/* | |
Exception in thread "main" kotlinx.serialization.MissingFieldException: | |
Field 'publishedArticles' is required for type with serial name 'Expert', but it was missing | |
at kotlinx.serialization.internal.PluginExceptionsKt.throwMissingFieldException(PluginExceptions.kt:20) | |
*/ |
Job Offers
Custom polymorphic class discriminators
- In hierarchy serialization, a useful attribute comes into play — class discriminator.
- It stores the exact class of the object that was encoded.
- By default, it has the name
type
and contains a fully qualified class name of theobject
beingserialized
.
In 1.3.0, By using @JsonClassDiscriminator’s
discriminator property we can set a custom discriminator name for each class hierarchy.
@OptIn(ExperimentalSerializationApi::class) | |
@Serializable | |
@JsonClassDiscriminator("languageType") | |
sealed class Language { | |
abstract val name: String | |
} | |
@Serializable | |
class Kotlin(override val name: String, private val version: String) : Language() { | |
fun getFormattedVersion(): String { | |
return version | |
} | |
} | |
@Serializable | |
class Java(override val name: String) : Language() | |
/* | |
Result: | |
{ | |
"languageType":"Kotlin", | |
"name":"Kotlin", | |
"version":"1.5.30" | |
} | |
*/ |