or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

builder-dsl.mdconfiguration.mdcore-operations.mdcustom-serializers.mddynamic-conversion.mdindex.mdjson-annotations.mdjson-element.md

custom-serializers.mddocs/

0

# Custom Serializers

1

2

Base classes and interfaces for implementing custom JSON serialization logic with transformation and polymorphic capabilities. These provide powerful extension points for handling complex serialization scenarios.

3

4

## Capabilities

5

6

### JsonTransformingSerializer

7

8

Abstract base class for creating serializers that transform JsonElement structures during serialization/deserialization.

9

10

```kotlin { .api }

11

/**

12

* Base class for JSON transformation serializers

13

* @param tSerializer The underlying serializer for type T

14

*/

15

abstract class JsonTransformingSerializer<T>(private val tSerializer: KSerializer<T>) : KSerializer<T> {

16

/**

17

* Transform JsonElement during serialization (encode)

18

* @param element JsonElement to transform

19

* @return Transformed JsonElement

20

*/

21

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

22

23

/**

24

* Transform JsonElement during deserialization (decode)

25

* @param element JsonElement to transform

26

* @return Transformed JsonElement

27

*/

28

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

29

}

30

```

31

32

**Usage Examples:**

33

34

```kotlin

35

@Serializable

36

data class Coordinates(val x: Double, val y: Double)

37

38

// Transform coordinates between different coordinate systems

39

object CoordinateTransformer : JsonTransformingSerializer<Coordinates>(Coordinates.serializer()) {

40

override fun transformSerialize(element: JsonElement): JsonElement {

41

val obj = element.jsonObject

42

val x = obj["x"]?.jsonPrimitive?.double ?: 0.0

43

val y = obj["y"]?.jsonPrimitive?.double ?: 0.0

44

45

// Convert from internal coordinate system to API coordinate system

46

// Example: scale by 100 and offset

47

return buildJsonObject {

48

put("x", x * 100 + 1000)

49

put("y", y * 100 + 2000)

50

}

51

}

52

53

override fun transformDeserialize(element: JsonElement): JsonElement {

54

val obj = element.jsonObject

55

val x = obj["x"]?.jsonPrimitive?.double ?: 0.0

56

val y = obj["y"]?.jsonPrimitive?.double ?: 0.0

57

58

// Convert from API coordinate system to internal coordinate system

59

return buildJsonObject {

60

put("x", (x - 1000) / 100)

61

put("y", (y - 2000) / 100)

62

}

63

}

64

}

65

66

// Usage

67

val json = Json.Default

68

val coords = Coordinates(5.0, 10.0)

69

70

// Serialize with transformation

71

val serialized = json.encodeToString(CoordinateTransformer, coords)

72

// Result: {"x":1500.0,"y":3000.0} (transformed values)

73

74

// Deserialize with transformation

75

val apiCoords = """{"x":1500.0,"y":3000.0}"""

76

val deserialized = json.decodeFromString(CoordinateTransformer, apiCoords)

77

// Result: Coordinates(x=5.0, y=10.0) (original values restored)

78

```

79

80

### Property Manipulation Serializer

81

82

Transform property names or values during serialization.

83

84

**Usage Examples:**

85

86

```kotlin

87

@Serializable

88

data class User(val firstName: String, val lastName: String, val age: Int)

89

90

// Serializer that wraps user data in an envelope

91

object UserEnvelopeSerializer : JsonTransformingSerializer<User>(User.serializer()) {

92

override fun transformSerialize(element: JsonElement): JsonElement {

93

return buildJsonObject {

94

put("user_data", element)

95

put("metadata", buildJsonObject {

96

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

97

put("version", "1.0")

98

})

99

}

100

}

101

102

override fun transformDeserialize(element: JsonElement): JsonElement {

103

val obj = element.jsonObject

104

// Extract user data from envelope

105

return obj["user_data"] ?: obj // Fallback to original if no envelope

106

}

107

}

108

109

// Usage

110

val user = User("Alice", "Smith", 30)

111

val enveloped = json.encodeToString(UserEnvelopeSerializer, user)

112

// Result: {

113

// "user_data": {"firstName":"Alice","lastName":"Smith","age":30},

114

// "metadata": {"serialized_at":1234567890,"version":"1.0"}

115

// }

116

117

val restored = json.decodeFromString(UserEnvelopeSerializer, enveloped)

118

// Result: User(firstName="Alice", lastName="Smith", age=30)

119

```

120

121

### JsonContentPolymorphicSerializer

122

123

