#はじめに
Kotlin Serialization ガイドの目次に基づいて「Kotlin Serialization guide」を訳しています。
なお、翻訳にはDeepLの力を99%借りています。
もし、一緒に「Kotlin Serialization ガイド」の翻訳をして下さる方がいらっしゃいましたら、コメント欄などから連絡をください。
##第3章 シリアライザ <原文>
Latest commit 728e220 on 24 Nov 2020版
Kotlin Serialization Guideの第3章です。この章では、シリアライザについて詳しく見ていき、カスタムシリアライザの書き方を見ていきます。
目次
##シリアライザの紹介
JSON のようなフォーマットでは、オブジェクトの特定の出力バイトへの encoding を制御しますが、オブジェクトがどのように構成されたプロパティに分解されるかは serializer によって制御されます。これまでは、シリアライゼーション クラスで説明したように、@Serializable
アノテーションを使用して自動生成されたシリアライザを使用したり、組み込みクラスで示した組み込みのシリアライザを使用したりしてきました。
興味を与える例として、rgb
バイトを格納した整数値を持つ以下の Color
クラスを見てみよう。
@Serializable
class Color(val rgb: Int)
fun main() {
val green = Color(0x00ff00)
println(Json.encodeToString(green))
}
完全なコードはこちらから取得できます。
デフォルトでは、このクラスは rgb
プロパティを JSON にシリアライズします。
{"rgb":65280}
###プラグイン生成シリアライザ
前の例の Color
クラスのように @Serializable
アノテーションを付けたクラスはすべて、Kotlin Serialization コンパイラプラグインによって自動的に生成される KSerializer インターフェイスのインスタンスを取得します。
シリアライズされたクラスの構造を記述した descriptor プロパティを調べることができます。これについての詳細は次のセクションで説明します。
fun main() {
val colorSerializer: KSerializer<Color> = Color.serializer()
println(colorSerializer.descriptor)
}
完全なコードはこちらから取得できます。
Color(rgb: kotlin.Int)
このシリアライザは、Color
クラス自体がシリアライズされたとき、または他のクラスのプロパティとして使用されたときに、Kotlin Serialization フレームワークによって自動的に取得され、使用されます。
シリアライズ可能なクラスのコンパニオンオブジェクトに対して独自の関数
serializer()
を定義することはできません。
###プラグイン生成ジェネリックシリアライザ
ジェネリッククラス で示した Box
クラスのような汎用クラスの場合、自動的に生成される .serializer()
関数は対応するクラスの型パラメータの数だけパラメータを受け付ける。これらのパラメータはKSerializer型であるため、ジェネリッククラスのシリアライザのインスタンスを作成する際には、実際の型引数のシリアライザを指定しなければならない。
@Serializable
@SerialName("Box")
class Box<T>(val contents: T)
fun main() {
val boxedColorSerializer = Box.serializer(Color.serializer())
println(boxedColorSerializer.descriptor)
}
完全なコードはこちらから取得できます。
ご覧の通り、具体的な Box<Color>
をシリアライズするためにシリアライザがインスタンス化されています。
Box(contents: Color)
###組み込みプリミティブシリアライザ
プリミティブ組み込みクラスのシリアライザは、.serializer()
拡張子を使って取得することができます。
fun main() {
val intSerializer: KSerializer<Int> = Int.serializer()
println(intSerializer.descriptor)
}
フルコードはこちらから取得することができます。
###コレクションシリアライザを構築
ビルトインコレクションシリアライザは、必要に応じて、対応する関数ListSerializer()、SetSerializer()、MapSerializer()などを使って明示的に構築しなければなりません。これらのクラスは汎用的なので、シリアライザをインスタンス化するためには、対応する数の型パラメータに対応するシリアライザを提供しなければなりません。例えば、以下のように List<String>
のシリアライザを生成することができます。
fun main() {
val stringListSerializer: KSerializer<List<String>> = ListSerializer(String.serializer())
println(stringListSerializer.descriptor)
}
フルコードはこちらから取得することができます。
###トップレベルのシリアライザ機能を使用
疑わしい場合は、トップレベルの汎用 serializer<T>()
関数を使って、ソースコード中の任意のKotlin型のシリアライザを取得することができます。
@Serializable
@SerialName("Color")
class Color(val rgb: Int)
fun main() {
val stringToColorMapSerializer: KSerializer<Map<String, Color>> = serializer()
println(stringToColorMapSerializer.descriptor)
}
フルコードはこちらから取得することができます。
##カスタムシリアライザ
プラグイン生成のシリアライザは便利だが、Color
のようなクラスのために必要なJSONを生成できないかもしれない。代替案を検討してみましょう。
###プリミティブシリアライザ
緑の色を "00ff00"
で表現した16進文字列として Color
クラスをシリアライズしたい。これを実現するために、Color
クラスのための KSerializer インターフェースを実装したオブジェクトを書く。
object ColorAsStringSerializer : KSerializer<Color> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Color) {
val string = value.rgb.toString(16).padStart(6, '0')
encoder.encodeString(string)
}
override fun deserialize(decoder: Decoder): Color {
val string = decoder.decodeString()
return Color(string.toInt(16))
}
}
シリアライザには3つの必要なピースがあります。
-
serialize 関数は、SerializationStrategy を実装しています。 Encoder のインスタンスとシリアライズする値を受け取ります。 この関数は
Encoder
のencodeXxx
関数を使用して値をプリミティブのシーケンスとして表現します。シリアライズでサポートされるプリミティブの型ごとにencodeXxx
がある。 この例では、encodeStringを使用しています。 -
deserialize 関数は、DeserializationStrategy を実装しています。 これは Decoder のインスタンスを受け取り、デシリアライズされた値を返します。これは
Decoder
のdecodeXxx
関数を利用しますが、これはEncoder
の対応する関数をミラーリングしたものです。 この例では、decodeStringが使われています。 -
descriptorプロパティは、
encodeXxx
とdecodeXxx
関数がどのようなエンコーディング/デコーディングメソッドを呼び出すかをフォーマットの実装が事前に知っているように、正確に何をするのかを忠実に説明しなければなりません。いくつかのフォーマットでは、シリアライズされたデータのスキーマを生成するためにこれを使うこともあります。プリミティブシリアライズの場合、PrimitiveSerialDescriptor関数はシリアライズされる型のユニークな名前を指定して使用しなければならない。PrimitiveKind は、実装で使用される特定のencodeXxx
/decodeXxx
メソッドを記述する。
このように、
descriptor
がエンコード/デコード方法に対応していない場合、結果として得られるコードの振る舞いは不特定であり、将来の更新で任意に変更される可能性があります。
次のステップはシリアライザをクラスにバインドすることです。これは、with
プロパティ値を追加することで、@Serializable
アノテーションで行います。
@Serializable(with = ColorAsStringSerializer::class)
class Color(val rgb: Int)
これで、先ほどと同じように Color
クラスをシリアライズすることができるようになりました。
fun main() {
val green = Color(0x00ff00)
println(Json.encodeToString(green))
}
完全なコードはこちらから取得できます。
シリアル表現を16進数の文字列として取得します。
"00ff00"
デシリアライズも deserialize
メソッドを実装しているので簡単である。
@Serializable(with = ColorAsStringSerializer::class)
class Color(val rgb: Int)
fun main() {
val color = Json.decodeFromString<Color>("\"00ff00\"")
println(color.rgb) // prints 65280
}
フルコードはこちらから取得することができます。
また、Color
プロパティを持つ別のクラスをシリアライズまたはデシリアライズしても動作します。
@Serializable(with = ColorAsStringSerializer::class)
data class Color(val rgb: Int)
@Serializable
data class Settings(val background: Color, val foreground: Color)
fun main() {
val data = Settings(Color(0xffffff), Color(0))
val string = Json.encodeToString(data)
println(string)
require(Json.decodeFromString<Settings>(string) == data)
}
フルコードはこちらから取得することができます。
色`プロパティはいずれも文字列としてシリアライズされる。
{"background":"ffffff","foreground":"000000"}
###サロゲート経由のコンポジットシリアライザ
さて、私たちの課題は Color
をシリアライズして、JSON で r
, g
, b
の 3 つのプロパティを持つクラスであるかのように JSON で表現し、JSON がオブジェクトとしてエンコードするようにすることです。これを実現する最も簡単な方法は、シリアル化に使用する Color
を模倣した surrogate クラスを定義することです。また、このサロゲートクラスの SerialName に Color
を設定します。これで、この名前を使うフォーマットがあれば、サロゲートは Color
クラスであるかのように見える。サロゲートクラスは private
にすることができ、その init
ブロックでクラスのシリアル表現に対するすべての制約を強制することができる。
@Serializable
@SerialName("Color")
private class ColorSurrogate(val r: Int, val g: Int, val b: Int) {
init {
require(r in 0..255 && g in 0..255 && b in 0..255)
}
}
クラス名を使用する場所の例は、ポリモーフィズムの章のカスタムサブクラスシリアル名の項に記載されています。
関数 ColorSurrogate.serializer()
を使って、プラグインが生成したシリアライザを取得することができます。
オリジナルの Color
クラスのための KSerializer の実装は、Color
と ColorSurrogate
の変換を行う。しかし、実際のシリアライズロジックは ColorSurrogate.serializer()
に委譲して encodeSerializableValue と decodeSerializableValue を使用し、サロゲート用に自動生成された SerialDescriptor を完全に再利用します。
object ColorSerializer : KSerializer<Color> {
override val descriptor: SerialDescriptor = ColorSurrogate.serializer().descriptor
override fun serialize(encoder: Encoder, value: Color) {
val surrogate = ColorSurrogate((value.rgb shr 16) and 0xff, (value.rgb shr 8) and 0xff, value.rgb and 0xff)
encoder.encodeSerializableValue(ColorSurrogate.serializer(), surrogate)
}
override fun deserialize(decoder: Decoder): Color {
val surrogate = decoder.decodeSerializableValue(ColorSurrogate.serializer())
return Color((surrogate.r shl 16) or (surrogate.g shl 8) or surrogate.b)
}
}
ColorSerializer
シリアライザを Color
クラスにバインドする。
@Serializable(with = ColorSerializer::class)
class Color(val rgb: Int)
これで、Color
クラスのシリアライズの結果を楽しむことができる。
フルコードはこちらから取得することができます。
{"r":0,"g":255,"b":0}
###手書き複合シリアライザ
サロゲートソリューションが合わない場合もあります。おそらく、追加の割り当てによるパフォーマンスへの影響を避けたい場合や、結果として得られるシリアル表現のために、設定可能で動的なプロパティのセットが欲しい場合があるでしょう。このような場合は、生成されたシリアライザの動作を模倣するクラスシリアライザを手動で記述する必要があります。
object ColorAsObjectSerializer : KSerializer<Color> {
少しずつ紹介していきましょう。まず、buildClassSerialDescriptorビルダーを使って記述子を定義します。ビルダーDSLのelement関数は、対応するフィールドのシリアライザをタイプ別に自動的に取得します。要素の順序は重要です。これらの要素はゼロから始まるインデックスが付けられます。
override val descriptor: SerialDescriptor =
buildClassSerialDescriptor("Color") {
element<Int>("r")
element<Int>("g")
element<Int>("b")
}
ここでは「要素」は一般的な用語です。記述子の要素が何であるかは,そのSerialKindに依存します。クラス記述子の要素はそのプロパティ、enum記述子の要素はそのケースなどです。
次に、ブロック内の CompositeEncoder へのアクセスを提供する encodeStructure DSLを用いて serialize
関数を書く。Encoder と CompositeEncoder の違いは、後者には前者の encodeXxx
関数に対応する encodeXxxElement
関数があることである。これらの関数は記述子と同じ順番で呼ばれなければなりません。
override fun serialize(encoder: Encoder, value: Color) =
encoder.encodeStructure(descriptor) {
encodeIntElement(descriptor, 0, (value.rgb shr 16) and 0xff)
encodeIntElement(descriptor, 1, (value.rgb shr 8) and 0xff)
encodeIntElement(descriptor, 2, value.rgb and 0xff)
}
最も複雑なコードは deserialize
関数である。これはJSONのように、任意の順序でプロパティをデコードできるフォーマットをサポートしなければなりません。これは、CompositeDecoder へのアクセスを得るための decodeStructure の呼び出しから始まる。その中で、次の要素のインデックスをデコードするために decodeElementIndex を繰り返し呼び出すループを書き、次に、この例では decodeIntElement を使って対応する要素をデコードし、最後にCompositeDecoder.DECODE_DONE
が発生した。その中で、decodeElementIndexを繰り返し呼び出して次の要素のインデックスをデコードし、対応する要素をdecodeIntElementを使ってデコードし、最後に CompositeDecoder.DECODE_DONE
に遭遇したときにループを終了させるループを書いています。
override fun deserialize(decoder: Decoder): Color =
decoder.decodeStructure(descriptor) {
var r = -1
var g = -1
var b = -1
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> r = decodeIntElement(descriptor, 0)
1 -> g = decodeIntElement(descriptor, 1)
2 -> b = decodeIntElement(descriptor, 2)
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}
require(r in 0..255 && g in 0..255 && b in 0..255)
Color((r shl 16) or (g shl 8) or b)
}
ここで、できあがったシリアライザを Color
クラスにバインドし、そのシリアライゼーション/デシリアライゼーションをテストする。
@Serializable(with = ColorAsObjectSerializer::class)
data class Color(val rgb: Int)
fun main() {
val color = Color(0x00ff00)
val string = Json.encodeToString(color)
println(string)
require(Json.decodeFromString<Color>(string) == color)
}
フルコードはこちらから取得することができます。
以前と同様に、Color
クラスは 3 つのキーを持つ JSON オブジェクトとして表現されます。
{"r":0,"g":255,"b":0}
###逐次復号化プロトコル(実験)
前節の deserialize
関数の実装は、どのような形式でも動作する。しかし、いくつかのフォーマットでは、すべての複雑なデータを常に順番に保存しているものと、時々しか保存していないものがあります(JSONは常にコレクションを順番に保存しています)。これらのフォーマットでは、ループ内で decodeElementIndex
を呼び出す複雑なプロトコルは不要で、CompositeDecoder.decodeSequentially 関数が true
を返す場合は、より高速な実装を使うことができます。 プラグインが生成するシリアライザは、実際には以下のコードと概念的に似ています。
override fun deserialize(decoder: Decoder): Color =
decoder.decodeStructure(descriptor) {
var r = -1
var g = -1
var b = -1
if (decodeSequentially()) { // sequential decoding protocol
r = decodeIntElement(descriptor, 0)
g = decodeIntElement(descriptor, 1)
b = decodeIntElement(descriptor, 2)
} else while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> r = decodeIntElement(descriptor, 0)
1 -> g = decodeIntElement(descriptor, 1)
2 -> b = decodeIntElement(descriptor, 2)
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}
require(r in 0..255 && g in 0..255 && b in 0..255)
Color((r shl 16) or (g shl 8) or b)
}
フルコードはこちらから取得することができます。
###サードパーティクラスのシリアライズ
時々、アプリケーションはシリアル化できない外部の型を使用しなければならないことがあります。例として、java.util.Dateを使ってみましょう。先ほどと同様に、このクラスのためにKSerializerの実装を書くことから始めます。私たちの目標は、プリミティブシリアライザのセクションのアプローチに従って、Date
をLongのミリ秒数でシリアライズすることです。
以下の節では、どのような種類の
Date
シリアライザでも動作します。例えば、Date
をオブジェクトとしてシリアライズしたい場合は、サロゲートを使った複合シリアライザのセクションのアプローチを使います。サードパーティの Kotlin クラスをシリアライズする必要がある場合は、他のkotlinクラスのための外部シリアライザの導出(実験)も参照してください。
object DateAsLongSerializer : KSerializer<Date> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG)
override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
}
Date のソースコードを制御できないため、
DateAsLongSerializerシリアライザを
Date` クラスに @Serializable アノテーションでバインドすることはできません。これを回避する方法はいくつかあります。
###シリアライザを手動で渡す
すべての encodeToXxx
および decodeFromXxx
関数は、最初のシリアライザパラメータを持つオーバーロードを持っています。シリアライズ可能ではないクラス、例えば Date
のような最上位クラスがシリアライズされる場合は、これらを使うことができます。
fun main() {
val kotlin10ReleaseDate = SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")
println(Json.encodeToString(DateAsLongSerializer, kotlin10ReleaseDate))
}
フルコードはこちらから取得することができます。
1.45549E+12
###プロパティにシリアライザを指定
Date`のようなシリアライズ可能ではないクラスのプロパティがシリアライズ可能なクラスの一部としてシリアライズされている場合、そのシリアライザを提供しなければなりません。これは、プロパティの @Serializable アノテーションを使用して行います。
@Serializable
class ProgrammingLanguage(
val name: String,
@Serializable(with = DateAsLongSerializer::class)
val stableReleaseDate: Date
)
fun main() {
val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
println(Json.encodeToString(data))
}
フルコードはこちらから取得することができます。
stableReleaseDate
プロパティは、指定したシリアライゼーションの方針(the serialization strategy)でシリアライズされます。
{"name":"Kotlin","stableReleaseDate":1455494400000}
###ファイルにシリアライザを指定
Date` のような特定の型のシリアライザは、ファイルの先頭にあるファイルレベルのUseSerializersアノテーションでソースコードファイル全体に対して指定することができます。
@file:UseSerializers(DateAsLongSerializer::class)
これで、追加のアノテーションなしに Date
プロパティをシリアライズ可能なクラスで使用できるようになりました。
@Serializable
class ProgrammingLanguage(val name: String, val stableReleaseDate: Date)
fun main() {
val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
println(Json.encodeToString(data))
}
フルコードはこちらから取得することができま。
{"name":"Kotlin","stableReleaseDate":1455494400000}
###ジェネリックタイプ用のカスタムシリアライザ
以下に汎用の Box<T>
クラスの例を見てみよう。これは、カスタムのシリアライズ戦略を持つ予定なので、@Serializable(with = BoxSerializer::class)
とマークされている。
@Serializable(with = BoxSerializer::class)
data class Box<T>(val contents: T)
この章の Color
型の例で見たように、通常の型に対する KSerializer の実装は object
として書かれている。一般的なクラスのシリアライザは、その一般的なパラメータのためのシリアライザを使ってインスタンス化される。これについてはプラグインで生成された汎用シリアライザのセクションで見た。ジェネリッククラス用のカスタムシリアライザは、その型が持つジェネリックパラメータの数だけKSerializerパラメータを受け付けるコンストラクタを持つ class
でなければならない。ここでは、Box<T>
のシリアライザを書いてみましょう。
class BoxSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Box<T>> {
override val descriptor: SerialDescriptor = dataSerializer.descriptor
override fun serialize(encoder: Encoder, value: Box<T>) = dataSerializer.serialize(encoder, value.contents)
override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder))
}
これで、Box<Project>
をシリアライズ、デシリアライズできるようになりました。
@Serializable
data class Project(val name: String)
fun main() {
val box = Box(Project("kotlinx.serialization"))
val string = Json.encodeToString(box)
println(string)
println(Json.decodeFromString<Box<Project>>(string))
}
フルコードはこちらから取得することができます。
結果のJSONは Project
クラスが直接シリアライズされたように見えます。
{"name":"kotlinx.serialization"}
Box(contents=Project(name=kotlinx.serialization))
###フォーマット固有のシリアライザ
上記のカスタムシリアライザは、どのフォーマットでも同じように動作しました。しかし、シリアライザの実装で利用したいフォーマット固有の機能があるかもしれません。
-
Json の章の Json transformations のセクションでは、JSON 固有の機能を利用したシリアライザの例を提供しています。
-
フォーマットの実装では、代替フォーマットとカスタムフォーマット(実験的)の章のフォーマット固有の型のセクションで説明されているように、型に対してフォーマット固有の表現を持つことができます。
この章では、コンテキストに基づいてシリアライズ戦略を微調整するための一般的なアプローチを進めます。
##コンテクスチュアルシリアライズ
カスタムシリアライズ戦略を指定するこれまでのアプローチは、コンパイル時に完全に定義される static でした。例外はシリアライザを手動で渡すというアプローチでしたが、トップレベルのオブジェクトに対してしか動作しませんでした。シリアライズされたオブジェクトツリーの奥にあるオブジェクトのシリアライズ戦略を実行時に変更する必要があるかもしれません。例えば、java.util.Date
をJSON形式でISO 8601の文字列で表現したい場合と、データをシリアライズするプロトコルのバージョンに応じて長い整数で表現したい場合があります。これは_contextual_シリアライズと呼ばれ、組み込みのContextualSerializerクラスでサポートされています。通常はこのシリアライザクラスを明示的に使う必要はありません。Contextualアノテーションは @Serializable(with = ContextualSerializer::class)
アノテーションへのショートカットを提供し、UseContextualSerializationアノテーションはUseSerializersアノテーションと同様にファイルレベルで使用できます。前者を活用した例を見てみましょう。
@Serializable
class ProgrammingLanguage(
val name: String,
@Contextual
val stableReleaseDate: Date
)
このクラスを実際にシリアライズするためには、encodeToXxx
/decodeFromXxx` 関数を呼び出す際に対応するコンテキストを指定しなければなりません。これがないと、"Serializer for class 'Date' is not found" という例外が発生します。
その例外を発生させる例については、こちらを参照してください。
###シリアライザモジュール
コンテキストを提供するために、実行時にどのシリアライザを使用してどのコンテキストでシリアライズ可能なクラスをシリアライズするかを記述するSerializersModuleインスタンスを定義します。これは、シリアライザを登録するためのSerializersModuleBuilder DSLを提供するSerializersModule {}ビルダー関数を使用して行います。以下の例では、シリアライザでcontextual関数を使用しています。このシリアライザが定義されている対応するクラスは、reified type パラメータを介して自動的に取得されます。
private val module = SerializersModule {
contextual(DateAsLongSerializer)
}
次に、Json {}ビルダー関数とserializersModuleプロパティを使用して、このモジュールでJsonフォーマットのインスタンスを作成します。
カスタムJSON設定の詳細は、JSON設定のセクションに記載されています。
val format = Json { serializersModule = module }
これで、この format
でデータをシリアライズすることができるようになりました。
fun main() {
val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
println(format.encodeToString(data))
}
フルコードはこちらから取得することができます。
{"name":"Kotlin","stableReleaseDate":1455494400000}
シリアライズモジュールの詳細は、ポリモフィズムの章のライブラリのシリアライザモジュールのマージの項に記載されています。
##他のKotlinクラスのための外部シリアライザの導出(実験)
シリアライズするサードパーティのクラスがプロパティのみのプライマリコンストラクタを持つKotlinクラスで、@Serializable
のようなクラスであれば、forClass`プロパティを持つオブジェクトのSerializerアノテーションを使って、_external_シリアライザを生成することができます。
// NOT @Serializable
class Project(val name: String, val language: String)
@Serializer(forClass = Project::class)
object ProjectSerializer
この章で説明したアプローチのいずれかを使用して、このシリアライザをクラスにバインドする必要があります。この例では 「シリアライザを手動で渡す」のアプローチに従います。
fun main() {
val data = Project("kotlinx.serialization", "Kotlin")
println(Json.encodeToString(ProjectSerializer, data))
}
フルコードはこちらから取得することができます。
これはすべての Project
プロパティをシリアライズして取得する。
{"name":"kotlinx.serialization","language":"Kotlin"}
###外部シリアライズはプロパティを使用
"先ほど見たように、通常の @Serializer
アノテーションはシリアライザを作成し、「バッキングフィールドをシリアライズする」のようにしています。Serializer(forClass = ....)` を使った _External_シリアライズはバッキングフィールドにアクセスできず、動作が異なります。これは、セッターを持つ、あるいはプライマリコンストラクタの一部である accessible プロパティのみをシリアライズします。 次の例はこれを示しています。"
// NOT @Serializable, will use external serializer
class Project(
// val in a primary constructor -- serialized
val name: String
) {
var stars: Int = 0 // property with getter & setter -- serialized
val path: String // getter only -- not serialized
get() = "kotlin/$name"
private var locked: Boolean = false // private, not accessible -- not serialized
}
@Serializer(forClass = Project::class)
object ProjectSerializer
fun main() {
val data = Project("kotlinx.serialization").apply { stars = 9000 }
println(Json.encodeToString(ProjectSerializer, data))
}
フルコードはこちらから取得することができます
出力は以下のようになります。
{"name":"kotlinx.serialization","stars":9000}
次の章では、ポリモフィズムを取り上げます。