or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced.mdannotations.mdbuilders.mdconfiguration.mdindex.mdjson-element.mdplatform.mdserialization.md

advanced.mddocs/

0

# Advanced Serialization

1

2

Advanced serialization interfaces, custom serializers, and low-level JSON processing APIs for specialized use cases requiring fine-grained control over serialization behavior.

3

4

## Capabilities

5

6

### JsonEncoder Interface

7

8

Low-level encoder interface for custom JSON serialization with direct JsonElement access.

9

10

```kotlin { .api }

11

/**

12

* Encoder interface for JSON serialization with JsonElement support

13

* Provides access to the Json instance and direct JsonElement encoding

14

*/

15

@SubclassOptInRequired(SealedSerializationApi::class)

16

interface JsonEncoder : Encoder, CompositeEncoder {

17

/**

18

* The Json instance used for encoding

19

*/

20

val json: Json

21

22

/**

23

* Encode a JsonElement directly to the output

24

* @param element JsonElement to encode

25

*/

26

fun encodeJsonElement(element: JsonElement)

27

}

28

```

29

30

### JsonDecoder Interface

31

32

Low-level decoder interface for custom JSON deserialization with direct JsonElement access.

33

34

```kotlin { .api }

35

/**

36

* Decoder interface for JSON deserialization with JsonElement support

37

* Provides access to the Json instance and direct JsonElement decoding

38

*/

39

@SubclassOptInRequired(SealedSerializationApi::class)

40

interface JsonDecoder : Decoder, CompositeDecoder {

41

/**

42

* The Json instance used for decoding

43

*/

44

val json: Json

45

46

/**

47

* Decode the current JSON input as a JsonElement

48

* @return JsonElement representing the current JSON value

49

*/

50

fun decodeJsonElement(): JsonElement

51

}

52

```

53

54

**Usage Examples:**

55

56

```kotlin

57

import kotlinx.serialization.*

58

import kotlinx.serialization.json.*

59

import kotlinx.serialization.encoding.*

60

import kotlinx.serialization.descriptors.*

61

62

// Custom serializer using JsonEncoder/JsonDecoder

63

object TimestampSerializer : KSerializer<Long> {

64

override val descriptor = PrimitiveSerialDescriptor("Timestamp", PrimitiveKind.LONG)

65

66

override fun serialize(encoder: Encoder, value: Long) {

67

if (encoder is JsonEncoder) {

68

// Custom JSON encoding - store as ISO string

69

val isoString = java.time.Instant.ofEpochMilli(value).toString()

70

encoder.encodeJsonElement(JsonPrimitive(isoString))

71

} else {

72

encoder.encodeLong(value)

73

}

74

}

75

76

override fun deserialize(decoder: Decoder): Long {

77

return if (decoder is JsonDecoder) {

78

// Custom JSON decoding - parse ISO string

79

val element = decoder.decodeJsonElement()

80

if (element is JsonPrimitive && element.isString) {

81

java.time.Instant.parse(element.content).toEpochMilli()

82

} else {

83

element.jsonPrimitive.long

84

}

85

} else {

86

decoder.decodeLong()

87

}

88

}

89

}

90

91

@Serializable

92

data class LogEntry(

93

val message: String,

94

@Serializable(TimestampSerializer::class)

95

val timestamp: Long

96

)

97

98

val entry = LogEntry("Application started", System.currentTimeMillis())

99

val json = Json.encodeToString(entry)

100

val decoded = Json.decodeFromString<LogEntry>(json)

101

```

102

103

### JsonContentPolymorphicSerializer

104

105

Abstract base class for content-based polymorphic serialization where type selection is based on JSON content rather than discriminator fields.

106

107

```kotlin { .api }

108

/**

109

* Abstract serializer for polymorphic serialization based on JSON content

110

* Type selection is performed by examining the JsonElement structure

111

*/

112

abstract class JsonContentPolymorphicSerializer<T : Any>(

113

baseClass: KClass<T>

114

) : KSerializer<T> {

115

/**

116

* Select the appropriate deserializer based on JSON content

117

* @param element JsonElement representing the JSON to deserialize

118

* @return DeserializationStrategy for the appropriate concrete type

119

*/

120

abstract fun selectDeserializer(element: JsonElement): DeserializationStrategy<T>

121

}

122

```

