Json 인스턴스 커스터마이즈하기

Json 인스턴스 커스터마이즈하기

기본 Json 인스턴스는 JSON 명세와 Kotlin 클래스의 선언을 엄격하게 따릅니다.

더 유연한 JSON 기능이나 타입 변환이 필요하다면, Json() 빌더 함수로 커스텀 Json 인스턴스를 만들 수 있어요.

// Creates a Json instance based on the default configuration, allowing special floating-point values
val customJson = Json {
    allowSpecialFloatingPointValues = true
}

// Use the customJson instance with the same syntax as the default one to encode a string
val jsonString = customJson.encodeToString(Data(Double.NaN))
println(jsonString)

이렇게 만든 Json 인스턴스는 **변경 불가(immutable)**이고 **스레드에 안전(thread-safe)**해요. 그래서 최상위 프로퍼티에 저장해 두고 재사용하기에도 안전하죠.

커스텀 Json 인스턴스를 재사용하면 클래스별 정보를 캐시할 수 있어서 성능이 좋아집니다.

또한 기존 Json 인스턴스를 바탕으로 새 인스턴스를 만들어 설정만 바꿀 수도 있어요. 같은 빌더 문법을 사용하면 돼요.

// Creates a new instance based on an existing Json
val lenientJson = Json(customJson) {
    isLenient = true
    prettyPrint = true
}

출처: Customize the Json instance

본문

JSON 구조 커스터마이즈하기

Json 인스턴스가 인코딩·디코딩 시 데이터를 구조화하는 방식을 바꿀 수 있어요. 이를 통해 출력에 어떤 값이 나타날지, 특정 타입이 어떻게 표현될지를 제어할 수 있습니다.

기본값 인코딩하기

기본적으로 JSON 인코더는 기본값(default value)을 가진 프로퍼티를 생략해요. 왜냐하면 디코딩 시 누락된 프로퍼티에 기본값이 자동으로 적용되기 때문이죠. 이 동작은 특히 null 기본값을 가진 nullable 프로퍼티에서 유용한데, 불필요한 null 값을 쓰지 않아도 되기 때문이에요. 자세한 내용은 기본값 프로퍼티의 직렬화 관리하기 절을 참고해 주세요.

이 기본 동작을 바꾸려면 Json 인스턴스의 encodeDefaults 프로퍼티를 true로 설정하면 돼요.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
// Configures a Json instance to encode default values
val format = Json { encodeDefaults = true }

@Serializable
class Project(
    val name: String,
    val language: String = "Kotlin",
    val website: String? = null
)

fun main() {
    val data = Project("kotlinx.serialization")

    // Encodes all the property values, including the default ones
    println(format.encodeToString(data))
    // {"name":"kotlinx.serialization","language":"Kotlin","website":null}
}
//sampleEnd

명시적 null 생략하기

기본적으로 모든 null 값은 JSON 출력에 인코딩돼요. null 값을 생략하려면 Json 인스턴스의 explicitNulls 프로퍼티를 false로 설정하면 됩니다.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
// Configures a Json instance to omit null values during serialization
val format = Json { explicitNulls = false }

@Serializable
data class Project(
    val name: String,
    val language: String,
    val version: String? = "1.2.2",
    val website: String?,
    val description: String? = null
)

fun main() {
    val data = Project("kotlinx.serialization", "Kotlin", null, null, null)
    val json = format.encodeToString(data)

    // Omits version, website, and description properties from the JSON output
    println(json)
    // {"name":"kotlinx.serialization","language":"Kotlin"}

    // Treats missing nullable properties without defaults as null
    // Fills properties that have defaults with their default values
    println(format.decodeFromString<Project>(json))
    // Project(name=kotlinx.serialization, language=Kotlin, version=1.2.2, website=null, description=null)
}
//sampleEnd

explicitNullsfalse로 설정하면 인코딩과 디코딩이 비대칭이 될 수 있어요. 이 예시에서 version 프로퍼티는 인코딩 전에는 null이었지만, 다시 디코딩하면 1.2.2가 되는 걸 볼 수 있죠.

특정 잘못된 입력 값을 누락된 프로퍼티처럼 취급하도록 디코더를 구성하려면 coerceInputValues 프로퍼티를 쓰면 돼요. 자세한 내용은 입력 값 강제 변환하기 절을 참고해 주세요.

