CtrlK
BlogDocsLog inGet started
Tessl Logo

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

Kotlin multiplatform JSON serialization library with type-safe, reflectionless approach supporting all platforms

Pending
Overview
Eval results
Files

platform-extensions.mddocs/

Platform Extensions

Platform-specific extensions providing additional functionality for JVM streams and JavaScript dynamic objects.

Capabilities

JVM Stream Extensions

JVM-specific extensions for working with InputStream and OutputStream for efficient streaming operations.

/**
 * Serializes the value with serializer into a stream using JSON format and UTF-8 encoding
 * @param serializer Strategy for serializing type T
 * @param value Value to serialize
 * @param stream OutputStream to write JSON to
 * @throws SerializationException if value cannot be serialized to JSON
 * @throws IOException if I/O error occurs during writing
 */
fun <T> Json.encodeToStream(
    serializer: SerializationStrategy<T>,
    value: T,
    stream: OutputStream
)

/**
 * Serializes value to stream using serializer retrieved from reified type parameter
 * @param value Value to serialize
 * @param stream OutputStream to write JSON to
 * @throws SerializationException if value cannot be serialized to JSON
 * @throws IOException if I/O error occurs during writing
 */
inline fun <reified T> Json.encodeToStream(value: T, stream: OutputStream)

/**
 * Deserializes JSON from stream using UTF-8 encoding to value of type T
 * Expects exactly one object in the stream
 * @param deserializer Strategy for deserializing type T
 * @param stream InputStream to read JSON from
 * @return Deserialized value of type T
 * @throws SerializationException if JSON input cannot be deserialized
 * @throws IllegalArgumentException if decoded input is invalid for type T
 * @throws IOException if I/O error occurs during reading
 */
fun <T> Json.decodeFromStream(
    deserializer: DeserializationStrategy<T>,
    stream: InputStream
): T

/**
 * Deserializes from stream using deserializer retrieved from reified type parameter
 * @param stream InputStream to read JSON from
 * @return Deserialized value of type T
 * @throws SerializationException if JSON input cannot be deserialized
 * @throws IllegalArgumentException if decoded input is invalid for type T
 * @throws IOException if I/O error occurs during reading
 */
inline fun <reified T> Json.decodeFromStream(stream: InputStream): T

/**
 * Transforms stream into lazily deserialized sequence of elements of type T
 * Stream can contain multiple elements separated as format declares
 * @param stream InputStream to read JSON sequence from
 * @param deserializer Strategy for deserializing type T
 * @param format How elements are separated in the stream
 * @return Sequence of deserialized elements (evaluated lazily)
 * @throws SerializationException if JSON input cannot be deserialized
 * @throws IllegalArgumentException if decoded input is invalid for type T
 * @throws IOException if I/O error occurs during reading
 */
fun <T> Json.decodeToSequence(
    stream: InputStream,
    deserializer: DeserializationStrategy<T>,
    format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
): Sequence<T>

/**
 * Reified version of decodeToSequence
 * @param stream InputStream to read JSON sequence from
 * @param format How elements are separated in the stream
 * @return Sequence of deserialized elements (evaluated lazily)
 */
inline fun <reified T> Json.decodeToSequence(
    stream: InputStream,
    format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
): Sequence<T>

Usage Examples:

@Serializable
data class LogEntry(
    val timestamp: Long,
    val level: String,
    val message: String,
    val metadata: Map<String, String> = emptyMap()
)

// Encoding to stream
val logEntry = LogEntry(
    timestamp = System.currentTimeMillis(),
    level = "INFO",
    message = "Application started",
    metadata = mapOf("version" to "1.0.0", "env" to "production")
)

// Write to file
val outputFile = File("application.log")
outputFile.outputStream().use { stream ->
    Json.encodeToStream(logEntry, stream)
}

// Write to ByteArrayOutputStream
val byteArrayStream = ByteArrayOutputStream()
Json.encodeToStream(LogEntry.serializer(), logEntry, byteArrayStream)
val jsonBytes = byteArrayStream.toByteArray()