123

124

**Usage Examples:**

125

126

```kotlin

127

import kotlinx.serialization.*

128

import kotlinx.serialization.json.*

129

130

@Serializable

131

sealed class ApiMessage

132

133

@Serializable

134

data class TextMessage(val text: String) : ApiMessage()

135

136

@Serializable

137

data class ImageMessage(val url: String, val width: Int, val height: Int) : ApiMessage()

138

139

@Serializable

140

data class LocationMessage(val latitude: Double, val longitude: Double, val address: String?) : ApiMessage()

141

142

// Content-based polymorphic serializer

143

object ApiMessageSerializer : JsonContentPolymorphicSerializer<ApiMessage>(ApiMessage::class) {

144

override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ApiMessage> {

145

val jsonObject = element.jsonObject

146

147

return when {

148

"text" in jsonObject -> TextMessage.serializer()

149

"url" in jsonObject && "width" in jsonObject -> ImageMessage.serializer()

150

"latitude" in jsonObject && "longitude" in jsonObject -> LocationMessage.serializer()

151

else -> throw SerializationException("Unknown message type")

152

}

153

}

154

}

155

156

// Register the serializer

157

val json = Json {

158

serializersModule = SerializersModule {

159

polymorphic(ApiMessage::class) {

160

subclass(TextMessage::class)

161

subclass(ImageMessage::class)

162

subclass(LocationMessage::class)

163

}

164

}

165

}

166

167

// Usage - no discriminator field needed

168

val messages = listOf<ApiMessage>(

169

TextMessage("Hello world"),

170

ImageMessage("https://example.com/image.jpg", 800, 600),

171

LocationMessage(40.7128, -74.0060, "New York, NY")

172

)

173

174

val encoded = json.encodeToString(messages)

175

// Result: [

176

// {"text":"Hello world"},

177

// {"url":"https://example.com/image.jpg","width":800,"height":600},

178

// {"latitude":40.7128,"longitude":-74.0060,"address":"New York, NY"}

179

// ]

180

181

val decoded = json.decodeFromString<List<ApiMessage>>(encoded)

182

```

183

184

### JsonTransformingSerializer

185

186

Abstract base class for transforming JsonElement during serialization and deserialization, allowing modification of JSON structure without changing the data class.

187

188

```kotlin { .api }

189

/**

190

* Abstract serializer for transforming JsonElement during serialization/deserialization

191

* Allows modification of JSON structure without changing the underlying data class

192

*/

193

abstract class JsonTransformingSerializer<T>(

194

private val tSerializer: KSerializer<T>

195

) : KSerializer<T> {

196

override val descriptor: SerialDescriptor = tSerializer.descriptor

197

198

/**

199

* Transform JsonElement after deserialization before converting to object

200

* @param element JsonElement to transform

201

* @return Transformed JsonElement

202

*/

203

open fun transformDeserialize(element: JsonElement): JsonElement = element

204

205

/**

206

* Transform JsonElement after serialization from object

207

* @param element JsonElement to transform

208

* @return Transformed JsonElement

209

*/

210

open fun transformSerialize(element: JsonElement): JsonElement = element

211

}

212

```

213

214

**Usage Examples:**

215

216