구조화된 맵 키 허용하기

JSON 형식은 구조화된 키를 가진 맵을 기본적으로 지원하지 않아요. JSON 객체 키는 항상 문자열이라서 프리미티브나 enum만 표현할 수 있기 때문이죠. 사용자 정의 클래스 키를 가진 맵을 직렬화·역직렬화하려면 allowStructuredMapKeys 프로퍼티를 사용하면 됩니다.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
// Configures a Json instance to encode maps with structured keys
val format = Json { allowStructuredMapKeys = true }

@Serializable
data class Project(val name: String)

fun main() {
    val map = mapOf(
        Project("kotlinx.serialization") to "Serialization",
        Project("kotlinx.coroutines") to "Coroutines"
    )
    // Serializes the map with structured keys as a JSON array:
    // [key1, value1, key2, value2,...]
    println(format.encodeToString(map))
    // [{"name":"kotlinx.serialization"},"Serialization",{"name":"kotlinx.coroutines"},"Coroutines"]
}
//sampleEnd

특수 부동 소수점 값 허용하기

기본적으로 Double.NaN이나 무한대 같은 특수 부동 소수점 값은 JSON에서 지원되지 않아요. JSON 명세가 이를 금지하기 때문이죠.

이 값들의 인코딩·디코딩을 허용하려면 Json 인스턴스의 allowSpecialFloatingPointValues 프로퍼티를 true로 설정하면 됩니다.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
// Configures a Json instance to allow special floating-point values
val format = Json { allowSpecialFloatingPointValues = true }

@Serializable
class Data(
    val value: Double
)

fun main() {
    val data = Data(Double.NaN)
    // Produces a non-standard JSON output used for representing special floating-point values
    println(format.encodeToString(data))
    // {"value":NaN}
}
//sampleEnd

다형성용 클래스 구분자(class discriminator) 지정하기

다형성 데이터를 다룰 때는 classDiscriminator 프로퍼티로 직렬화된 다형성 객체의 타입을 식별하는 키 이름을 지정할 수 있어요. 이를 @SerialName 어노테이션으로 정의한 명시적 직렬 이름과 함께 쓰면 결과 JSON 구조를 완전히 제어할 수 있습니다.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
// Configures a Json instance to use a custom class discriminator
val format = Json { classDiscriminator = "#class" }

@Serializable
sealed class Project {
    abstract val name: String
}

// Specifies a custom serial name for the OwnedProject class
@Serializable
@SerialName("owned")
class OwnedProject(override val name: String, val owner: String) : Project()

// Specifies a custom serial name for the SimpleProject class
@Serializable
@SerialName("simple")
class SimpleProject(override val name: String) : Project()

fun main() {
    val simpleProject: Project = SimpleProject("kotlinx.serialization")
    val ownedProject: Project = OwnedProject("kotlinx.coroutines", "kotlin")

    // Serializes SimpleProject with #class: "simple"
    println(format.encodeToString(simpleProject))
    // {"#class":"simple","name":"kotlinx.serialization"}

    // Serializes OwnedProject with #class: "owned"
    println(format.encodeToString(ownedProject))
    // {"#class":"owned","name":"kotlinx.coroutines","owner":"kotlin"}
}
//sampleEnd

Json 인스턴스의 classDiscriminator 프로퍼티가 모든 다형성 타입에 단일 구분자 키를 지정해 주는 반면, Experimental @JsonClassDiscriminator 어노테이션은 더 유연해요. 기본 클래스에 직접 커스텀 구분자를 정의할 수 있고, 그 구분자가 모든 하위 클래스에 자동으로 상속되죠.

상속 가능한 직렬 어노테이션에 대해 더 알고 싶다면 @InheritableSerialInfo를 참고해 주세요.

예시를 볼게요.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
// The @JsonClassDiscriminator annotation is inheritable, so all subclasses of Base will have the same discriminator
@Serializable
@OptIn(ExperimentalSerializationApi::class)
@JsonClassDiscriminator("message_type")
sealed class Base

// Inherits the discriminator from Base
@Serializable
sealed class ErrorClass: Base()

// Defines a class that combines a message and an optional error
@Serializable
data class Message(val message: Base, val error: ErrorClass?)

