or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotations.mdconfiguration.mdcore-operations.mdcustom-serializers.mddsl-builders.mdindex.mdjson-elements.mdnaming-strategies.mdplatform-extensions.md

naming-strategies.mddocs/

0

# Naming Strategies

1

2

Built-in naming strategies for transforming property names during serialization/deserialization.

3

4

## Capabilities

5

6

### JsonNamingStrategy Interface

7

8

Function interface for defining custom property name transformations.

9

10

```kotlin { .api }

11

/**

12

* Represents naming strategy - a transformer for serial names in Json format

13

* Transformed serial names are used for both serialization and deserialization

14

* Applied globally in Json configuration builder

15

*/

16

fun interface JsonNamingStrategy {

17

/**

18

* Accepts original serialName and returns transformed name for JSON

19

* @param descriptor SerialDescriptor of the containing class

20

* @param elementIndex Index of the element in the descriptor

21

* @param serialName Original serial name (property name or @SerialName value)

22

* @return Transformed serial name for JSON encoding/decoding

23

*/

24

fun serialNameForJson(descriptor: SerialDescriptor, elementIndex: Int, serialName: String): String

25

}

26

```

27

28

### Built-in Naming Strategies

29

30

Pre-built naming strategies for common naming conventions.

31

32

```kotlin { .api }

33

/**

34

* Built-in naming strategies companion object

35

*/

36

companion object Builtins {

37

/**

38

* Strategy that transforms serial names from camelCase to snake_case

39

* Words' bounds are defined by uppercase characters

40

*/

41

val SnakeCase: JsonNamingStrategy

42

43

/**

44

* Strategy that transforms serial names from camelCase to kebab-case

45

* Words' bounds are defined by uppercase characters

46

*/

47

val KebabCase: JsonNamingStrategy

48

}

49

```

50

51

### SnakeCase Strategy

52

53

Transforms camelCase property names to snake_case format.

54

55

**Usage Examples:**

56

57

```kotlin

58

@Serializable

59

data class UserAccount(

60

val userId: Int,

61

val firstName: String,

62

val lastName: String,

63

val emailAddress: String,

64

val isActive: Boolean,

65

val createdAt: Long,

66

val lastLoginTime: Long?

67

)

68

69

// Configure Json with snake_case naming

70

val snakeCaseJson = Json {

71

namingStrategy = JsonNamingStrategy.SnakeCase

72

}

73

74

val account = UserAccount(

75

userId = 123,

76

firstName = "Alice",

77

lastName = "Smith",

78

emailAddress = "alice.smith@example.com",

79

isActive = true,

80

createdAt = 1634567890123L,

81

lastLoginTime = 1634567990456L

82

)

83

84

val jsonString = snakeCaseJson.encodeToString(account)

85

/* Output:

86

{

87

"user_id": 123,

88

"first_name": "Alice",

89

"last_name": "Smith",

90

"email_address": "alice.smith@example.com",

91

"is_active": true,

92

"created_at": 1634567890123,

93

"last_login_time": 1634567990456

94

}

95

*/

96

97

// Deserialization also uses snake_case

98

val snakeCaseInput = """

99

{

100

"user_id": 456,

101

"first_name": "Bob",

102

"last_name": "Johnson",

103

"email_address": "bob@example.com",

104

"is_active": false,

105

"created_at": 1634567800000,

106

"last_login_time": null

107

}

108

"""

109

110

val deserializedAccount = snakeCaseJson.decodeFromString<UserAccount>(snakeCaseInput)

111

// UserAccount(userId=456, firstName="Bob", lastName="Johnson", ...)

112

113

// Acronym handling

114

@Serializable

115

data class APIConfiguration(

116

val httpURL: String,

117

val apiKey: String,

118

val maxHTTPConnections: Int,

119

val enableHTTP2: Boolean,

120

val xmlHttpTimeout: Long

121

)

122

123

val apiConfig = APIConfiguration(

124

httpURL = "https://api.example.com",

125

apiKey = "secret123",

126

maxHTTPConnections = 10,

127

enableHTTP2 = true,

128

xmlHttpTimeout = 30000L

129

)

130

131

val apiJson = snakeCaseJson.encodeToString(apiConfig)

132

/* Output:

133

{

134

"http_url": "https://api.example.com",

135

"api_key": "secret123",

136

"max_http_connections": 10,

137

"enable_http2": true,

138

"xml_http_timeout": 30000

139

}

140

*/

141

```

142

143

### KebabCase Strategy

144

145

Transforms camelCase property names to kebab-case format.

146

147

**Usage Examples:**

148

149