```kotlin

217

import kotlinx.serialization.*

218

import kotlinx.serialization.json.*

219

220

@Serializable

221

data class UserData(

222

val id: Int,

223

val name: String,

224

val email: String

225

)

226

227

// Transformer that adds/removes metadata fields

228

object UserDataTransformer : JsonTransformingSerializer<UserData>(UserData.serializer()) {

229

230

override fun transformSerialize(element: JsonElement): JsonElement {

231

val jsonObject = element.jsonObject

232

return buildJsonObject {

233

// Copy original fields

234

jsonObject.forEach { (key, value) ->

235

put(key, value)

236

}

237

// Add metadata

238

put("version", "1.0")

239

put("serializedAt", System.currentTimeMillis())

240

}

241

}

242

243

override fun transformDeserialize(element: JsonElement): JsonElement {

244

val jsonObject = element.jsonObject

245

return buildJsonObject {

246

// Copy only the fields we want, ignoring metadata

247

put("id", jsonObject["id"]!!)

248

put("name", jsonObject["name"]!!)

249

put("email", jsonObject["email"]!!)

250

// Ignore "version", "serializedAt", etc.

251

}

252

}

253

}

254

255

@Serializable(UserDataTransformer::class)

256

data class UserWithTransformer(

257

val id: Int,

258

val name: String,

259

val email: String

260

)

261

262

val user = UserWithTransformer(1, "Alice", "alice@example.com")

263

val json = Json.encodeToString(user)

264

// Result: {"id":1,"name":"Alice","email":"alice@example.com","version":"1.0","serializedAt":1234567890}

265

266

val decoded = Json.decodeFromString<UserWithTransformer>(json)

267

// Metadata fields are stripped during deserialization

268

```

269

270

### Advanced Transformer Examples

271

272

**Field Renaming Transformer:**

273

274

```kotlin

275

import kotlinx.serialization.*

276

import kotlinx.serialization.json.*

277

278

@Serializable

279

data class InternalUser(val userId: Int, val userName: String, val userEmail: String)

280

281

// Transform between internal field names and API field names

282

object ApiFieldTransformer : JsonTransformingSerializer<InternalUser>(InternalUser.serializer()) {

283

284

private val fieldMapping = mapOf(

285

"userId" to "id",

286

"userName" to "name",

287

"userEmail" to "email"

288

)

289

290

private val reverseMapping = fieldMapping.entries.associate { (k, v) -> v to k }

291

292

override fun transformSerialize(element: JsonElement): JsonElement {

293

val jsonObject = element.jsonObject

294

return buildJsonObject {

295

jsonObject.forEach { (key, value) ->

296

val apiKey = fieldMapping[key] ?: key

297

put(apiKey, value)

298

}

299

}

300

}

301

302

override fun transformDeserialize(element: JsonElement): JsonElement {

303

val jsonObject = element.jsonObject

304

return buildJsonObject {

305

jsonObject.forEach { (key, value) ->

306

val internalKey = reverseMapping[key] ?: key

307

put(internalKey, value)

308

}

309

}

310

}

311

}

312

```

313

314

**Nested Structure Transformer:**

315

316

```kotlin

317

import kotlinx.serialization.*

318

import kotlinx.serialization.json.*

319

320

@Serializable

321

data class FlatData(val name: String, val street: String, val city: String, val zip: String)

322

323

// Transform between flat structure and nested structure

324

object NestedTransformer : JsonTransformingSerializer<FlatData>(FlatData.serializer()) {

325

326

override fun transformSerialize(element: JsonElement): JsonElement {

327

val obj = element.jsonObject

328

return buildJsonObject {

329

put("name", obj["name"]!!)

330

putJsonObject("address") {

331

put("street", obj["street"]!!)

332

put("city", obj["city"]!!)

333

put("zip", obj["zip"]!!)

334

}

335

}

336

}

337

338

override fun transformDeserialize(element: JsonElement): JsonElement {

339

val obj = element.jsonObject

340

val address = obj["address"]?.jsonObject

341

342

return buildJsonObject {

343

put("name", obj["name"]!!)

344

if (address != null) {

345

put("street", address["street"]!!)

346

put("city", address["city"]!!)

347

put("zip", address["zip"]!!)

348

}

349

}

350

}

351

}

352

353

// Usage transforms:

354

// {"name":"John","street":"123 Main St","city":"Anytown","zip":"12345"}

355

// <->

356

// {"name":"John","address":{"street":"123 Main St","city":"Anytown","zip":"12345"}}

357

```

358

359

### Custom Serializer Registration

360

361

Register custom serializers with the Json configuration.

362

363