@Serializable
@SerialName("my.app.BaseMessage")
data class BaseMessage(val message: String) : Base()

@Serializable
@SerialName("my.app.GenericError")
data class GenericError(@SerialName("error_code") val errorCode: Int) : ErrorClass()

val format = Json { classDiscriminator = "#class" }

fun main() {
    val data = Message(BaseMessage("not found"), GenericError(404))
    // Uses the discriminator from Base for all subclasses
    println(format.encodeToString(data))
    // {"message":{"message_type":"my.app.BaseMessage","message":"not found"},"error":{"message_type":"my.app.GenericError","error_code":404}}
}
//sampleEnd

sealed 기본 클래스의 하위 클래스에서는 서로 다른 클래스 구분자를 지정할 수 없어요. 서로 겹치지 않는 별개의 하위 클래스를 가진 계층만 자체 구분자를 정의할 수 있습니다.

둘 다 구분자를 지정하면 @JsonClassDiscriminatorJson 구성의 구분자보다 우선해요.

클래스 구분자 출력 모드 설정하기

JsonBuilder.classDiscriminatorMode 프로퍼티로 JSON 출력에 클래스 구분자를 추가하는 방식을 제어할 수 있어요. 기본적으로 구분자는 다형성 타입에만 추가되는데, 다형성 클래스 계층을 다룰 때 유용하죠.

이 동작을 조정하려면 ClassDiscriminatorMode 프로퍼티를 다음 옵션 중 하나로 설정하면 됩니다.

  • POLYMORPHIC: (기본값) 다형성 타입에만 클래스 구분자를 추가해요.
  • ALL_JSON_OBJECTS: 가능한 모든 JSON 객체에 클래스 구분자를 추가해요.
  • NONE: 클래스 구분자를 완전히 생략해요.

다음은 ClassDiscriminatorMode 프로퍼티를 NONE으로 설정한 예시예요.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
// Configures a Json instance to omit the class discriminator from the output
val format = Json { classDiscriminatorMode = ClassDiscriminatorMode.NONE }

@Serializable
sealed class Project {
    abstract val name: String
}

@Serializable
class OwnedProject(override val name: String, val owner: String) : Project()

fun main() {
    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
    // Serializes without a discriminator
    println(format.encodeToString(data))
    // {"name":"kotlinx.coroutines","owner":"kotlin"}
}
//sampleEnd

구분자가 없으면 Kotlin 직렬화 라이브러리는 출력을 다시 적절한 타입으로 역직렬화할 수 없어요.

예쁘게 출력하기(pretty printing)

기본적으로 Json은 컴팩트한 한 줄 출력을 만들어요.

가독성을 위해 출력에 들여쓰기와 줄바꿈을 추가하려면, Json 인스턴스의 prettyPrint 프로퍼티를 true로 설정하면 됩니다.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
// Creates a custom Json format
val format = Json { prettyPrint = true }

@Serializable
data class Project(val name: String, val language: String)

fun main() {
    val data = Project("kotlinx.serialization", "Kotlin")

    // Prints the JSON output with line breaks and indentations
    println(format.encodeToString(data))
}
//sampleEnd

이 예시는 다음과 같은 결과를 출력해요.

{
    "name": "kotlinx.serialization",
    "language": "Kotlin"
}

prettyPrintIndent 옵션으로 예쁘게 출력된 JSON의 들여쓰기를 커스터마이즈할 수 있어요.

예를 들어 기본 4칸 공백을 \t\n 같은 다른 허용 공백 문자로 바꿀 수 있죠.

JSON 역직렬화 커스터마이즈하기

Kotlin Json 파서는 JSON 데이터의 파싱·역직렬화를 커스터마이즈할 수 있는 여러 설정을 제공합니다.

알 수 없는 키 무시하기

서드파티 서비스나 다른 동적 소스의 JSON 데이터를 다룰 때는 JSON 객체에 새 프로퍼티가 시간이 지나며 추가되곤 해요.

기본적으로 알 수 없는 키(JSON 입력의 프로퍼티 이름)는 역직렬화 중 오류를 일으켜요. 이를 막으려면 Json 인스턴스의 ignoreUnknownKeys 프로퍼티를 true로 설정하면 됩니다.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
// Creates a Json instance to ignore unknown keys
val format = Json { ignoreUnknownKeys = true }

