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
```