Abstract class for polymorphic serialization based on JSON content inspection.

124

125

```kotlin { .api }

126

/**

127

* Polymorphic serializer that selects implementation based on JSON content

128

* @param baseClass Base class for polymorphism

129

*/

130

abstract class JsonContentPolymorphicSerializer<T : Any>(private val baseClass: KClass<T>) : AbstractPolymorphicSerializer<T>() {

131

/**

132

* Select deserializer based on JsonElement content

133

* @param element JsonElement to inspect

134

* @return Deserialization strategy for the specific type

135

*/

136

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

137

}

138

```

139

140

**Usage Examples:**

141

142

```kotlin

143

@Serializable

144

abstract class Shape {

145

abstract val area: Double

146

}

147

148

@Serializable

149

data class Circle(val radius: Double) : Shape() {

150

override val area: Double get() = 3.14159 * radius * radius

151

}

152

153

@Serializable

154

data class Rectangle(val width: Double, val height: Double) : Shape() {

155

override val area: Double get() = width * height

156

}

157

158

@Serializable

159

data class Triangle(val base: Double, val height: Double) : Shape() {

160

override val area: Double get() = 0.5 * base * height

161

}

162

163

// Content-based polymorphic serializer

164

object ShapeSerializer : JsonContentPolymorphicSerializer<Shape>(Shape::class) {

165

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

166

val obj = element.jsonObject

167

return when {

168

"radius" in obj -> Circle.serializer()

169

"width" in obj && "height" in obj -> Rectangle.serializer()

170

"base" in obj && "height" in obj -> Triangle.serializer()

171

else -> throw JsonDecodingException("Unknown shape type: ${obj.keys}")

172

}

173

}

174

}

175

176

// Usage

177

val shapes = listOf(

178

Circle(5.0),

179

Rectangle(4.0, 6.0),

180

Triangle(3.0, 8.0)

181

)

182

183

val json = Json.Default

184

185

// Serialize different shapes

186

shapes.forEach { shape ->

187

val serialized = json.encodeToString(ShapeSerializer, shape)

188

println("Serialized: $serialized")

189

190

val deserialized = json.decodeFromString(ShapeSerializer, serialized)

191

println("Deserialized: $deserialized")

192

println("Area: ${deserialized.area}")

193

println()

194

}

195

196

// Handle JSON without discriminator

197

val circleJson = """{"radius":10.0}"""

198

val circle = json.decodeFromString(ShapeSerializer, circleJson)

199

// Result: Circle(radius=10.0)

200

201

val rectangleJson = """{"width":5.0,"height":3.0}"""

202

val rectangle = json.decodeFromString(ShapeSerializer, rectangleJson)

203

// Result: Rectangle(width=5.0, height=3.0)

204

```

205

206

### JsonEncoder and JsonDecoder Interfaces

207

208

Access JSON-specific encoding and decoding capabilities in custom serializers.

209

210

```kotlin { .api }

211

/**

212

* JSON-specific encoder interface

213

*/

214

interface JsonEncoder : Encoder, CompositeEncoder {

215

/**

216

* Json instance being used for encoding

217

*/

218

val json: Json

219

220

/**

221

* Encode JsonElement directly

222

* @param element JsonElement to encode

223

*/

224

fun encodeJsonElement(element: JsonElement)

225

}

226

227

/**

228

* JSON-specific decoder interface

229

*/

230

interface JsonDecoder : Decoder, CompositeDecoder {

231

/**

232

* Json instance being used for decoding

233

*/

234

val json: Json

235

236

/**

237

* Decode current value as JsonElement

238

* @return JsonElement representation

239

*/

240

fun decodeJsonElement(): JsonElement

241

}

242

```

243

244

**Usage Examples:**

245

246

