【Kotlin】kotlinx.serialization で JSON を自在に変換する 🤔

 

🤔 Kotlin Serialization Guide を眺めてみる

JSON 処理なら、ここらですよね。

どんな扱いになっているのか見てみましょうか。


👉 kotlinx.serialization/serialization-guide.md at master · Kotlin/kotlinx.serialization · GitHub hatena-bookmark

基本的な使い方「Chapter 1.Basic Serialization」として、以下のようなデータクラスを例に以下のサンプルコード。


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


val data = Project("kotlinx.serialization", "Kotlin")
println(Json.encodeToString(data))

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


val data = Json.decodeFromString<Project>("""
  {"name":"kotlinx.serialization","language":"Kotlin"}
""")
println(data)

// Project(name=kotlinx.serialization, language=Kotlin)

以下のようなイメージのことを書いてる。


                +-----------------------------------+                 
                |                                   |                 
                | Project(                          |                 
                |   name = "kotlinx.serialization", |                 
                |   language = "Kotlin"             |                 
                | )                                 |                 
                |                                   |                 
                +-----------------------------------+                 
                              |      ^                                
                              |      |                                
                              |      |                                
Json.encodeToString(Project)  |      |  Json.decodeFromString(String) 
                              |      |                                
                              |      |                                
                              v      |                                
               +------------------------------------+                 
               |                                    |                 
               | {                                  |                 
               |   "name":"kotlinx.serialization",  |                 
               |   "language":"Kotlin"              |                 
               | }                                  |                 
               |                                    |                 
               +------------------------------------+

汎用的にしてこうですか。


            +-----------------------------------+                          
            |                 T                 |                          
            +-----------------------------------+                          
                            |  ^                                           
                            |  |                                           
                            |  |                                           
Json.encodeToString(T)      |  |     Json.decodeFromString<T>(String)      
                            |  |                                           
                            |  |                                           
                            v  |                                           
           +------------------------------------+                          
           |               String               |                          
           +------------------------------------+

メソッド名的には、


シリアル化  → encode
逆シリアル化 → decode

となってます。

 

🤔 JsonElement

というのがあります。

👉 JsonElement hatena-bookmark

少し試してみます。変換しながら型を見ていきます。


// T
val t = Project(name = "kotlinx.serialization", language = "Kotlin")
println("$t ${t::class.simpleName}")
// Project(name=kotlinx.serialization, language=Kotlin) Project


// T -> JsonElement
val e = Json.encodeToJsonElement(t)
println("$e ${e::class.simpleName}")
// {"name":"kotlinx.serialization","language":"Kotlin"} JsonObject


// JsonElement -> String
val s = Json.encodeToString(e)
println("$s ${s::class.simpleName}")
// {"name":"kotlinx.serialization","language":"Kotlin"} String


// String -> JsonElement
val e2 = Json.parseToJsonElement(s)
println("$e2 ${e2::class.simpleName}")
// {"name":"kotlinx.serialization","language":"Kotlin"} JsonObject


// JsonElement -> T
val t2 = Json.decodeFromJsonElement<Project>(e2)
println("$t2 ${t2::class.simpleName}")
// Project(name=kotlinx.serialization, language=Kotlin) Project

JsonElement の型は JsonObject です。

図にします。


                  +-----------------------------------+                             
                  |                 T                 |                             
                  +-----------------------------------+                             
                                  |  ^                                              
                                  |  |                                              
                                  |  |                                              
  Json.encodeToJsonElement(T)     |  |  Json.decodeFromJsonElement<T>(JsonElement)  
                                  |  |                                              
                                  |  |                                              
                                  v  |                                              
                 +------------------------------------+                             
                 |            JsonObject              |                             
                 +------------------------------------+                             
                                  |  ^                                              
                                  |  |                                              
                                  |  |                                              
Json.encodeToString(JsonElement)  |  |  Json.parseToJsonElement(String)      
                                  |  |                                              
                                  |  |                                              
                                  v  |                                              
                 +------------------------------------+                             
                 |               String               |                             
                 +------------------------------------+

しかし、考えてみると、JsonObject って最近ではあまり見かけなくなった気がします。

 

🤔 まとめ

以上を図にしておきます。