// Decoding from stream
val inputFile = File("application.log")
val decodedEntry = inputFile.inputStream().use { stream ->
    Json.decodeFromStream<LogEntry>(stream)
}

// Decoding with explicit deserializer
val anotherEntry = inputFile.inputStream().use { stream ->
    Json.decodeFromStream(LogEntry.serializer(), stream)
}

// Stream processing with sequence
@Serializable
data class SensorReading(
    val sensorId: String,
    val value: Double,
    val timestamp: Long
)

// Multiple readings in whitespace-separated format
val readingsData = """
{"sensorId":"temp01","value":23.5,"timestamp":1634567890123}
{"sensorId":"temp01","value":24.1,"timestamp":1634567890223}
{"sensorId":"temp01","value":23.8,"timestamp":1634567890323}
{"sensorId":"temp02","value":22.3,"timestamp":1634567890423}
"""

val readingsStream = ByteArrayInputStream(readingsData.toByteArray())
val readings = Json.decodeToSequence<SensorReading>(
    readingsStream, 
    DecodeSequenceMode.WHITESPACE_SEPARATED
)

// Process readings lazily
readings.forEach { reading ->
    println("Sensor ${reading.sensorId}: ${reading.value}°C at ${reading.timestamp}")
}

// Array-wrapped sequence
val arrayWrappedData = """
[
  {"sensorId":"temp01","value":25.0,"timestamp":1634567890523},
  {"sensorId":"temp02","value":23.5,"timestamp":1634567890623},
  {"sensorId":"temp03","value":24.2,"timestamp":1634567890723}
]
"""

val arrayStream = ByteArrayInputStream(arrayWrappedData.toByteArray())
val arrayReadings = Json.decodeToSequence<SensorReading>(
    arrayStream,
    DecodeSequenceMode.ARRAY_WRAPPED
)

// Filter and transform during processing
val highTempReadings = arrayReadings
    .filter { it.value > 24.0 }
    .map { "${it.sensorId}: ${it.value}°C" }
    .toList()

// Large file processing
val largeLogFile = File("large-log.jsonl") // JSON Lines format
val errorEntries = largeLogFile.inputStream().use { stream ->
    Json.decodeToSequence<LogEntry>(stream, DecodeSequenceMode.WHITESPACE_SEPARATED)
        .filter { it.level == "ERROR" }
        .take(100) // Only process first 100 errors
        .toList()
}

JavaScript Dynamic Extensions

JavaScript-specific extensions for working with native JavaScript objects using the dynamic type.

/**
 * Converts native JavaScript objects into Kotlin ones, verifying their types
 * Equivalent to Json.decodeFromString(JSON.stringify(nativeObj))
 * @param deserializer Strategy for deserializing type T
 * @param dynamic Native JavaScript object to convert
 * @return Kotlin object of type T
 * @throws SerializationException if dynamic object cannot be converted to T
 */
fun <T> Json.decodeFromDynamic(deserializer: DeserializationStrategy<T>, dynamic: dynamic): T

/**
 * Reified version of decodeFromDynamic
 * @param dynamic Native JavaScript object to convert
 * @return Kotlin object of type T
 */
inline fun <reified T> Json.decodeFromDynamic(dynamic: dynamic): T

/**
 * Converts Kotlin data structures to plain JavaScript objects
 * @param serializer Strategy for serializing type T
 * @param value Kotlin object to convert
 * @return Native JavaScript object
 * @throws SerializationException if value cannot be converted to dynamic
 */
fun <T> Json.encodeToDynamic(serializer: SerializationStrategy<T>, value: T): dynamic

/**
 * Reified version of encodeToDynamic
 * @param value Kotlin object to convert
 * @return Native JavaScript object
 */
inline fun <reified T> Json.encodeToDynamic(value: T): dynamic

Usage Examples:

@Serializable
data class UserProfile(
    val id: String,
    val name: String,
    val email: String,
    val preferences: UserPreferences
)