```kotlin

247

@Serializable

248

data class FlexibleData(val content: String)

249

250

// Custom serializer using JsonEncoder/JsonDecoder

251

object FlexibleDataSerializer : KSerializer<FlexibleData> {

252

override val descriptor = buildClassSerialDescriptor("FlexibleData") {

253

element<String>("content")

254

}

255

256

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

257

if (encoder is JsonEncoder) {

258

// Use JSON-specific functionality

259

val element = buildJsonObject {

260

put("content", value.content)

261

put("serialized_with", "custom_serializer")

262

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

263

}

264

encoder.encodeJsonElement(element)

265

} else {

266

// Fallback for non-JSON formats

267

encoder.encodeString(value.content)

268

}

269

}

270

271

override fun deserialize(decoder: Decoder): FlexibleData {

272

return if (decoder is JsonDecoder) {

273

// Use JSON-specific functionality

274

val element = decoder.decodeJsonElement()

275

val obj = element.jsonObject

276

val content = obj["content"]?.jsonPrimitive?.content

277

?: throw JsonDecodingException("Missing content field")

278

FlexibleData(content)

279

} else {

280

// Fallback for non-JSON formats

281

FlexibleData(decoder.decodeString())

282

}

283

}

284

}

285

286

// Usage

287

val data = FlexibleData("Hello, World!")

288

val json = Json { prettyPrint = true }

289

290

val serialized = json.encodeToString(FlexibleDataSerializer, data)

291

// Result: {

292

// "content": "Hello, World!",

293

// "serialized_with": "custom_serializer",

294

// "timestamp": 1234567890

295

// }

296

297

val deserialized = json.decodeFromString(FlexibleDataSerializer, serialized)

298

// Result: FlexibleData(content="Hello, World!")

299

```

300

301

### Contextual Serialization

302

303

Access serialization context and configuration in custom serializers.

304

305

**Usage Examples:**

306

307

```kotlin

308

// Serializer that adapts behavior based on Json configuration

309

object AdaptiveStringSerializer : KSerializer<String> {

310

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

311

312

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

313

if (encoder is JsonEncoder) {

314

val json = encoder.json

315

val element = if (json.configuration.prettyPrint) {

316

// If pretty printing is enabled, add formatting hints

317

JsonPrimitive("FORMATTED: $value")

318

} else {

319

JsonPrimitive(value)

320

}

321

encoder.encodeJsonElement(element)

322

} else {

323

encoder.encodeString(value)

324

}

325

}

326

327

override fun deserialize(decoder: Decoder): String {

328

return if (decoder is JsonDecoder) {

329

val element = decoder.decodeJsonElement()

330

val content = element.jsonPrimitive.content

331

// Remove formatting prefix if present

332

content.removePrefix("FORMATTED: ")

333

} else {

334

decoder.decodeString()

335

}

336

}

337

}

338

339

// Usage with different Json configurations

340

val compactJson = Json.Default

341

val prettyJson = Json { prettyPrint = true }

342

343

val text = "Hello"

344

345

val compactResult = compactJson.encodeToString(AdaptiveStringSerializer, text)

346

// Result: "Hello"

347

348

val prettyResult = prettyJson.encodeToString(AdaptiveStringSerializer, text)

349

// Result: "FORMATTED: Hello"

350

351

// Both deserialize to the same value

352

val restored1 = compactJson.decodeFromString(AdaptiveStringSerializer, compactResult)

353

val restored2 = prettyJson.decodeFromString(AdaptiveStringSerializer, prettyResult)

354

// Both result in: "Hello"

355

```

356

357

### Error Handling in Custom Serializers

358

359

Proper error handling patterns for custom serializers.

360

361

**Usage Examples:**

362

363

```kotlin

364

object SafeIntSerializer : KSerializer<Int> {

365

override val descriptor = PrimitiveSerialDescriptor("SafeInt", PrimitiveKind.INT)

366

367

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

368

encoder.encodeInt(value)

369

}

370

371

override fun deserialize(decoder: Decoder): Int {

372

return try {

373

if (decoder is JsonDecoder) {

374

val element = decoder.decodeJsonElement()

375

when (val primitive = element.jsonPrimitive) {

376

is JsonNull -> 0 // Default value for null

377

else -> {

378

// Try to parse as int, with fallback handling

379

primitive.intOrNull

380

?: primitive.doubleOrNull?.toInt()

381

?: primitive.content.toIntOrNull()

382

?: throw JsonDecodingException("Cannot convert '${primitive.content}' to Int")

383

}

384

}

385

} else {

386

decoder.decodeInt()

387

}

388

} catch (e: NumberFormatException) {

389

throw JsonDecodingException("Invalid number format: ${e.message}")

390

} catch (e: IllegalArgumentException) {

391

throw JsonDecodingException("Invalid integer value: ${e.message}")

392

}

393

}

394

}

395

396

// Usage - handles various input formats gracefully

397

val json = Json.Default

398

399

val validInputs = listOf(

400

"42", // String number

401

"42.0", // Float that converts to int

402

"null" // Null converts to 0

403

)

404

405

validInputs.forEach { input ->

406

try {

407

val result = json.decodeFromString(SafeIntSerializer, input)

408

println("'$input' -> $result")

409

} catch (e: JsonDecodingException) {

410

println("'$input' -> Error: ${e.message}")

411

}

412

}

413

```