```kotlin

150

@Serializable

151

data class ServerConfig(

152

val serverPort: Int,

153

val bindAddress: String,

154

val maxConnections: Int,

155

val enableSSL: Boolean,

156

val sslCertPath: String?,

157

val requestTimeout: Long,

158

val keepAliveTimeout: Long

159

)

160

161

// Configure Json with kebab-case naming

162

val kebabCaseJson = Json {

163

namingStrategy = JsonNamingStrategy.KebabCase

164

prettyPrint = true

165

}

166

167

val config = ServerConfig(

168

serverPort = 8080,

169

bindAddress = "0.0.0.0",

170

maxConnections = 1000,

171

enableSSL = true,

172

sslCertPath = "/etc/ssl/cert.pem",

173

requestTimeout = 30000L,

174

keepAliveTimeout = 60000L

175

)

176

177

val configJson = kebabCaseJson.encodeToString(config)

178

/* Output:

179

{

180

"server-port": 8080,

181

"bind-address": "0.0.0.0",

182

"max-connections": 1000,

183

"enable-ssl": true,

184

"ssl-cert-path": "/etc/ssl/cert.pem",

185

"request-timeout": 30000,

186

"keep-alive-timeout": 60000

187

}

188

*/

189

190

// Works with deserialization too

191

val kebabInput = """

192

{

193

"server-port": 9090,

194

"bind-address": "127.0.0.1",

195

"max-connections": 500,

196

"enable-ssl": false,

197

"ssl-cert-path": null,

198

"request-timeout": 15000,

199

"keep-alive-timeout": 30000

200

}

201

"""

202

203

val deserializedConfig = kebabCaseJson.decodeFromString<ServerConfig>(kebabInput)

204

205

// CSS-like configuration format

206

@Serializable

207

data class StyleConfig(

208

val backgroundColor: String,

209

val textColor: String,

210

val fontSize: Int,

211

val fontWeight: String,

212

val borderRadius: Int,

213

val marginTop: Int,

214

val paddingLeft: Int

215

)

216

217

val style = StyleConfig(

218

backgroundColor = "#ffffff",

219

textColor = "#333333",

220

fontSize = 14,

221

fontWeight = "bold",

222

borderRadius = 4,

223

marginTop = 10,

224

paddingLeft = 20

225

)

226

227

val styleJson = kebabCaseJson.encodeToString(style)

228

/* Output:

229

{

230

"background-color": "#ffffff",

231

"text-color": "#333333",

232

"font-size": 14,

233

"font-weight": "bold",

234

"border-radius": 4,

235

"margin-top": 10,

236

"padding-left": 20

237

}

238

*/

239

```

240

241

### Custom Naming Strategies

242

243

Implementing custom naming transformation logic.

244

245

**Usage Examples:**

246

247

