CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-jetbrains-kotlinx--kotlinx-serialization-json-jvm

A Kotlin multiplatform library for JSON serialization providing type-safe JSON parsing and object serialization

Pending
Overview
Eval results
Files

platform.mddocs/

Platform-Specific APIs

Platform-specific extensions providing optimized JSON processing for JVM stream operations and JavaScript dynamic object interoperability.

Capabilities

JVM Stream Operations

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 Dynamic Object Interop

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): dynamic

Usage 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}")
    }
}

Error Handling in Platform APIs

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
    }
}

Performance Considerations

JVM Streams:

  • Memory Efficiency: Stream APIs avoid loading entire JSON into memory
  • Sequence Processing: decodeToSequence enables lazy evaluation for large datasets
  • I/O Performance: Direct stream operations reduce intermediate string allocations

JavaScript Dynamic Interop:

  • Object Conversion: Dynamic object conversion has overhead; cache converted objects when possible
  • Type Safety: Runtime type checking is performed during dynamic conversions
  • Memory Usage: Large objects may require additional memory during conversion

Platform Detection

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

docs

advanced.md

annotations.md

builders.md

configuration.md

index.md

json-element.md

platform.md

serialization.md

tile.json