@Serializable
data class Project(val name: String)

fun main() {
    val data = format.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":"Kotlin"}
    """)
    // The language key is ignored because it's not in the Project class
    println(data)
    // Project(name=kotlinx.serialization)
}
//sampleEnd

특정 클래스에 대해서만 알 수 없는 키 무시하기

모든 클래스에 대해 ignoreUnknownKeys를 켜는 대신, @JsonIgnoreUnknownKeys 어노테이션을 사용해 특정 클래스에서만 알 수 없는 키를 무시할 수 있어요. 이렇게 하면 기본적으로는 엄격한 역직렬화를 유지하면서, 필요한 곳에서만 관대하게 동작하게 만들 수 있죠.

@JsonIgnoreUnknownKeys 어노테이션은 Experimental이에요. 사용하려면 @OptIn(ExperimentalSerializationApi::class) 어노테이션이나 -opt-in=kotlinx.serialization.ExperimentalSerializationApi 컴파일러 옵션으로 옵트인해야 합니다.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
@OptIn(ExperimentalSerializationApi::class)
@Serializable
// Unknown properties in Outer are ignored during deserialization
@JsonIgnoreUnknownKeys
data class Outer(val a: Int, val inner: Inner)

@Serializable
data class Inner(val x: String)

fun main() {
    val outer = Json.decodeFromString<Outer>(
        """{"a":1,"inner":{"x":"value"},"unknownKey":42}"""
    )
    println(outer)
    // Outer(a=1, inner=Inner(x=value))

    // Throws an exception
    // unknownKey inside inner is NOT ignored because Inner is not annotated
    println(
        Json.decodeFromString<Outer>(
            """{"a":1,"inner":{"x":"value","unknownKey":"unexpected"}}"""
        )
    )
}
//sampleEnd

이 예시에서 Inner@JsonIgnoreUnknownKeys로 표시되지 않았으므로 알 수 없는 키에 대해 SerializationException을 던져요.

입력 값 강제 변환하기

서드파티 서비스나 다른 동적 소스의 JSON 데이터를 다룰 때는 형식이 진화할 수 있어요. 실제 값이 예상 타입과 맞지 않으면 디코딩 중 예외가 발생할 수 있죠.

기본 Json 구현은 입력 타입에 엄격해요. 이 제약을 완화하려면 Json 인스턴스의 coerceInputValues 프로퍼티를 true로 설정하면 됩니다.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
val format = Json { coerceInputValues = true }

@Serializable
data class Project(val name: String, val language: String = "Kotlin")

fun main() {
    val data = format.decodeFromString<Project>("""
        {"name":"kotlinx.serialization","language":null}
    """)

    // Coerces the invalid null value for language to its default value
    println(data)
    // Project(name=kotlinx.serialization, language=Kotlin)
}
//sampleEnd

coerceInputValues 프로퍼티는 디코딩에만 영향을 줘요. 특정 잘못된 입력 값을 해당 프로퍼티가 누락된 것처럼 취급하죠. 현재는 다음에 적용됩니다.

  • non-nullable 타입에 대한 null 입력
  • enum에 대한 알 수 없는 값

이 목록은 향후 버전에서 확장될 수 있어요. 이 프로퍼티를 켠 Json 인스턴스는 잘못된 값을 기본값이나 null로 대체하며 더 관대해질 수 있죠.

값이 누락되면, 기본값이 존재할 경우 기본값으로 대체됩니다.

enum의 경우 값은 다음 조건에서만 null로 대체돼요.

  • 기본값이 정의되지 않았고,
  • explicitNulls 프로퍼티가 false이고,
  • 프로퍼티가 nullable일 때.

coerceInputValuesexplicitNulls 프로퍼티와 조합해 잘못된 enum 값을 처리할 수 있어요.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
enum class Color { BLACK, WHITE }

@Serializable
data class Brush(val foreground: Color = Color.BLACK, val background: Color?)

val json = Json { 
  coerceInputValues = true
  explicitNulls = false
}

fun main() {

    // Coerces the unknown foreground value to its default and background to null
    val brush = json.decodeFromString<Brush>("""{"foreground":"pink", "background":"purple"}""")
    println(brush)
    // Brush(foreground=BLACK, background=null)
}
//sampleEnd

후행 쉼표 허용하기

JSON 입력에 후행 쉼표(trailing comma)를 허용하려면 allowTrailingComma 프로퍼티를 true로 설정하면 됩니다.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
// Allows trailing commas in JSON objects and arrays
val format = Json { allowTrailingComma = true }

fun main() {
    val numbers = format.decodeFromString<List<Int>>(
        """
            [1, 2, 3,]
        """
    )
    println(numbers)
    // [1, 2, 3]
}
//sampleEnd

JSON에서 주석 허용하기

allowComments 프로퍼티로 JSON 입력에서 주석을 허용할 수 있어요. 이 프로퍼티를 켜면 파서가 입력에서 다음 주석 형태를 받아들입니다.

  • 새 줄 \n에서 끝나는 // 한 줄 주석
  • /* */ 블록 주석

예시를 볼게요.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
// Allows comments in JSON input
val format = Json { allowComments = true }

fun main() {
    val numbers = format.decodeFromString<List<Int>>(
        """
            [
                // first element
                1,
                /* second element */
                2
            ]
        """
    )
    println(numbers)
    // [1, 2]
}
//sampleEnd

관대한 파싱(Lenient parsing)

기본적으로 Json 파서는 엄격한 JSON 규칙을 적용해 RFC-8259 명세를 준수해요. 이 명세는 키와 문자열 리터럴이 따옴표로 감싸져야 한다고 요구하죠.

이 제약을 완화하려면 Json 인스턴스의 isLenient 프로퍼티를 true로 설정하면 됩니다.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
val format = Json { isLenient = true }

enum class Status { SUPPORTED }

@Serializable
data class Project(val name: String, val status: Status, val votes: Int)

fun main() {
    // Decodes a JSON string with lenient parsing
    // Lenient parsing allows unquoted keys, string, and enum values
    val data = format.decodeFromString<Project>("""
        {
            name   : kotlinx.serialization,
            status : SUPPORTED,
            votes  : "9000"
        }
    """)
    println(data)
    // Project(name=kotlinx.serialization, status=SUPPORTED, votes=9000)
}
//sampleEnd

JSON과 Kotlin 간 이름 매핑 커스터마이즈하기

일부 JSON 데이터는 Kotlin의 명명 규칙이나 예상 형식과 정확히 맞지 않을 수 있어요. 이런 어려움을 다루기 위해 Kotlin 직렬화 라이브러리는 명명 불일치를 관리하고, 하나의 Kotlin 프로퍼티에 여러 JSON 프로퍼티 이름을 처리하며, 직렬화 데이터 전반에 일관된 명명 전략을 적용하는 여러 도구를 제공합니다.

하나의 Kotlin 프로퍼티에 대체 JSON 프로퍼티 이름 허용하기

JSON 프로퍼티 이름이 스키마 버전 사이에 바뀌는 경우, @SerialName 어노테이션으로 JSON 프로퍼티를 이름을 바꿀 수 있어요.

하지만 이렇게 하면 이전 프로퍼티 이름을 사용하는 데이터는 디코딩할 수 없게 됩니다. 한 프로퍼티에 대해 대체 JSON 이름을 허용하려면 @JsonNames 어노테이션을 사용하면 됩니다.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
@Serializable
// Maps both name and title JSON properties to the name property
data class Project(@JsonNames("title") val name: String)

fun main() {
    val project = Json.decodeFromString<Project>("""{"name":"kotlinx.serialization"}""")
    println(project)
    // Project(name=kotlinx.serialization)

    val oldProject = Json.decodeFromString<Project>("""{"title":"kotlinx.coroutines"}""")
    // Both name and title Json properties correspond to name property
    println(oldProject)
    // Project(name=kotlinx.coroutines)
}
//sampleEnd

JsonBuilderuseAlternativeNames 프로퍼티가 @JsonNames 어노테이션을 켜 줘요. 이 프로퍼티는 기본적으로 true이며, Json이 한 프로퍼티에 대해 여러 이름을 인식하고 디코딩할 수 있게 합니다.

@JsonNames를 쓰지 않으면서, 특히 ignoreUnknownKeys로 많은 알 수 없는 프로퍼티를 건너뛰는 경우 성능을 올리고 싶다면 이 프로퍼티를 false로 설정할 수 있어요.

enum을 대소문자 구분 없이 디코딩하기

Kotlin의 명명 규칙은 enum 값을 대문자나 어퍼 카멜 케이스로 쓰는 것을 권장해요. 기본적으로 Json은 디코딩 시 Kotlin enum 상수의 정확한 이름을 사용합니다.

하지만 외부 소스의 JSON 데이터는 소문자나 혼합 대소문자 이름을 쓸 수 있어요. 이런 경우 JsonBuilder.decodeEnumsCaseInsensitive 프로퍼티로 Json 인스턴스를 구성해 enum 값을 대소문자 구분 없이 디코딩할 수 있습니다.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
// Configures a Json instance to decode enum values in a case-insensitive way
val format = Json { decodeEnumsCaseInsensitive = true }

enum class Cases { VALUE_A, @JsonNames("Alternative") VALUE_B }

@Serializable
data class CasesList(val cases: List<Cases>)

fun main() {
    // Decodes enum values regardless of their case, including alternative names
    println(format.decodeFromString<CasesList>("""{"cases":["value_A", "alternative"]}""")) 
    // CasesList(cases=[VALUE_A, VALUE_B])
}
//sampleEnd

이 프로퍼티는 직렬 이름@JsonNames 어노테이션으로 지정한 대체 이름 모두에 적용되어 모든 값이 성공적으로 디코딩되게 해요. 이 프로퍼티는 인코딩에는 영향을 주지 않습니다.

전역 명명 전략 적용하기

JSON 입력의 프로퍼티 이름이 Kotlin의 것과 다를 때, @SerialName 어노테이션으로 각 프로퍼티의 이름을 명시적으로 지정할 수 있어요. 하지만 다른 프레임워크나 레거시 코드베이스에서 이전할 때는 모든 직렬 이름을 같은 방식으로 변환해야 할 수도 있어요.

이런 시나리오에서는 Json 인스턴스의 JsonBuilder.namingStrategy 프로퍼티로 전역 명명 전략을 지정할 수 있어요. Kotlin 직렬화 라이브러리는 JsonNamingStrategy.SnakeCase 같은 내장 전략을 제공합니다.

// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*

//sampleStart
@Serializable
data class Project(val projectName: String, val projectOwner: String)

// Configures a Json instance to apply SnakeCase naming strategy
val format = Json { namingStrategy = JsonNamingStrategy.SnakeCase }

fun main() {
    val project = format.decodeFromString<Project>("""{"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}""")
    // Serializes and deserializes as if all serial names are transformed from camel case to snake case
    println(format.encodeToString(project.copy(projectName = "kotlinx.serialization")))
    // {"project_name":"kotlinx.serialization","project_owner":"Kotlin"}
}
//sampleEnd