```kotlin

248

// Custom strategy for API field names

249

val apiNamingStrategy = JsonNamingStrategy { descriptor, elementIndex, serialName ->

250

when {

251

// ID fields get special treatment

252

serialName.endsWith("Id") -> serialName.removeSuffix("Id") + "_id"

253

serialName.endsWith("ID") -> serialName.removeSuffix("ID") + "_id"

254

255

// Boolean fields get is_ prefix

256

descriptor.getElementDescriptor(elementIndex).kind == PrimitiveKind.BOOLEAN &&

257

!serialName.startsWith("is") -> "is_$serialName"

258

259

// Everything else to snake_case

260

else -> serialName.fold("") { acc, char ->

261

when {

262

char.isUpperCase() && acc.isNotEmpty() -> acc + "_" + char.lowercase()

263

else -> acc + char.lowercase()

264

}

265

}

266

}

267

}

268

269

@Serializable

270

data class EntityModel(

271

val entityId: Long,

272

val parentID: Long?,

273

val name: String,

274

val active: Boolean,

275

val verified: Boolean,

276

val createdAt: Long

277

)

278

279

val customJson = Json {

280

namingStrategy = apiNamingStrategy

281

prettyPrint = true

282

}

283

284

val entity = EntityModel(

285

entityId = 123L,

286

parentID = 456L,

287

name = "Test Entity",

288

active = true,

289

verified = false,

290

createdAt = System.currentTimeMillis()

291

)

292

293

val entityJson = customJson.encodeToString(entity)

294

/* Output:

295

{

296

"entity_id": 123,

297

"parent_id": 456,

298

"name": "Test Entity",

299

"is_active": true,

300

"is_verified": false,

301

"created_at": 1634567890123

302

}

303

*/

304

305

// Context-aware naming strategy

306

val contextualNamingStrategy = JsonNamingStrategy { descriptor, elementIndex, serialName ->

307

val className = descriptor.serialName.substringAfterLast(".")

308

309

when (className) {

310

"DatabaseConfig" -> {

311

// Database configs use underscore convention

312

serialName.fold("") { acc, char ->

313

if (char.isUpperCase() && acc.isNotEmpty()) acc + "_" + char.lowercase()

314

else acc + char.lowercase()

315

}

316

}

317

"UIComponent" -> {

318

// UI components use kebab-case

319

serialName.fold("") { acc, char ->

320

if (char.isUpperCase() && acc.isNotEmpty()) acc + "-" + char.lowercase()

321

else acc + char.lowercase()

322

}

323

}

324

else -> serialName // No transformation for other classes

325

}

326

}

327

328

@Serializable

329

data class DatabaseConfig(

330

val hostName: String,

331

val portNumber: Int,

332

val databaseName: String,

333

val connectionTimeout: Long

334

)

335

336

@Serializable

337

data class UIComponent(

338

val componentType: String,

339

val backgroundColor: String,

340

val borderWidth: Int,

341

val isVisible: Boolean

342

)

343

344

@Serializable

345

data class RegularClass(

346

val someProperty: String,

347

val anotherProperty: Int

348

)

349

350

val contextualJson = Json { namingStrategy = contextualNamingStrategy }

351

352

val dbConfig = DatabaseConfig("localhost", 5432, "myapp", 30000L)

353

val uiComponent = UIComponent("button", "#ffffff", 1, true)

354

val regular = RegularClass("value", 42)

355

356

// Each class gets appropriate naming convention

357

val dbJson = contextualJson.encodeToString(dbConfig)

358

// {"host_name":"localhost","port_number":5432,"database_name":"myapp","connection_timeout":30000}

359

360

val uiJson = contextualJson.encodeToString(uiComponent)

361

// {"component-type":"button","background-color":"#ffffff","border-width":1,"is-visible":true}

362

363

val regularJson = contextualJson.encodeToString(regular)

364

// {"someProperty":"value","anotherProperty":42}

365

```

366

367

### Naming Strategy Considerations

368

369

Important considerations when using naming strategies.

370

371

**Usage Examples:**

372

373

```kotlin

374

// Naming strategies affect ALL properties

375

@Serializable

376

data class MixedCase(

377

@SerialName("custom_name") // SerialName is also transformed!

378

val property1: String,

379

val camelCaseProperty: String

380

)

381

382

val snakeJson = Json { namingStrategy = JsonNamingStrategy.SnakeCase }

383

val mixed = MixedCase("value1", "value2")

384

val result = snakeJson.encodeToString(mixed)

385

// {"custom_name":"value1","camel_case_property":"value2"}

386

// Note: custom_name becomes custom_name (already snake_case)

387

388

// Use JsonNames for alternative deserialization

389

@Serializable

390

data class FlexibleModel(

391

@JsonNames("legacy_name", "old_name") // JsonNames values are NOT transformed

392

val newName: String

393

)

394

395

val flexibleJson = Json { namingStrategy = JsonNamingStrategy.SnakeCase }

396

397

// Can deserialize from multiple formats

398

val input1 = """{"new_name":"value"}""" // Transformed property name

399

val input2 = """{"legacy_name":"value"}""" // JsonNames alternative (not transformed)

400

val input3 = """{"old_name":"value"}""" // Another JsonNames alternative

401

402

val result1 = flexibleJson.decodeFromString<FlexibleModel>(input1)

403

val result2 = flexibleJson.decodeFromString<FlexibleModel>(input2)

404

val result3 = flexibleJson.decodeFromString<FlexibleModel>(input3)

405

// All create FlexibleModel(newName="value")

406

407

// But encoding uses transformed name

408

val encoded = flexibleJson.encodeToString(result1)

409

// {"new_name":"value"}

410

411

// Polymorphic serialization is not affected

412

@Serializable

413

@JsonClassDiscriminator("messageType") // NOT transformed

414

sealed class Message {

415

@Serializable

416

data class TextMessage(val messageText: String) : Message() // messageText -> message_text

417

}

418

419

val polyJson = Json {

420

namingStrategy = JsonNamingStrategy.SnakeCase

421

serializersModule = SerializersModule {

422

polymorphic(Message::class) {

423

subclass(Message.TextMessage::class)

424

}

425

}

426

}

427

428

val textMsg: Message = Message.TextMessage("Hello")

429

val polyResult = polyJson.encodeToString(textMsg)

430

// {"messageType":"TextMessage","message_text":"Hello"}

431

// Note: discriminator key is NOT transformed, but properties are

432

```