```kotlin

364

import kotlinx.serialization.*

365

import kotlinx.serialization.json.*

366

import kotlinx.serialization.modules.*

367

368

// Custom URL serializer

369

object UrlSerializer : KSerializer<java.net.URL> {

370

override val descriptor = PrimitiveSerialDescriptor("URL", PrimitiveKind.STRING)

371

override fun serialize(encoder: Encoder, value: java.net.URL) = encoder.encodeString(value.toString())

372

override fun deserialize(decoder: Decoder): java.net.URL = java.net.URL(decoder.decodeString())

373

}

374

375

// Custom UUID serializer with JsonEncoder optimization

376

object UuidSerializer : KSerializer<java.util.UUID> {

377

override val descriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)

378

379

override fun serialize(encoder: Encoder, value: java.util.UUID) {

380

if (encoder is JsonEncoder) {

381

// Optimize for JSON by storing as compact object

382

encoder.encodeJsonElement(buildJsonObject {

383

put("uuid", value.toString())

384

put("type", "uuid")

385

})

386

} else {

387

encoder.encodeString(value.toString())

388

}

389

}

390

391

override fun deserialize(decoder: Decoder): java.util.UUID {

392

return if (decoder is JsonDecoder) {

393

val element = decoder.decodeJsonElement()

394

when {

395

element is JsonPrimitive -> java.util.UUID.fromString(element.content)

396

element is JsonObject && "uuid" in element ->

397

java.util.UUID.fromString(element["uuid"]!!.jsonPrimitive.content)

398

else -> throw SerializationException("Invalid UUID format")

399

}

400

} else {

401

java.util.UUID.fromString(decoder.decodeString())

402

}

403

}

404

}

405

406

// Configure Json with custom serializers

407

val customJson = Json {

408

serializersModule = SerializersModule {

409

contextual(java.net.URL::class, UrlSerializer)

410

contextual(java.util.UUID::class, UuidSerializer)

411

412

// Polymorphic serializers

413

polymorphic(ApiMessage::class) {

414

subclass(TextMessage::class)

415

subclass(ImageMessage::class)

416

default { ApiMessageSerializer }

417

}

418

}

419

}

420

421

@Serializable

422

data class ResourceInfo(

423

@Contextual val id: java.util.UUID,

424

@Contextual val location: java.net.URL,

425

val name: String

426

)

427

428

val resource = ResourceInfo(

429

java.util.UUID.randomUUID(),

430

java.net.URL("https://example.com/resource"),

431

"Example Resource"

432

)

433

434

val encoded = customJson.encodeToString(resource)

435

val decoded = customJson.decodeFromString<ResourceInfo>(encoded)

436

```

437

438

## Error Handling in Advanced Serialization

439

440

Advanced serializers should handle errors gracefully and provide meaningful error messages.

441

442

```kotlin

443

import kotlinx.serialization.*

444

import kotlinx.serialization.json.*

445

446

object SafeTransformer : JsonTransformingSerializer<MyData>(MyData.serializer()) {

447

override fun transformDeserialize(element: JsonElement): JsonElement {

448

return try {

449

// Attempt transformation

450

performTransformation(element)

451

} catch (e: Exception) {

452

// Provide context in error message

453

throw SerializationException(

454

"Failed to transform JSON element: ${e.message}. " +

455

"Element was: $element",

456

e

457

)

458

}

459

}

460

461

private fun performTransformation(element: JsonElement): JsonElement {

462

// Safe transformation logic with validation

463

require(element is JsonObject) { "Expected JsonObject, got ${element::class.simpleName}" }

464

465

val requiredFields = listOf("id", "name")

466

val missingFields = requiredFields.filter { it !in element }

467

if (missingFields.isNotEmpty()) {

468

throw IllegalArgumentException("Missing required fields: $missingFields")

469

}

470

471

return buildJsonObject {

472

element.forEach { (key, value) ->

473

put(key, value)

474

}

475

}

476

}

477

}

478

```

479

480

## Performance Considerations

481

482

- **JsonEncoder/JsonDecoder**: Provide direct access to JSON structure, avoiding intermediate object creation

483

- **JsonContentPolymorphicSerializer**: Content inspection has overhead; use sparingly for large datasets

484

- **JsonTransformingSerializer**: Each transformation creates new JsonElement instances; consider caching for repeated operations

485

- **Custom Serializers**: Implement efficiently to avoid performance bottlenecks in serialization-heavy applications