👉 Json.md GitHub Gist hatena-bookmark

String 方向が encode(エンコード)serialize(シリアル化)

T 方向が decode(デコード)deserialize(逆シリアル化)



【Python】絵文字を含む Unicode 文字列の文字数をカウントする方法と文字ごとの構成要素


print(len("👩‍👧‍👦"))
# 5

絵文字1文字の長さが「5」。

コードポイントの個数ですね!

 

😀 文字を数える

データを15文字で作ります。

この文字をカウントするのは、re でどう書くのか分からなかったので、ライブラリを使いました。

👉 regex 2022.10.31 on PyPI - Libraries.io hatena-bookmark

Matching a single grapheme \X
The grapheme matcher is supported. It conforms to the Unicode specification at http://www.unicode.org/reports/tr29/.

👉 mrabarnett/mrab-regex hatena-bookmark


print(len(regex.findall(r'\X', data)))
# 15

正しくカウントできました!

 

😀 文字の構成(要素)を確認する

作ったデータを使って、各文字に含まれるコードポイントを確認するスクリプトを書いて実行してみます。


dump(data)

【Python】絵文字を含む Unicode 文字列の文字数をカウントする方法と文字ごとの構成要素

うまくそれぞれの絵文字の構成要素が分かるようになりました。

 

😀 まとめ

Python ではコードポイントをそのまま \UXXXXXXXX の形式で書けるので便利。

Kotlin では以下から。



【Kotlin】絵文字を含む Unicode 文字列の文字数をカウントする方法と文字ごとの構成要素

絵文字 は AndroidStudio 上で扱いづらいですよね。

例えば、


"😀😍"

という2つの絵文字は、


"\uD83E\uDD78" + "\uD83D\uDC3B\u200D\u2744\uFE0F"

と書く方が編集しやすいです。(しかし分かりづらい。)

絵文字をコピーして Android Studio 上でペーストすると、

\uXXXX\uXXXX

のような「UTF-16 エスケープシーケンス」に置き換わりますよね。

(しかし置き換わらない場合もある。)

👉 Python vs Kotlin Unicode Escape Sequence (エスケープシーケンス) の記述 hatena-bookmark

 

😀 文字を数える

検証するために文字列データを作ります。

絵文字も新旧バージョンのものを含めます。

15文字です。

比較的に新しい Unicode Emoji 15.1 の2つの絵文字は、今現在、まともに見たことがありません。

このデータの文字数をカウントするのは、


println(Regex("\\X").findAll(data).count())
// 15

というかんじでしょうか。

Unicode extended grapheme clusters are supported by the grapheme cluster matcher \X.

👉 2.2 Extended Grapheme Clusters and Character Classes with Strings hatena-bookmark
👉 Unicord support - Pattern  |  Android Developers hatena-bookmark

強く開発環境に影響されると思いますので、各バージョン安定版最新に更新して揃えておくべきでしょう。


android {
  // ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_17
    targetCompatibility JavaVersion.VERSION_17
  }

  kotlinOptions {
    jvmTarget = JavaVersion.VERSION_17.toString()
  }
}

 

😀 文字の構成(要素)を確認する

人間が見ている文字は内部的には複数の要素から構成されています。

絵文字周りで開発している方にはどこかで見たようなすごく便利な表です。

文字列を渡せば簡単に確認できるようにメソッドにしておきます。

先ほどの1文字ごとに分割したあとにその内部を要素に分割して表示します。

data は、先述の15文字を使ってみました。


dump(data)

AndroidStudio のデバッグウィンドウやターミナルでも表示できない絵文字はありますが、内部的には問題なく処理できてるように見えます。

 

😀 まとめ

今回、新しくはっきり認識できたことは、

kotlin の String.length は、


その文字(列)の Char (/uXXXX) の数を表している。

ということ。コードポイントの数ではない。

Char は、コードポイントを 内部的な UTF-16サロゲートペア 分割されたあとの要素。


println("👩‍👧‍👦".length)
// 8

👉 kotlin/Char.kt at 924c28507067cbfbf78a6509ea89eabe496e34ca · JetBrains/kotlin · GitHub hatena-bookmark



👉 絵文字が意図しない白黒で表示される ➡️ - Unicode Variation Selector hatena-bookmark