A Kotlin multiplatform library for JSON serialization providing type-safe JSON parsing and object serialization
—
Platform-specific extensions providing optimized JSON processing for JVM stream operations and JavaScript dynamic object interoperability.
JVM-specific extensions for working with InputStream and OutputStream, enabling efficient streaming JSON processing.
/**
* Serialize a value directly to an OutputStream
* @param serializer Serialization strategy for type T
* @param value Object to serialize
* @param stream OutputStream to write JSON to
*/
@ExperimentalSerializationApi
fun <T> Json.encodeToStream(
serializer: SerializationStrategy<T>,
value: T,
stream: OutputStream
)
/**
* Serialize a value directly to an OutputStream using reified type
* @param value Object to serialize
* @param stream OutputStream to write JSON to
*/
@ExperimentalSerializationApi
inline fun <reified T> Json.encodeToStream(value: T, stream: OutputStream)
/**
* Deserialize JSON from an InputStream
* @param deserializer Deserialization strategy for type T
* @param stream InputStream containing JSON data
* @return Deserialized object of type T
*/
@ExperimentalSerializationApi
fun <T> Json.decodeFromStream(
deserializer: DeserializationStrategy<T>,
stream: InputStream
): T
/**
* Deserialize JSON from an InputStream using reified type
* @param stream InputStream containing JSON data
* @return Deserialized object of type T
*/
@ExperimentalSerializationApi
inline fun <reified T> Json.decodeFromStream(stream: InputStream): T
/**
* Decode a sequence of JSON objects from an InputStream
* @param stream InputStream containing JSON sequence
* @param deserializer Deserialization strategy for type T
* @param format Format of the JSON sequence
* @return Sequence of deserialized objects
*/
@ExperimentalSerializationApi
fun <T> Json.decodeToSequence(
stream: InputStream,
deserializer: DeserializationStrategy<T>,
format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
): Sequence<T>
/**
* Decode a sequence of JSON objects from an InputStream using reified type
* @param stream InputStream containing JSON sequence
* @param format Format of the JSON sequence
* @return Sequence of deserialized objects
*/
@ExperimentalSerializationApi
inline fun <reified T> Json.decodeToSequence(
stream: InputStream,
format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
): Sequence<T>
/**
* Mode for decoding JSON sequences
*/
@ExperimentalSerializationApi
enum class DecodeSequenceMode {
/** Objects separated by whitespace: {"a":1} {"b":2} */
WHITESPACE_SEPARATED,
/** Objects wrapped in array: [{"a":1}, {"b":2}] */
ARRAY_WRAPPED,
/** Automatically detect the format */
AUTO_DETECT
}Usage Examples:
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import java.io.*
@Serializable
data class LogEntry(
val timestamp: Long,
val level: String,
val message: String,
val metadata: Map<String, String> = emptyMap()
)
// Writing to streams
val entries = listOf(
LogEntry(System.currentTimeMillis(), "INFO", "Application started"),
LogEntry(System.currentTimeMillis(), "DEBUG", "Configuration loaded"),
LogEntry(System.currentTimeMillis(), "ERROR", "Database connection failed")
)
// Encode single object to stream
ByteArrayOutputStream().use { output ->
Json.encodeToStream(entries, output)
val jsonBytes = output.toByteArray()
println(String(jsonBytes))
}
// Encode to file
FileOutputStream("logs.json").use { fileOutput ->
Json.encodeToStream(entries, fileOutput)
}
// Reading from streams
FileInputStream("logs.json").use { fileInput ->
val loadedEntries = Json.decodeFromStream<List<LogEntry>>(fileInput)
loadedEntries.forEach { println(it) }
}
// Process large JSON array as sequence to avoid loading all into memory
FileInputStream("large-logs.json").use { input ->
val sequence = Json.decodeToSequence<LogEntry>(input, DecodeSequenceMode.ARRAY_WRAPPED)
sequence
.filter { it.level == "ERROR" }
.take(100)
.forEach { println("Error: ${it.message}") }
}Streaming JSON Sequences:
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import java.io.*
@Serializable
data class SensorReading(
val sensorId: String,
val timestamp: Long,
val temperature: Double,
val humidity: Double
)
// Reading whitespace-separated JSON objects
val whitespaceSeparatedJson = """
{"sensorId":"temp001","timestamp":1234567890,"temperature":22.5,"humidity":45.0}
{"sensorId":"temp002","timestamp":1234567891,"temperature":23.1,"humidity":46.2}
{"sensorId":"temp001","timestamp":1234567892,"temperature":22.8,"humidity":44.8}
"""
ByteArrayInputStream(whitespaceSeparatedJson.toByteArray()).use { input ->
val readings = Json.decodeToSequence<SensorReading>(
input,
DecodeSequenceMode.WHITESPACE_SEPARATED
)
readings.forEach { reading ->
println("Sensor ${reading.sensorId}: ${reading.temperature}°C, ${reading.humidity}% humidity")
}
}
// Reading array-wrapped format
val arrayWrappedJson = """
[
{"sensorId":"temp001","timestamp":1234567890,"temperature":22.5,"humidity":45.0},
{"sensorId":"temp002","timestamp":1234567891,"temperature":23.1,"humidity":46.2}
]
"""
ByteArrayInputStream(arrayWrappedJson.toByteArray()).use { input ->
val readings = Json.decodeToSequence<SensorReading>(
input,
DecodeSequenceMode.ARRAY_WRAPPED
)
val avgTemp = readings.map { it.temperature }.average()
println("Average temperature: $avgTemp°C")
}
// Auto-detect format
ByteArrayInputStream(whitespaceSeparatedJson.toByteArray()).use { input ->
val readings = Json.decodeToSequence<SensorReading>(
input,
DecodeSequenceMode.AUTO_DETECT // Will detect whitespace-separated format
)
readings.forEach { println(it) }
}JavaScript-specific extensions for seamless interoperability with JavaScript's dynamic object system.
/**
* Deserialize from JavaScript dynamic object
* @param deserializer Deserialization strategy for type T
* @param dynamic JavaScript dynamic object
* @return Deserialized Kotlin object of type T
*/
@ExperimentalSerializationApi
fun <T> Json.decodeFromDynamic(
deserializer: DeserializationStrategy<T>,
dynamic: dynamic
): T
/**
* Deserialize from JavaScript dynamic object using reified type
* @param dynamic JavaScript dynamic object
* @return Deserialized Kotlin object of type T
*/
@ExperimentalSerializationApi
inline fun <reified T> Json.decodeFromDynamic(dynamic: dynamic): T
/**
* Serialize to JavaScript dynamic object
* @param serializer Serialization strategy for type T
* @param value Kotlin object to serialize
* @return JavaScript dynamic object
*/
@ExperimentalSerializationApi
fun <T> Json.encodeToDynamic(
serializer: SerializationStrategy<T>,
value: T
): dynamic
/**
* Serialize to JavaScript dynamic object using reified type
* @param value Kotlin object to serialize
* @return JavaScript dynamic object
*/
@ExperimentalSerializationApi
inline fun <reified T> Json.encodeToDynamic(value: T): dynamicUsage Examples:
// Kotlin/JS specific code
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class UserProfile(
val id: Int,
val name: String,
val email: String,
val preferences: Map<String, String>
)
// Working with JavaScript objects in Kotlin/JS
external interface JsUserProfile {
val id: Int
val name: String
val email: String
val preferences: dynamic
}
// Convert from JavaScript object to Kotlin data class
fun processJavaScriptUser(jsUser: JsUserProfile): UserProfile {
// Convert JS object to dynamic, then to Kotlin object
return Json.decodeFromDynamic<UserProfile>(jsUser.asDynamic())
}
// Convert Kotlin object to JavaScript object
fun createJavaScriptUser(user: UserProfile): dynamic {
return Json.encodeToDynamic(user)
}
// Usage with browser APIs
fun saveUserToLocalStorage(user: UserProfile) {
val jsObject = Json.encodeToDynamic(user)
kotlinx.browser.localStorage.setItem("user", JSON.stringify(jsObject))
}
fun loadUserFromLocalStorage(): UserProfile? {
val userJson = kotlinx.browser.localStorage.getItem("user") ?: return null
val jsObject = JSON.parse<dynamic>(userJson)
return Json.decodeFromDynamic<UserProfile>(jsObject)
}Advanced JavaScript Interop:
// Kotlin/JS specific code
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class ApiResponse<T>(
val success: Boolean,
val data: T?,
val error: String?,
val metadata: Map<String, String> = emptyMap()
)
@Serializable
data class Product(
val id: Int,
val name: String,
val price: Double,
val categories: List<String>
)
// Interop with JavaScript fetch API
suspend fun fetchProducts(): List<Product> {
val response = kotlinx.browser.window.fetch("/api/products").await()
val jsObject = response.json().await()
val apiResponse = Json.decodeFromDynamic<ApiResponse<List<Product>>>(jsObject)
return if (apiResponse.success) {
apiResponse.data ?: emptyList()
} else {
throw Exception(apiResponse.error ?: "Unknown error")
}
}
// Send data to JavaScript API
suspend fun createProduct(product: Product): Product {
val jsProduct = Json.encodeToDynamic(product)
val response = kotlinx.browser.window.fetch("/api/products", object {
val method = "POST"
val headers = js("{}")
val body = JSON.stringify(jsProduct)
}.asDynamic()).await()
val jsResult = response.json().await()
val apiResponse = Json.decodeFromDynamic<ApiResponse<Product>>(jsResult)
return if (apiResponse.success) {
apiResponse.data ?: throw Exception("No product data returned")
} else {
throw Exception(apiResponse.error ?: "Failed to create product")
}
}
// Working with complex nested JavaScript objects
external interface ComplexJsObject {
val users: Array<dynamic>
val settings: dynamic
val metadata: dynamic
}
fun processComplexObject(jsObj: ComplexJsObject) {
// Convert JavaScript arrays and objects to Kotlin types
val users = jsObj.users.map { userJs ->
Json.decodeFromDynamic<UserProfile>(userJs)
}
val settings = Json.decodeFromDynamic<Map<String, String>>(jsObj.settings)
val metadata = Json.decodeFromDynamic<Map<String, Any>>(jsObj.metadata)
// Process the converted data
users.forEach { user ->
println("Processing user: ${user.name}")
}
}Platform-specific APIs can throw additional exceptions related to I/O operations.
import kotlinx.serialization.json.*
import java.io.*
fun safeStreamProcessing(inputFile: String, outputFile: String) {
try {
FileInputStream(inputFile).use { input ->
FileOutputStream(outputFile).use { output ->
val data = Json.decodeFromStream<List<String>>(input)
val processed = data.map { it.uppercase() }
Json.encodeToStream(processed, output)
}
}
} catch (e: IOException) {
println("File I/O error: ${e.message}")
} catch (e: SerializationException) {
println("JSON serialization error: ${e.message}")
} catch (e: Exception) {
println("Unexpected error: ${e.message}")
}
}
// JavaScript error handling
fun safeDynamicProcessing(jsObject: dynamic): UserProfile? {
return try {
Json.decodeFromDynamic<UserProfile>(jsObject)
} catch (e: SerializationException) {
console.log("Failed to decode JavaScript object: ${e.message}")
null
} catch (e: Exception) {
console.log("Unexpected error: ${e.message}")
null
}
}JVM Streams:
decodeToSequence enables lazy evaluation for large datasetsJavaScript Dynamic Interop:
Code using platform-specific APIs should handle multiplatform scenarios appropriately:
// Multiplatform code structure
expect fun platformSpecificJsonProcessing(data: String): String
// JVM implementation
actual fun platformSpecificJsonProcessing(data: String): String {
return ByteArrayInputStream(data.toByteArray()).use { input ->
val objects = Json.decodeToSequence<Map<String, Any>>(input)
val processed = objects.map { /* process */ }.toList()
Json.encodeToString(processed)
}
}
// JS implementation
actual fun platformSpecificJsonProcessing(data: String): String {
val jsObject = JSON.parse<dynamic>(data)
val kotlinObject = Json.decodeFromDynamic<Map<String, Any>>(jsObject)
val processed = /* process kotlinObject */
return Json.encodeToString(processed)
}Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-serialization-json-jvm