JsonNamingStrategy로 전역 명명 전략을 사용할 때는 다음을 염두에 두세요.

  • 변환이 모든 프로퍼티에 적용돼요. 직렬 이름이 프로퍼티 이름에서 유래했든 @SerialName 어노테이션으로 명시적으로 정의했든 상관없죠. 직렬 이름을 지정한다고 프로퍼티를 변환에서 제외할 수는 없어요. 직렬화 중 특정 이름을 그대로 유지하려면 @JsonNames 어노테이션을 대신 사용하세요.
  • 변환된 이름이 다른 변환된 프로퍼티 이름이나 @JsonNames 어노테이션이 지정한 대체 이름과 충돌하면, 역직렬화가 예외와 함께 실패해요.
  • 전역 명명 전략은 암시적이에요. 그래서 클래스 정의만 보고 직렬화된 이름을 파악하기 어렵죠. 이는 IDE의 Find Usages, Rename이나 grep 같은 도구를 이용한 전체 텍스트 검색 같은 작업을 어렵게 만들고, 버그와 유지보수 비용의 위험을 높일 수 있어요.

이런 요소들을 고려하면, 애플리케이션에 전역 명명 전략을 도입하기 전에 장단점을 신중히 따져 봐야 합니다.

더 알아보기

  • 파싱·직렬화 전에 JSON 데이터를 조작하고 다루려면 고급 JSON 요소 처리를 살펴볼 수 있어요.
  • 직렬화·역직렬화 중 JSON을 변환하는 방법은 JSON 변환하기에서 더 제어할 수 있어요.