0
# Platform Extensions
1
2
Platform-specific extensions providing additional functionality for JVM streams and JavaScript dynamic objects.
3
4
## Capabilities
5
6
### JVM Stream Extensions
7
8
JVM-specific extensions for working with InputStream and OutputStream for efficient streaming operations.
9
10
```kotlin { .api }
11
/**
12
* Serializes the value with serializer into a stream using JSON format and UTF-8 encoding
13
* @param serializer Strategy for serializing type T
14
* @param value Value to serialize
15
* @param stream OutputStream to write JSON to
16
* @throws SerializationException if value cannot be serialized to JSON
17
* @throws IOException if I/O error occurs during writing
18
*/
19
fun <T> Json.encodeToStream(
20
serializer: SerializationStrategy<T>,
21
value: T,
22
stream: OutputStream
23
)
24
25
/**
26
* Serializes value to stream using serializer retrieved from reified type parameter
27
* @param value Value to serialize
28
* @param stream OutputStream to write JSON to
29
* @throws SerializationException if value cannot be serialized to JSON
30
* @throws IOException if I/O error occurs during writing
31
*/
32
inline fun <reified T> Json.encodeToStream(value: T, stream: OutputStream)
33
34
/**
35
* Deserializes JSON from stream using UTF-8 encoding to value of type T
36
* Expects exactly one object in the stream
37
* @param deserializer Strategy for deserializing type T
38
* @param stream InputStream to read JSON from
39
* @return Deserialized value of type T
40
* @throws SerializationException if JSON input cannot be deserialized
41
* @throws IllegalArgumentException if decoded input is invalid for type T
42
* @throws IOException if I/O error occurs during reading
43
*/
44
fun <T> Json.decodeFromStream(
45
deserializer: DeserializationStrategy<T>,
46
stream: InputStream
47
): T
48
49
/**
50
* Deserializes from stream using deserializer retrieved from reified type parameter
51
* @param stream InputStream to read JSON from
52
* @return Deserialized value of type T
53
* @throws SerializationException if JSON input cannot be deserialized
54
* @throws IllegalArgumentException if decoded input is invalid for type T
55
* @throws IOException if I/O error occurs during reading
56
*/
57
inline fun <reified T> Json.decodeFromStream(stream: InputStream): T
58
59
/**
60
* Transforms stream into lazily deserialized sequence of elements of type T
61
* Stream can contain multiple elements separated as format declares
62
* @param stream InputStream to read JSON sequence from
63
* @param deserializer Strategy for deserializing type T
64
* @param format How elements are separated in the stream
65
* @return Sequence of deserialized elements (evaluated lazily)
66
* @throws SerializationException if JSON input cannot be deserialized
67
* @throws IllegalArgumentException if decoded input is invalid for type T
68
* @throws IOException if I/O error occurs during reading
69
*/
70
fun <T> Json.decodeToSequence(
71
stream: InputStream,
72
deserializer: DeserializationStrategy<T>,
73
format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
74
): Sequence<T>
75
76
/**
77
* Reified version of decodeToSequence
78
* @param stream InputStream to read JSON sequence from
79
* @param format How elements are separated in the stream
80
* @return Sequence of deserialized elements (evaluated lazily)
81
*/
82
inline fun <reified T> Json.decodeToSequence(
83
stream: InputStream,
84
format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
85
): Sequence<T>
86
```
87
88
**Usage Examples:**
89
90
```kotlin
91
@Serializable
92
data class LogEntry(
93
val timestamp: Long,
94
val level: String,
95
val message: String,
96
val metadata: Map<String, String> = emptyMap()
97
)
98
99
// Encoding to stream
100
val logEntry = LogEntry(
101
timestamp = System.currentTimeMillis(),
102
level = "INFO",
103
message = "Application started",
104
metadata = mapOf("version" to "1.0.0", "env" to "production")
105
)
106
107
// Write to file
108
val outputFile = File("application.log")
109
outputFile.outputStream().use { stream ->
110
Json.encodeToStream(logEntry, stream)
111
}
112
113
// Write to ByteArrayOutputStream
114
val byteArrayStream = ByteArrayOutputStream()
115
Json.encodeToStream(LogEntry.serializer(), logEntry, byteArrayStream)
116
val jsonBytes = byteArrayStream.toByteArray()
117
118
// Decoding from stream
119
val inputFile = File("application.log")
120
val decodedEntry = inputFile.inputStream().use { stream ->
121
Json.decodeFromStream<LogEntry>(stream)
122
}
123
124
// Decoding with explicit deserializer
125
val anotherEntry = inputFile.inputStream().use { stream ->
126
Json.decodeFromStream(LogEntry.serializer(), stream)
127
}
128
129
// Stream processing with sequence
130
@Serializable
131
data class SensorReading(
132
val sensorId: String,
133
val value: Double,
134
val timestamp: Long
135
)
136
137
// Multiple readings in whitespace-separated format
138
val readingsData = """
139
{"sensorId":"temp01","value":23.5,"timestamp":1634567890123}
140
{"sensorId":"temp01","value":24.1,"timestamp":1634567890223}
141
{"sensorId":"temp01","value":23.8,"timestamp":1634567890323}
142
{"sensorId":"temp02","value":22.3,"timestamp":1634567890423}
143
"""
144
145
val readingsStream = ByteArrayInputStream(readingsData.toByteArray())
146
val readings = Json.decodeToSequence<SensorReading>(
147
readingsStream,
148
DecodeSequenceMode.WHITESPACE_SEPARATED
149
)
150
151
// Process readings lazily
152
readings.forEach { reading ->
153
println("Sensor ${reading.sensorId}: ${reading.value}°C at ${reading.timestamp}")
154
}
155
156
// Array-wrapped sequence
157
val arrayWrappedData = """
158
[
159
{"sensorId":"temp01","value":25.0,"timestamp":1634567890523},
160
{"sensorId":"temp02","value":23.5,"timestamp":1634567890623},
161
{"sensorId":"temp03","value":24.2,"timestamp":1634567890723}
162
]
163
"""
164
165
val arrayStream = ByteArrayInputStream(arrayWrappedData.toByteArray())
166
val arrayReadings = Json.decodeToSequence<SensorReading>(
167
arrayStream,
168
DecodeSequenceMode.ARRAY_WRAPPED
169
)
170
171
// Filter and transform during processing
172
val highTempReadings = arrayReadings
173
.filter { it.value > 24.0 }
174
.map { "${it.sensorId}: ${it.value}°C" }
175
.toList()
176
177
// Large file processing
178
val largeLogFile = File("large-log.jsonl") // JSON Lines format
179
val errorEntries = largeLogFile.inputStream().use { stream ->
180
Json.decodeToSequence<LogEntry>(stream, DecodeSequenceMode.WHITESPACE_SEPARATED)
181
.filter { it.level == "ERROR" }
182
.take(100) // Only process first 100 errors
183
.toList()
184
}
185
```
186
187
### JavaScript Dynamic Extensions
188
189
JavaScript-specific extensions for working with native JavaScript objects using the `dynamic` type.
190
191
```kotlin { .api }
192
/**
193
* Converts native JavaScript objects into Kotlin ones, verifying their types
194
* Equivalent to Json.decodeFromString(JSON.stringify(nativeObj))
195
* @param deserializer Strategy for deserializing type T
196
* @param dynamic Native JavaScript object to convert
197
* @return Kotlin object of type T
198
* @throws SerializationException if dynamic object cannot be converted to T
199
*/
200
fun <T> Json.decodeFromDynamic(deserializer: DeserializationStrategy<T>, dynamic: dynamic): T
201
202
/**
203
* Reified version of decodeFromDynamic
204
* @param dynamic Native JavaScript object to convert
205
* @return Kotlin object of type T
206
*/
207
inline fun <reified T> Json.decodeFromDynamic(dynamic: dynamic): T
208
209
/**
210
* Converts Kotlin data structures to plain JavaScript objects
211
* @param serializer Strategy for serializing type T
212
* @param value Kotlin object to convert
213
* @return Native JavaScript object
214
* @throws SerializationException if value cannot be converted to dynamic
215
*/
216
fun <T> Json.encodeToDynamic(serializer: SerializationStrategy<T>, value: T): dynamic
217
218
/**
219
* Reified version of encodeToDynamic
220
* @param value Kotlin object to convert
221
* @return Native JavaScript object
222
*/
223
inline fun <reified T> Json.encodeToDynamic(value: T): dynamic
224
```
225
226
**Usage Examples:**
227
228
```kotlin
229
@Serializable
230
data class UserProfile(
231
val id: String,
232
val name: String,
233
val email: String,
234
val preferences: UserPreferences
235
)
236
237
@Serializable
238
data class UserPreferences(
239
val theme: String,
240
val notifications: Boolean,
241
val language: String
242
)
243
244
// Working with JavaScript objects from APIs
245
val jsApiResponse: dynamic = window.fetch("/api/user/profile")
246
.then { response -> response.json() }
247
.await()
248
249
// Convert JavaScript object to Kotlin
250
val userProfile = Json.decodeFromDynamic<UserProfile>(jsApiResponse)
251
252
// With explicit deserializer
253
val explicitProfile = Json.decodeFromDynamic(UserProfile.serializer(), jsApiResponse)
254
255
// Convert Kotlin object to JavaScript
256
val preferences = UserPreferences(
257
theme = "dark",
258
notifications = true,
259
language = "en"
260
)
261
262
val jsPreferences = Json.encodeToDynamic(preferences)
263
// jsPreferences is now a native JavaScript object
264
265
// Can be used directly with JavaScript APIs
266
window.localStorage.setItem("preferences", JSON.stringify(jsPreferences))
267
268
// Working with mixed JavaScript/Kotlin code
269
@Serializable
270
data class ApiRequest(
271
val method: String,
272
val endpoint: String,
273
val data: JsonElement?
274
)
275
276
// Create request in Kotlin
277
val apiRequest = ApiRequest(
278
method = "POST",
279
endpoint = "/api/users",
280
data = buildJsonObject {
281
put("name", "Alice")
282
put("email", "alice@example.com")
283
}
284
)
285
286
// Convert to JavaScript object for fetch API
287
val jsRequest = Json.encodeToDynamic(apiRequest)
288
289
// Use with native fetch
290
val fetchOptions = js("{}")
291
fetchOptions.method = jsRequest.method
292
fetchOptions.body = JSON.stringify(jsRequest.data)
293
fetchOptions.headers = js("{}")
294
fetchOptions.headers["Content-Type"] = "application/json"
295
296
val response = window.fetch(jsRequest.endpoint, fetchOptions).await()
297
298
// Process JavaScript response
299
val jsResponseData: dynamic = response.json().await()
300
301
@Serializable
302
data class ApiResponse(
303
val success: Boolean,
304
val data: JsonElement?,
305
val message: String?
306
)
307
308
val apiResponse = Json.decodeFromDynamic<ApiResponse>(jsResponseData)
309
310
// Interop with existing JavaScript libraries
311
// Assuming a JavaScript chart library expects configuration object
312
@Serializable
313
data class ChartConfig(
314
val type: String,
315
val data: ChartData,
316
val options: ChartOptions
317
)
318
319
@Serializable
320
data class ChartData(
321
val labels: List<String>,
322
val datasets: List<Dataset>
323
)
324
325
@Serializable
326
data class Dataset(
327
val label: String,
328
val data: List<Double>,
329
val backgroundColor: String
330
)
331
332
@Serializable
333
data class ChartOptions(
334
val responsive: Boolean,
335
val maintainAspectRatio: Boolean
336
)
337
338
val chartConfig = ChartConfig(
339
type = "bar",
340
data = ChartData(
341
labels = listOf("Jan", "Feb", "Mar", "Apr"),
342
datasets = listOf(
343
Dataset(
344
label = "Sales",
345
data = listOf(100.0, 150.0, 200.0, 175.0),
346
backgroundColor = "#36A2EB"
347
)
348
)
349
),
350
options = ChartOptions(
351
responsive = true,
352
maintainAspectRatio = false
353
)
354
)
355
356
// Convert to JavaScript object for chart library
357
val jsChartConfig = Json.encodeToDynamic(chartConfig)
358
359
// Use with Chart.js (example)
360
val canvas = document.getElementById("myChart") as HTMLCanvasElement
361
val chart = Chart(canvas, jsChartConfig)
362
363
// Working with complex nested JavaScript objects
364
val complexJsObject: dynamic = js("""
365
{
366
user: {
367
profile: {
368
id: "user123",
369
name: "Alice",
370
email: "alice@example.com",
371
preferences: {
372
theme: "dark",
373
notifications: true,
374
language: "en"
375
}
376
},
377
permissions: ["read", "write"],
378
metadata: {
379
lastLogin: 1634567890123,
380
loginCount: 45
381
}
382
}
383
}
384
""")
385
386
// Extract specific parts
387
val userProfileJs = complexJsObject.user.profile
388
val kotlinProfile = Json.decodeFromDynamic<UserProfile>(userProfileJs)
389
390
// Handle arrays from JavaScript
391
val permissionsJs = complexJsObject.user.permissions
392
val permissions = Json.decodeFromDynamic<List<String>>(permissionsJs)
393
394
// Convert metadata map
395
val metadataJs = complexJsObject.user.metadata
396
val metadata = Json.decodeFromDynamic<Map<String, Long>>(metadataJs)
397
```
398
399
### Platform-Specific Considerations
400
401
Important considerations when using platform extensions.
402
403
**Usage Examples:**
404
405
```kotlin
406
// JVM: Resource management
407
class LogProcessor {
408
fun processLogFile(filePath: String): List<LogEntry> {
409
return File(filePath).inputStream().use { stream ->
410
// Stream is automatically closed even if exception occurs
411
Json.decodeToSequence<LogEntry>(stream, DecodeSequenceMode.WHITESPACE_SEPARATED)
412
.filter { it.level in setOf("ERROR", "WARN") }
413
.toList()
414
}
415
}
416
417
fun exportLogs(entries: List<LogEntry>, outputPath: String) {
418
File(outputPath).outputStream().use { stream ->
419
// Write each entry as separate JSON object
420
entries.forEach { entry ->
421
Json.encodeToStream(entry, stream)
422
stream.write("\n".toByteArray()) // Add newline
423
}
424
}
425
}
426
}
427
428
// JavaScript: Type safety with dynamic objects
429
fun processApiResponse(jsResponse: dynamic): UserProfile? {
430
return try {
431
// Validate structure before converting
432
if (jsResponse.id == undefined || jsResponse.name == undefined) {
433
return null
434
}
435
436
Json.decodeFromDynamic<UserProfile>(jsResponse)
437
} catch (e: SerializationException) {
438
console.error("Failed to parse user profile", e)
439
null
440
}
441
}
442
443
// JavaScript: Working with promises and async operations
444
suspend fun fetchUserData(userId: String): UserProfile? {
445
val response = window.fetch("/api/users/$userId").await()
446
447
if (!response.ok) {
448
return null
449
}
450
451
val jsData: dynamic = response.json().await()
452
return Json.decodeFromDynamic<UserProfile>(jsData)
453
}
454
455
// JavaScript: Integration with localStorage
456
fun saveUserPreferences(prefs: UserPreferences) {
457
val jsPrefs = Json.encodeToDynamic(prefs)
458
window.localStorage.setItem("userPreferences", JSON.stringify(jsPrefs))
459
}
460
461
fun loadUserPreferences(): UserPreferences? {
462
val stored = window.localStorage.getItem("userPreferences") ?: return null
463
val jsPrefs = JSON.parse(stored)
464
465
return try {
466
Json.decodeFromDynamic<UserPreferences>(jsPrefs)
467
} catch (e: Exception) {
468
null
469
}
470
}
471
472
// Multiplatform considerations
473
expect class PlatformSerializer() {
474
fun serializeForPlatform(data: Any): String
475
fun deserializeFromPlatform(json: String): Any?
476
}
477
478
// JVM implementation
479
actual class PlatformSerializer {
480
actual fun serializeForPlatform(data: Any): String {
481
// Use stream-based serialization for large objects
482
val stream = ByteArrayOutputStream()
483
when (data) {
484
is Serializable -> {
485
Json.encodeToStream(data, stream)
486
return stream.toString(Charsets.UTF_8)
487
}
488
else -> return Json.encodeToString(data.toString())
489
}
490
}
491
492
actual fun deserializeFromPlatform(json: String): Any? {
493
return try {
494
val stream = ByteArrayInputStream(json.toByteArray())
495
Json.decodeFromStream<JsonElement>(stream)
496
} catch (e: Exception) {
497
null
498
}
499
}
500
}
501
502
// JavaScript implementation
503
actual class PlatformSerializer {
504
actual fun serializeForPlatform(data: Any): String {
505
// Use dynamic serialization for JavaScript interop
506
val dynamic = Json.encodeToDynamic(data)
507
return JSON.stringify(dynamic)
508
}
509
510
actual fun deserializeFromPlatform(json: String): Any? {
511
return try {
512
val jsObject = JSON.parse(json)
513
Json.decodeFromDynamic<JsonElement>(jsObject)
514
} catch (e: Exception) {
515
null
516
}
517
}
518
}
519
```