Kotlin multiplatform JSON serialization library with type-safe, reflectionless approach supporting all platforms
—
Built-in naming strategies for transforming property names during serialization/deserialization.
Function interface for defining custom property name transformations.
/**
* Represents naming strategy - a transformer for serial names in Json format
* Transformed serial names are used for both serialization and deserialization
* Applied globally in Json configuration builder
*/
fun interface JsonNamingStrategy {
/**
* Accepts original serialName and returns transformed name for JSON
* @param descriptor SerialDescriptor of the containing class
* @param elementIndex Index of the element in the descriptor
* @param serialName Original serial name (property name or @SerialName value)
* @return Transformed serial name for JSON encoding/decoding
*/
fun serialNameForJson(descriptor: SerialDescriptor, elementIndex: Int, serialName: String): String
}Pre-built naming strategies for common naming conventions.
/**
* Built-in naming strategies companion object
*/
companion object Builtins {
/**
* Strategy that transforms serial names from camelCase to snake_case
* Words' bounds are defined by uppercase characters
*/
val SnakeCase: JsonNamingStrategy
/**
* Strategy that transforms serial names from camelCase to kebab-case
* Words' bounds are defined by uppercase characters
*/
val KebabCase: JsonNamingStrategy
}Transforms camelCase property names to snake_case format.
Usage Examples:
@Serializable
data class UserAccount(
val userId: Int,
val firstName: String,
val lastName: String,
val emailAddress: String,
val isActive: Boolean,
val createdAt: Long,
val lastLoginTime: Long?
)
// Configure Json with snake_case naming
val snakeCaseJson = Json {
namingStrategy = JsonNamingStrategy.SnakeCase
}
val account = UserAccount(
userId = 123,
firstName = "Alice",
lastName = "Smith",
emailAddress = "alice.smith@example.com",
isActive = true,
createdAt = 1634567890123L,
lastLoginTime = 1634567990456L
)
val jsonString = snakeCaseJson.encodeToString(account)
/* Output:
{
"user_id": 123,
"first_name": "Alice",
"last_name": "Smith",
"email_address": "alice.smith@example.com",
"is_active": true,
"created_at": 1634567890123,
"last_login_time": 1634567990456
}
*/
// Deserialization also uses snake_case
val snakeCaseInput = """
{
"user_id": 456,
"first_name": "Bob",
"last_name": "Johnson",
"email_address": "bob@example.com",
"is_active": false,
"created_at": 1634567800000,
"last_login_time": null
}
"""
val deserializedAccount = snakeCaseJson.decodeFromString<UserAccount>(snakeCaseInput)
// UserAccount(userId=456, firstName="Bob", lastName="Johnson", ...)
// Acronym handling
@Serializable
data class APIConfiguration(
val httpURL: String,
val apiKey: String,
val maxHTTPConnections: Int,
val enableHTTP2: Boolean,
val xmlHttpTimeout: Long
)
val apiConfig = APIConfiguration(
httpURL = "https://api.example.com",
apiKey = "secret123",
maxHTTPConnections = 10,
enableHTTP2 = true,
xmlHttpTimeout = 30000L
)
val apiJson = snakeCaseJson.encodeToString(apiConfig)
/* Output:
{
"http_url": "https://api.example.com",
"api_key": "secret123",
"max_http_connections": 10,
"enable_http2": true,
"xml_http_timeout": 30000
}
*/Transforms camelCase property names to kebab-case format.
Usage Examples:
@Serializable
data class ServerConfig(
val serverPort: Int,
val bindAddress: String,
val maxConnections: Int,
val enableSSL: Boolean,
val sslCertPath: String?,
val requestTimeout: Long,
val keepAliveTimeout: Long
)
// Configure Json with kebab-case naming
val kebabCaseJson = Json {
namingStrategy = JsonNamingStrategy.KebabCase
prettyPrint = true
}
val config = ServerConfig(
serverPort = 8080,
bindAddress = "0.0.0.0",
maxConnections = 1000,
enableSSL = true,
sslCertPath = "/etc/ssl/cert.pem",
requestTimeout = 30000L,
keepAliveTimeout = 60000L
)
val configJson = kebabCaseJson.encodeToString(config)
/* Output:
{
"server-port": 8080,
"bind-address": "0.0.0.0",
"max-connections": 1000,
"enable-ssl": true,
"ssl-cert-path": "/etc/ssl/cert.pem",
"request-timeout": 30000,
"keep-alive-timeout": 60000
}
*/
// Works with deserialization too
val kebabInput = """
{
"server-port": 9090,
"bind-address": "127.0.0.1",
"max-connections": 500,
"enable-ssl": false,
"ssl-cert-path": null,
"request-timeout": 15000,
"keep-alive-timeout": 30000
}
"""
val deserializedConfig = kebabCaseJson.decodeFromString<ServerConfig>(kebabInput)
// CSS-like configuration format
@Serializable
data class StyleConfig(
val backgroundColor: String,
val textColor: String,
val fontSize: Int,
val fontWeight: String,
val borderRadius: Int,
val marginTop: Int,
val paddingLeft: Int
)
val style = StyleConfig(
backgroundColor = "#ffffff",
textColor = "#333333",
fontSize = 14,
fontWeight = "bold",
borderRadius = 4,
marginTop = 10,
paddingLeft = 20
)
val styleJson = kebabCaseJson.encodeToString(style)
/* Output:
{
"background-color": "#ffffff",
"text-color": "#333333",
"font-size": 14,
"font-weight": "bold",
"border-radius": 4,
"margin-top": 10,
"padding-left": 20
}
*/Implementing custom naming transformation logic.
Usage Examples:
// Custom strategy for API field names
val apiNamingStrategy = JsonNamingStrategy { descriptor, elementIndex, serialName ->
when {
// ID fields get special treatment
serialName.endsWith("Id") -> serialName.removeSuffix("Id") + "_id"
serialName.endsWith("ID") -> serialName.removeSuffix("ID") + "_id"
// Boolean fields get is_ prefix
descriptor.getElementDescriptor(elementIndex).kind == PrimitiveKind.BOOLEAN &&
!serialName.startsWith("is") -> "is_$serialName"
// Everything else to snake_case
else -> serialName.fold("") { acc, char ->
when {
char.isUpperCase() && acc.isNotEmpty() -> acc + "_" + char.lowercase()
else -> acc + char.lowercase()
}
}
}
}
@Serializable
data class EntityModel(
val entityId: Long,
val parentID: Long?,
val name: String,
val active: Boolean,
val verified: Boolean,
val createdAt: Long
)
val customJson = Json {
namingStrategy = apiNamingStrategy
prettyPrint = true
}
val entity = EntityModel(
entityId = 123L,
parentID = 456L,
name = "Test Entity",
active = true,
verified = false,
createdAt = System.currentTimeMillis()
)
val entityJson = customJson.encodeToString(entity)
/* Output:
{
"entity_id": 123,
"parent_id": 456,
"name": "Test Entity",
"is_active": true,
"is_verified": false,
"created_at": 1634567890123
}
*/
// Context-aware naming strategy
val contextualNamingStrategy = JsonNamingStrategy { descriptor, elementIndex, serialName ->
val className = descriptor.serialName.substringAfterLast(".")
when (className) {
"DatabaseConfig" -> {
// Database configs use underscore convention
serialName.fold("") { acc, char ->
if (char.isUpperCase() && acc.isNotEmpty()) acc + "_" + char.lowercase()
else acc + char.lowercase()
}
}
"UIComponent" -> {
// UI components use kebab-case
serialName.fold("") { acc, char ->
if (char.isUpperCase() && acc.isNotEmpty()) acc + "-" + char.lowercase()
else acc + char.lowercase()
}
}
else -> serialName // No transformation for other classes
}
}
@Serializable
data class DatabaseConfig(
val hostName: String,
val portNumber: Int,
val databaseName: String,
val connectionTimeout: Long
)
@Serializable
data class UIComponent(
val componentType: String,
val backgroundColor: String,
val borderWidth: Int,
val isVisible: Boolean
)
@Serializable
data class RegularClass(
val someProperty: String,
val anotherProperty: Int
)
val contextualJson = Json { namingStrategy = contextualNamingStrategy }
val dbConfig = DatabaseConfig("localhost", 5432, "myapp", 30000L)
val uiComponent = UIComponent("button", "#ffffff", 1, true)
val regular = RegularClass("value", 42)
// Each class gets appropriate naming convention
val dbJson = contextualJson.encodeToString(dbConfig)
// {"host_name":"localhost","port_number":5432,"database_name":"myapp","connection_timeout":30000}
val uiJson = contextualJson.encodeToString(uiComponent)
// {"component-type":"button","background-color":"#ffffff","border-width":1,"is-visible":true}
val regularJson = contextualJson.encodeToString(regular)
// {"someProperty":"value","anotherProperty":42}Important considerations when using naming strategies.
Usage Examples:
// Naming strategies affect ALL properties
@Serializable
data class MixedCase(
@SerialName("custom_name") // SerialName is also transformed!
val property1: String,
val camelCaseProperty: String
)
val snakeJson = Json { namingStrategy = JsonNamingStrategy.SnakeCase }
val mixed = MixedCase("value1", "value2")
val result = snakeJson.encodeToString(mixed)
// {"custom_name":"value1","camel_case_property":"value2"}
// Note: custom_name becomes custom_name (already snake_case)
// Use JsonNames for alternative deserialization
@Serializable
data class FlexibleModel(
@JsonNames("legacy_name", "old_name") // JsonNames values are NOT transformed
val newName: String
)
val flexibleJson = Json { namingStrategy = JsonNamingStrategy.SnakeCase }
// Can deserialize from multiple formats
val input1 = """{"new_name":"value"}""" // Transformed property name
val input2 = """{"legacy_name":"value"}""" // JsonNames alternative (not transformed)
val input3 = """{"old_name":"value"}""" // Another JsonNames alternative
val result1 = flexibleJson.decodeFromString<FlexibleModel>(input1)
val result2 = flexibleJson.decodeFromString<FlexibleModel>(input2)
val result3 = flexibleJson.decodeFromString<FlexibleModel>(input3)
// All create FlexibleModel(newName="value")
// But encoding uses transformed name
val encoded = flexibleJson.encodeToString(result1)
// {"new_name":"value"}
// Polymorphic serialization is not affected
@Serializable
@JsonClassDiscriminator("messageType") // NOT transformed
sealed class Message {
@Serializable
data class TextMessage(val messageText: String) : Message() // messageText -> message_text
}
val polyJson = Json {
namingStrategy = JsonNamingStrategy.SnakeCase
serializersModule = SerializersModule {
polymorphic(Message::class) {
subclass(Message.TextMessage::class)
}
}
}
val textMsg: Message = Message.TextMessage("Hello")
val polyResult = polyJson.encodeToString(textMsg)
// {"messageType":"TextMessage","message_text":"Hello"}
// Note: discriminator key is NOT transformed, but properties areInstall with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-serialization-json