Kotlin multiplatform JSON serialization library with type-safe, reflectionless approach supporting all platforms
—
Annotations for controlling JSON serialization behavior including alternative property names and polymorphic serialization.
Annotation for specifying alternative property names during deserialization.
/**
* Indicates that the field can be represented in JSON with multiple possible alternative names
* Json format recognizes this annotation and can decode data using any of the alternative names
* Does not affect JSON encoding in any way
* @param names Alternative names for the property during deserialization
*/
@Target(AnnotationTarget.PROPERTY)
annotation class JsonNames(vararg val names: String)Usage Examples:
@Serializable
data class User(
val id: Int,
@JsonNames("user_name", "username", "login")
val name: String,
@JsonNames("user_email", "email_address")
val email: String,
@JsonNames("is_active", "enabled")
val active: Boolean = true
)
// All of these JSON inputs will deserialize successfully
val json1 = """{"id":1,"name":"Alice","email":"alice@example.com","active":true}"""
val json2 = """{"id":1,"user_name":"Alice","user_email":"alice@example.com","is_active":true}"""
val json3 = """{"id":1,"username":"Alice","email_address":"alice@example.com","enabled":true}"""
val json4 = """{"id":1,"login":"Alice","user_email":"alice@example.com","is_active":true}"""
val user1 = Json.decodeFromString<User>(json1)
val user2 = Json.decodeFromString<User>(json2)
val user3 = Json.decodeFromString<User>(json3)
val user4 = Json.decodeFromString<User>(json4)
// All create the same User object
// But encoding always uses the original property name
val encoded = Json.encodeToString(user1)
// {"id":1,"name":"Alice","email":"alice@example.com","active":true}
// Legacy API compatibility
@Serializable
data class ApiResponse(
@JsonNames("success", "ok", "status")
val isSuccess: Boolean,
@JsonNames("msg", "message", "error_message")
val responseMessage: String,
@JsonNames("payload", "result", "response_data")
val data: JsonElement?
)
// Handles multiple API versions
val v1Response = """{"success":true,"msg":"OK","payload":{"user_id":123}}"""
val v2Response = """{"ok":true,"message":"OK","result":{"user_id":123}}"""
val v3Response = """{"status":true,"error_message":"OK","response_data":{"user_id":123}}"""
// All deserialize to same object
val response1 = Json.decodeFromString<ApiResponse>(v1Response)
val response2 = Json.decodeFromString<ApiResponse>(v2Response)
val response3 = Json.decodeFromString<ApiResponse>(v3Response)Annotation for specifying custom class discriminator key for polymorphic serialization.
/**
* Specifies key for class discriminator value used during polymorphic serialization
* Provided key is used only for annotated class and its subclasses
* This annotation is inheritable, so placing it on base class affects all subclasses
* @param discriminator Custom discriminator property name
*/
@Target(AnnotationTarget.CLASS)
annotation class JsonClassDiscriminator(val discriminator: String)Usage Examples:
@Serializable
@JsonClassDiscriminator("message_type")
abstract class BaseMessage {
abstract val timestamp: Long
}
@Serializable
data class TextMessage(
override val timestamp: Long,
val content: String
) : BaseMessage()
@Serializable
data class ImageMessage(
override val timestamp: Long,
val imageUrl: String,
val caption: String?
) : BaseMessage()
@Serializable
data class SystemMessage(
override val timestamp: Long,
val systemCode: String
) : BaseMessage()
// Configure polymorphic serialization
val json = Json {
serializersModule = SerializersModule {
polymorphic(BaseMessage::class) {
subclass(TextMessage::class)
subclass(ImageMessage::class)
subclass(SystemMessage::class)
}
}
}
// Serialization uses custom discriminator
val textMessage: BaseMessage = TextMessage(System.currentTimeMillis(), "Hello World")
val encoded = json.encodeToString(textMessage)
// {"message_type":"TextMessage","timestamp":1634567890123,"content":"Hello World"}
val decoded = json.decodeFromString<BaseMessage>(encoded)
// Different discriminator for different hierarchies
@Serializable
@JsonClassDiscriminator("event_type")
sealed class Event {
@Serializable
data class UserLogin(val userId: String, val timestamp: Long) : Event()
@Serializable
data class UserLogout(val userId: String, val sessionDuration: Long) : Event()
}
@Serializable
@JsonClassDiscriminator("shape_kind")
sealed class Shape {
@Serializable
data class Circle(val radius: Double) : Shape()
@Serializable
data class Rectangle(val width: Double, val height: Double) : Shape()
}
// Each hierarchy uses its own discriminator
val eventJson = Json {
serializersModule = SerializersModule {
polymorphic(Event::class) {
subclass(Event.UserLogin::class)
subclass(Event.UserLogout::class)
}
polymorphic(Shape::class) {
subclass(Shape.Circle::class)
subclass(Shape.Rectangle::class)
}
}
}
val login: Event = Event.UserLogin("user123", System.currentTimeMillis())
val circle: Shape = Shape.Circle(5.0)
val loginJson = eventJson.encodeToString(login)
// {"event_type":"UserLogin","userId":"user123","timestamp":1634567890123}
val circleJson = eventJson.encodeToString(circle)
// {"shape_kind":"Circle","radius":5.0}Annotation for allowing unknown properties in specific classes during deserialization.
/**
* Specifies that encounters of unknown properties in input JSON should be ignored
* instead of throwing SerializationException for this specific class
* Allows selective ignoring of unknown keys without affecting global Json configuration
*/
@Target(AnnotationTarget.CLASS)
annotation class JsonIgnoreUnknownKeysUsage Examples:
@Serializable
@JsonIgnoreUnknownKeys
data class FlexibleConfig(
val host: String,
val port: Int,
val ssl: Boolean = false
)
@Serializable
data class StrictConfig(
val apiKey: String,
val endpoint: String
)
// FlexibleConfig ignores unknown properties
val flexibleJson = """{"host":"localhost","port":8080,"ssl":true,"unknown_field":"ignored","extra":123}"""
val flexibleConfig = Json.decodeFromString<FlexibleConfig>(flexibleJson)
// Success: FlexibleConfig(host="localhost", port=8080, ssl=true)
// StrictConfig throws exception for unknown properties
val strictJson = """{"apiKey":"secret","endpoint":"https://api.example.com","unknown":"field"}"""
// Json.decodeFromString<StrictConfig>(strictJson)
// Would throw SerializationException: Encountered an unknown key 'unknown'
// Selective flexibility in complex structures
@Serializable
data class ApiRequest(
val method: String,
val path: String,
@JsonIgnoreUnknownKeys
val headers: Headers,
val body: RequestBody
)
@Serializable
@JsonIgnoreUnknownKeys
data class Headers(
val contentType: String,
val authorization: String? = null
)
@Serializable
data class RequestBody(
val data: JsonElement
)
val requestJson = """
{
"method": "POST",
"path": "/api/users",
"headers": {
"contentType": "application/json",
"authorization": "Bearer token123",
"x-custom-header": "ignored",
"user-agent": "also ignored"
},
"body": {
"data": {"name": "Alice", "email": "alice@example.com"}
}
}
"""
val request = Json.decodeFromString<ApiRequest>(requestJson)
// Success: Headers ignores unknown properties, but RequestBody must match exactly
// Evolution-friendly data classes
@Serializable
@JsonIgnoreUnknownKeys
data class UserProfile(
val id: String,
val name: String,
val email: String,
val createdAt: Long,
// Future versions might add more fields, but this version ignores them
)
// Can handle JSON from newer API versions
val futureJson = """
{
"id": "user123",
"name": "Alice",
"email": "alice@example.com",
"createdAt": 1634567890123,
"profilePicture": "https://example.com/pic.jpg",
"preferences": {"theme": "dark"},
"newFeature": true
}
"""
val profile = Json.decodeFromString<UserProfile>(futureJson)
// Success: Unknown fields are ignoredUsing multiple annotations together for flexible JSON handling.
Usage Examples:
@Serializable
@JsonIgnoreUnknownKeys
@JsonClassDiscriminator("notification_type")
sealed class Notification {
abstract val id: String
abstract val timestamp: Long
}
@Serializable
data class EmailNotification(
override val id: String,
override val timestamp: Long,
@JsonNames("email_address", "recipient", "to")
val email: String,
@JsonNames("email_subject", "title")
val subject: String,
@JsonNames("email_body", "content", "message")
val body: String
) : Notification()
@Serializable
data class PushNotification(
override val id: String,
override val timestamp: Long,
@JsonNames("device_token", "token", "device_id")
val deviceToken: String,
@JsonNames("push_title", "notification_title")
val title: String,
@JsonNames("push_body", "notification_body", "text")
val body: String
) : Notification()
val notificationJson = Json {
serializersModule = SerializersModule {
polymorphic(Notification::class) {
subclass(EmailNotification::class)
subclass(PushNotification::class)
}
}
}
// Handles various input formats with unknown fields
val legacyEmailJson = """
{
"notification_type": "EmailNotification",
"id": "email-001",
"timestamp": 1634567890123,
"email_address": "user@example.com",
"email_subject": "Welcome!",
"email_body": "Welcome to our service",
"legacy_field": "ignored",
"internal_id": 12345
}
"""
val modernPushJson = """
{
"notification_type": "PushNotification",
"id": "push-001",
"timestamp": 1634567890123,
"device_token": "abc123def456",
"push_title": "New Message",
"push_body": "You have a new message",
"priority": "high",
"ttl": 3600
}
"""
val emailNotification = notificationJson.decodeFromString<Notification>(legacyEmailJson)
val pushNotification = notificationJson.decodeFromString<Notification>(modernPushJson)
// Both deserialize successfully despite unknown fields and alternative namesInstall with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-serialization-json