@Serializable
data class UserPreferences(
    val theme: String,
    val notifications: Boolean,
    val language: String
)

// Working with JavaScript objects from APIs
val jsApiResponse: dynamic = window.fetch("/api/user/profile")
    .then { response -> response.json() }
    .await()

// Convert JavaScript object to Kotlin
val userProfile = Json.decodeFromDynamic<UserProfile>(jsApiResponse)

// With explicit deserializer
val explicitProfile = Json.decodeFromDynamic(UserProfile.serializer(), jsApiResponse)

// Convert Kotlin object to JavaScript
val preferences = UserPreferences(
    theme = "dark",
    notifications = true,
    language = "en"
)

val jsPreferences = Json.encodeToDynamic(preferences)
// jsPreferences is now a native JavaScript object

// Can be used directly with JavaScript APIs
window.localStorage.setItem("preferences", JSON.stringify(jsPreferences))

// Working with mixed JavaScript/Kotlin code
@Serializable
data class ApiRequest(
    val method: String,
    val endpoint: String,
    val data: JsonElement?
)

// Create request in Kotlin
val apiRequest = ApiRequest(
    method = "POST",
    endpoint = "/api/users",
    data = buildJsonObject {
        put("name", "Alice")
        put("email", "alice@example.com")
    }
)

// Convert to JavaScript object for fetch API
val jsRequest = Json.encodeToDynamic(apiRequest)

// Use with native fetch
val fetchOptions = js("{}")
fetchOptions.method = jsRequest.method
fetchOptions.body = JSON.stringify(jsRequest.data)
fetchOptions.headers = js("{}")
fetchOptions.headers["Content-Type"] = "application/json"

val response = window.fetch(jsRequest.endpoint, fetchOptions).await()

// Process JavaScript response
val jsResponseData: dynamic = response.json().await()

@Serializable
data class ApiResponse(
    val success: Boolean,
    val data: JsonElement?,
    val message: String?
)

val apiResponse = Json.decodeFromDynamic<ApiResponse>(jsResponseData)

// Interop with existing JavaScript libraries
// Assuming a JavaScript chart library expects configuration object
@Serializable
data class ChartConfig(
    val type: String,
    val data: ChartData,
    val options: ChartOptions
)

@Serializable
data class ChartData(
    val labels: List<String>,
    val datasets: List<Dataset>
)

@Serializable
data class Dataset(
    val label: String,
    val data: List<Double>,
    val backgroundColor: String
)

@Serializable
data class ChartOptions(
    val responsive: Boolean,
    val maintainAspectRatio: Boolean
)

val chartConfig = ChartConfig(
    type = "bar",
    data = ChartData(
        labels = listOf("Jan", "Feb", "Mar", "Apr"),
        datasets = listOf(
            Dataset(
                label = "Sales",
                data = listOf(100.0, 150.0, 200.0, 175.0),
                backgroundColor = "#36A2EB"
            )
        )
    ),
    options = ChartOptions(
        responsive = true,
        maintainAspectRatio = false
    )
)

// Convert to JavaScript object for chart library
val jsChartConfig = Json.encodeToDynamic(chartConfig)

// Use with Chart.js (example)
val canvas = document.getElementById("myChart") as HTMLCanvasElement
val chart = Chart(canvas, jsChartConfig)

// Working with complex nested JavaScript objects
val complexJsObject: dynamic = js("""
{
    user: {
        profile: {
            id: "user123",
            name: "Alice",
            email: "alice@example.com",
            preferences: {
                theme: "dark",
                notifications: true,
                language: "en"
            }
        },
        permissions: ["read", "write"],
        metadata: {
            lastLogin: 1634567890123,
            loginCount: 45
        }
    }
}
""")

// Extract specific parts
val userProfileJs = complexJsObject.user.profile
val kotlinProfile = Json.decodeFromDynamic<UserProfile>(userProfileJs)

// Handle arrays from JavaScript
val permissionsJs = complexJsObject.user.permissions
val permissions = Json.decodeFromDynamic<List<String>>(permissionsJs)

