0
# Annotations
1
2
Annotations for controlling JSON serialization behavior including alternative property names and polymorphic serialization.
3
4
## Capabilities
5
6
### JsonNames
7
8
Annotation for specifying alternative property names during deserialization.
9
10
```kotlin { .api }
11
/**
12
* Indicates that the field can be represented in JSON with multiple possible alternative names
13
* Json format recognizes this annotation and can decode data using any of the alternative names
14
* Does not affect JSON encoding in any way
15
* @param names Alternative names for the property during deserialization
16
*/
17
@Target(AnnotationTarget.PROPERTY)
18
annotation class JsonNames(vararg val names: String)
19
```
20
21
**Usage Examples:**
22
23
```kotlin
24
@Serializable
25
data class User(
26
val id: Int,
27
@JsonNames("user_name", "username", "login")
28
val name: String,
29
@JsonNames("user_email", "email_address")
30
val email: String,
31
@JsonNames("is_active", "enabled")
32
val active: Boolean = true
33
)
34
35
// All of these JSON inputs will deserialize successfully
36
val json1 = """{"id":1,"name":"Alice","email":"alice@example.com","active":true}"""
37
val json2 = """{"id":1,"user_name":"Alice","user_email":"alice@example.com","is_active":true}"""
38
val json3 = """{"id":1,"username":"Alice","email_address":"alice@example.com","enabled":true}"""
39
val json4 = """{"id":1,"login":"Alice","user_email":"alice@example.com","is_active":true}"""
40
41
val user1 = Json.decodeFromString<User>(json1)
42
val user2 = Json.decodeFromString<User>(json2)
43
val user3 = Json.decodeFromString<User>(json3)
44
val user4 = Json.decodeFromString<User>(json4)
45
// All create the same User object
46
47
// But encoding always uses the original property name
48
val encoded = Json.encodeToString(user1)
49
// {"id":1,"name":"Alice","email":"alice@example.com","active":true}
50
51
// Legacy API compatibility
52
@Serializable
53
data class ApiResponse(
54
@JsonNames("success", "ok", "status")
55
val isSuccess: Boolean,
56
@JsonNames("msg", "message", "error_message")
57
val responseMessage: String,
58
@JsonNames("payload", "result", "response_data")
59
val data: JsonElement?
60
)
61
62
// Handles multiple API versions
63
val v1Response = """{"success":true,"msg":"OK","payload":{"user_id":123}}"""
64
val v2Response = """{"ok":true,"message":"OK","result":{"user_id":123}}"""
65
val v3Response = """{"status":true,"error_message":"OK","response_data":{"user_id":123}}"""
66
67
// All deserialize to same object
68
val response1 = Json.decodeFromString<ApiResponse>(v1Response)
69
val response2 = Json.decodeFromString<ApiResponse>(v2Response)
70
val response3 = Json.decodeFromString<ApiResponse>(v3Response)
71
```
72
73
### JsonClassDiscriminator
74
75
Annotation for specifying custom class discriminator key for polymorphic serialization.
76
77
```kotlin { .api }
78
/**
79
* Specifies key for class discriminator value used during polymorphic serialization
80
* Provided key is used only for annotated class and its subclasses
81
* This annotation is inheritable, so placing it on base class affects all subclasses
82
* @param discriminator Custom discriminator property name
83
*/
84
@Target(AnnotationTarget.CLASS)
85
annotation class JsonClassDiscriminator(val discriminator: String)
86
```
87
88
**Usage Examples:**
89
90
```kotlin
91
@Serializable
92
@JsonClassDiscriminator("message_type")
93
abstract class BaseMessage {
94
abstract val timestamp: Long
95
}
96
97
@Serializable
98
data class TextMessage(
99
override val timestamp: Long,
100
val content: String
101
) : BaseMessage()
102
103
@Serializable
104
data class ImageMessage(
105
override val timestamp: Long,
106
val imageUrl: String,
107
val caption: String?
108
) : BaseMessage()
109
110
@Serializable
111
data class SystemMessage(
112
override val timestamp: Long,
113
val systemCode: String
114
) : BaseMessage()
115
116
// Configure polymorphic serialization
117
val json = Json {
118
serializersModule = SerializersModule {
119
polymorphic(BaseMessage::class) {
120
subclass(TextMessage::class)
121
subclass(ImageMessage::class)
122
subclass(SystemMessage::class)
123
}
124
}
125
}
126
127
// Serialization uses custom discriminator
128
val textMessage: BaseMessage = TextMessage(System.currentTimeMillis(), "Hello World")
129
val encoded = json.encodeToString(textMessage)
130
// {"message_type":"TextMessage","timestamp":1634567890123,"content":"Hello World"}
131
132
val decoded = json.decodeFromString<BaseMessage>(encoded)
133
134
// Different discriminator for different hierarchies
135
@Serializable
136
@JsonClassDiscriminator("event_type")
137
sealed class Event {
138
@Serializable
139
data class UserLogin(val userId: String, val timestamp: Long) : Event()
140
141
@Serializable
142
data class UserLogout(val userId: String, val sessionDuration: Long) : Event()
143
}
144
145
@Serializable
146
@JsonClassDiscriminator("shape_kind")
147
sealed class Shape {
148
@Serializable
149
data class Circle(val radius: Double) : Shape()
150
151
@Serializable
152
data class Rectangle(val width: Double, val height: Double) : Shape()
153
}
154
155
// Each hierarchy uses its own discriminator
156
val eventJson = Json {
157
serializersModule = SerializersModule {
158
polymorphic(Event::class) {
159
subclass(Event.UserLogin::class)
160
subclass(Event.UserLogout::class)
161
}
162
polymorphic(Shape::class) {
163
subclass(Shape.Circle::class)
164
subclass(Shape.Rectangle::class)
165
}
166
}
167
}
168
169
val login: Event = Event.UserLogin("user123", System.currentTimeMillis())
170
val circle: Shape = Shape.Circle(5.0)
171
172
val loginJson = eventJson.encodeToString(login)
173
// {"event_type":"UserLogin","userId":"user123","timestamp":1634567890123}
174
175
val circleJson = eventJson.encodeToString(circle)
176
// {"shape_kind":"Circle","radius":5.0}
177
```
178
179
### JsonIgnoreUnknownKeys
180
181
Annotation for allowing unknown properties in specific classes during deserialization.
182
183
```kotlin { .api }
184
/**
185
* Specifies that encounters of unknown properties in input JSON should be ignored
186
* instead of throwing SerializationException for this specific class
187
* Allows selective ignoring of unknown keys without affecting global Json configuration
188
*/
189
@Target(AnnotationTarget.CLASS)
190
annotation class JsonIgnoreUnknownKeys
191
```
192
193
**Usage Examples:**
194
195
```kotlin
196
@Serializable
197
@JsonIgnoreUnknownKeys
198
data class FlexibleConfig(
199
val host: String,
200
val port: Int,
201
val ssl: Boolean = false
202
)
203
204
@Serializable
205
data class StrictConfig(
206
val apiKey: String,
207
val endpoint: String
208
)
209
210
// FlexibleConfig ignores unknown properties
211
val flexibleJson = """{"host":"localhost","port":8080,"ssl":true,"unknown_field":"ignored","extra":123}"""
212
val flexibleConfig = Json.decodeFromString<FlexibleConfig>(flexibleJson)
213
// Success: FlexibleConfig(host="localhost", port=8080, ssl=true)
214
215
// StrictConfig throws exception for unknown properties
216
val strictJson = """{"apiKey":"secret","endpoint":"https://api.example.com","unknown":"field"}"""
217
// Json.decodeFromString<StrictConfig>(strictJson)
218
// Would throw SerializationException: Encountered an unknown key 'unknown'
219
220
// Selective flexibility in complex structures
221
@Serializable
222
data class ApiRequest(
223
val method: String,
224
val path: String,
225
@JsonIgnoreUnknownKeys
226
val headers: Headers,
227
val body: RequestBody
228
)
229
230
@Serializable
231
@JsonIgnoreUnknownKeys
232
data class Headers(
233
val contentType: String,
234
val authorization: String? = null
235
)
236
237
@Serializable
238
data class RequestBody(
239
val data: JsonElement
240
)
241
242
val requestJson = """
243
{
244
"method": "POST",
245
"path": "/api/users",
246
"headers": {
247
"contentType": "application/json",
248
"authorization": "Bearer token123",
249
"x-custom-header": "ignored",
250
"user-agent": "also ignored"
251
},
252
"body": {
253
"data": {"name": "Alice", "email": "alice@example.com"}
254
}
255
}
256
"""
257
258
val request = Json.decodeFromString<ApiRequest>(requestJson)
259
// Success: Headers ignores unknown properties, but RequestBody must match exactly
260
261
// Evolution-friendly data classes
262
@Serializable
263
@JsonIgnoreUnknownKeys
264
data class UserProfile(
265
val id: String,
266
val name: String,
267
val email: String,
268
val createdAt: Long,
269
// Future versions might add more fields, but this version ignores them
270
)
271
272
// Can handle JSON from newer API versions
273
val futureJson = """
274
{
275
"id": "user123",
276
"name": "Alice",
277
"email": "alice@example.com",
278
"createdAt": 1634567890123,
279
"profilePicture": "https://example.com/pic.jpg",
280
"preferences": {"theme": "dark"},
281
"newFeature": true
282
}
283
"""
284
285
val profile = Json.decodeFromString<UserProfile>(futureJson)
286
// Success: Unknown fields are ignored
287
```
288
289
### Combining Annotations
290
291
Using multiple annotations together for flexible JSON handling.
292
293
**Usage Examples:**
294
295
```kotlin
296
@Serializable
297
@JsonIgnoreUnknownKeys
298
@JsonClassDiscriminator("notification_type")
299
sealed class Notification {
300
abstract val id: String
301
abstract val timestamp: Long
302
}
303
304
@Serializable
305
data class EmailNotification(
306
override val id: String,
307
override val timestamp: Long,
308
@JsonNames("email_address", "recipient", "to")
309
val email: String,
310
@JsonNames("email_subject", "title")
311
val subject: String,
312
@JsonNames("email_body", "content", "message")
313
val body: String
314
) : Notification()
315
316
@Serializable
317
data class PushNotification(
318
override val id: String,
319
override val timestamp: Long,
320
@JsonNames("device_token", "token", "device_id")
321
val deviceToken: String,
322
@JsonNames("push_title", "notification_title")
323
val title: String,
324
@JsonNames("push_body", "notification_body", "text")
325
val body: String
326
) : Notification()
327
328
val notificationJson = Json {
329
serializersModule = SerializersModule {
330
polymorphic(Notification::class) {
331
subclass(EmailNotification::class)
332
subclass(PushNotification::class)
333
}
334
}
335
}
336
337
// Handles various input formats with unknown fields
338
val legacyEmailJson = """
339
{
340
"notification_type": "EmailNotification",
341
"id": "email-001",
342
"timestamp": 1634567890123,
343
"email_address": "user@example.com",
344
"email_subject": "Welcome!",
345
"email_body": "Welcome to our service",
346
"legacy_field": "ignored",
347
"internal_id": 12345
348
}
349
"""
350
351
val modernPushJson = """
352
{
353
"notification_type": "PushNotification",
354
"id": "push-001",
355
"timestamp": 1634567890123,
356
"device_token": "abc123def456",
357
"push_title": "New Message",
358
"push_body": "You have a new message",
359
"priority": "high",
360
"ttl": 3600
361
}
362
"""
363
364
val emailNotification = notificationJson.decodeFromString<Notification>(legacyEmailJson)
365
val pushNotification = notificationJson.decodeFromString<Notification>(modernPushJson)
366
// Both deserialize successfully despite unknown fields and alternative names
367
```