// Convert metadata map
val metadataJs = complexJsObject.user.metadata
val metadata = Json.decodeFromDynamic<Map<String, Long>>(metadataJs)

Platform-Specific Considerations

Important considerations when using platform extensions.

Usage Examples:

// JVM: Resource management
class LogProcessor {
    fun processLogFile(filePath: String): List<LogEntry> {
        return File(filePath).inputStream().use { stream ->
            // Stream is automatically closed even if exception occurs
            Json.decodeToSequence<LogEntry>(stream, DecodeSequenceMode.WHITESPACE_SEPARATED)
                .filter { it.level in setOf("ERROR", "WARN") }
                .toList()
        }
    }
    
    fun exportLogs(entries: List<LogEntry>, outputPath: String) {
        File(outputPath).outputStream().use { stream ->
            // Write each entry as separate JSON object
            entries.forEach { entry ->
                Json.encodeToStream(entry, stream)
                stream.write("\n".toByteArray()) // Add newline
            }
        }
    }
}

// JavaScript: Type safety with dynamic objects
fun processApiResponse(jsResponse: dynamic): UserProfile? {
    return try {
        // Validate structure before converting
        if (jsResponse.id == undefined || jsResponse.name == undefined) {
            return null
        }
        
        Json.decodeFromDynamic<UserProfile>(jsResponse)
    } catch (e: SerializationException) {
        console.error("Failed to parse user profile", e)
        null
    }
}

// JavaScript: Working with promises and async operations
suspend fun fetchUserData(userId: String): UserProfile? {
    val response = window.fetch("/api/users/$userId").await()
    
    if (!response.ok) {
        return null
    }
    
    val jsData: dynamic = response.json().await()
    return Json.decodeFromDynamic<UserProfile>(jsData)
}

// JavaScript: Integration with localStorage
fun saveUserPreferences(prefs: UserPreferences) {
    val jsPrefs = Json.encodeToDynamic(prefs)
    window.localStorage.setItem("userPreferences", JSON.stringify(jsPrefs))
}

fun loadUserPreferences(): UserPreferences? {
    val stored = window.localStorage.getItem("userPreferences") ?: return null
    val jsPrefs = JSON.parse(stored)
    
    return try {
        Json.decodeFromDynamic<UserPreferences>(jsPrefs)
    } catch (e: Exception) {
        null
    }
}

// Multiplatform considerations
expect class PlatformSerializer() {
    fun serializeForPlatform(data: Any): String
    fun deserializeFromPlatform(json: String): Any?
}

// JVM implementation
actual class PlatformSerializer {
    actual fun serializeForPlatform(data: Any): String {
        // Use stream-based serialization for large objects
        val stream = ByteArrayOutputStream()
        when (data) {
            is Serializable -> {
                Json.encodeToStream(data, stream)
                return stream.toString(Charsets.UTF_8)
            }
            else -> return Json.encodeToString(data.toString())
        }
    }
    
    actual fun deserializeFromPlatform(json: String): Any? {
        return try {
            val stream = ByteArrayInputStream(json.toByteArray())
            Json.decodeFromStream<JsonElement>(stream)
        } catch (e: Exception) {
            null
        }
    }
}

// JavaScript implementation  
actual class PlatformSerializer {
    actual fun serializeForPlatform(data: Any): String {
        // Use dynamic serialization for JavaScript interop
        val dynamic = Json.encodeToDynamic(data)
        return JSON.stringify(dynamic)
    }
    
    actual fun deserializeFromPlatform(json: String): Any? {
        return try {
            val jsObject = JSON.parse(json)
            Json.decodeFromDynamic<JsonElement>(jsObject)
        } catch (e: Exception) {
            null
        }
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-serialization-json

docs

annotations.md

configuration.md

core-operations.md

custom-serializers.md

dsl-builders.md

index.md

json-elements.md

naming-strategies.md

platform-extensions.md

tile.json