Home About
Kotlin , JSON , JavaScript

Kotlin serialization で JSON を扱う

KotlinでJSONに対する処理する場合 JavaVM上で実行するコード用であれば、たとえば org.json とか Gson などいろいろなライブラリがありますが、 Kotlin/JS を使って JavaScript に変換して作動させたい場合は、それらのライブラリは(たぶん)使えません。 でも、Kotlin Serialization を使えば、Kotlin で書いたコードもJavaScriptに変換できるので、それを試します。

Kotlin Serialization についてはこの辺からたどっていくと情報が得られます。

Kotlin Script

まずは kotlin script で試す。

json.main.kts :

@file:Repository("https://repo1.maven.org/maven2/")
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.4.1")

import kotlinx.serialization.json.*

val jsonElement: JsonElement = Json.parseToJsonElement( """{"hello":"world"}""" )
println(jsonElement)

val helloObject: JsonElement? = jsonElement.jsonObject["hello"]
println(helloObject)

val helloContent: String = jsonElement.jsonObject["hello"]?.jsonPrimitive?.contentOrNull?:""
println(helloContent)

実行:

$ kotlinc -script json.main.kts
{"hello":"world"}
"world"
world

はい、できました。

Json, JsonElement しか使っていないと思って、import を修正すると...

import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement

実行:

$ kotlinc -script json.main.kts
error: unresolved reference: jsonObject (json.main.kts:18:45)
error: unresolved reference: jsonObject (json.main.kts:21:40)
json.main.kts:18:45: error: unresolved reference: jsonObject
val helloObject: JsonElement? = jsonElement.jsonObject["hello"]
                                            ^
json.main.kts:21:40: error: unresolved reference: jsonObject
val helloContent: String = jsonElement.jsonObject["hello"]?.jsonPrimitive?.contentOrNull?:""

このようにエラーになります。 たぶん、これは jsonObject などが いわゆる Kotlin Extentions などで実装されているからだと思われますが、詳しくは調べていません。

結局、明示的にインポートした場合に必要となるインポートは以下の通り。

import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.contentOrNull

これで作動します。

コード全体 json.main.kts :

@file:Repository("https://repo1.maven.org/maven2/")
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.4.1")

import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.contentOrNull

val jsonElement: JsonElement = Json.parseToJsonElement( """{"hello":"world"}""" )
println(jsonElement)

val helloObject: JsonElement? = jsonElement.jsonObject["hello"]
println(helloObject)

val helloContent: String = jsonElement.jsonObject["hello"]?.jsonPrimitive?.contentOrNull?:""
println(helloContent)

ここまでは単にjson をパースしただけで、Serialization というからには、シリアライズやデシリアライズを試そうと思ったのですが、 kotlin script では @Serializable がうまく作動しないようです。

Kotlin/JS プロジェクト

それでは gradle を使って Serialization を Node.js から利用できるのか試します。

プロジェクトディレクトリを作成して build.gradle.kts 空のファイルを作成。

$ mkdir hello-serialization
$ cd hello-serialization
$ touch build.gradle.kts

build.gradle.kts を以下の内容にします。

plugins {
    kotlin("js") version "1.7.22"

    //kotlin("plugin.serialization") version "1.7.22"
    id("org.jetbrains.kotlin.plugin.serialization") version "1.7.22"
}

version = "0.1"

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
}

kotlin {
    js(IR) {
        browser {
            webpackTask {
                outputFileName = "hello_lib.js"
                output.library = "helloLib"
            }
        }
        nodejs()
        binaries.library()
        binaries.executable()
    }
}

plugins での指定は、以下のどちらでも問題ないようです。

kotlin("plugin.serialization") version "1.7.22"
id("org.jetbrains.kotlin.plugin.serialization") version "1.7.22"

id ではなく kotlin とすると org.jetbrains.kotlin がそれ(ここでは plugin.serialization)の前に足されるようです。 ドキュメント読んで確認したわけではなくエラーメッセージなどから推測しただけなので、その点はあしからず。

次に Main.kt を書きます。

$ mkdir -p src/main/kotlin
$ touch src/main/kotlin/Main.kt

Main.kt の内容は以下の通り。

import kotlinx.serialization.json.*

@ExperimentalJsExport
@JsExport
fun doJson(): String {
    val jsonElement: JsonElement = Json.parseToJsonElement( """{"hello":"world"}""" )
    val helloContent: String = jsonElement.jsonObject["hello"]?.jsonPrimitive?.contentOrNull?:""
    return helloContent
}

doJson() すると serialization のコードを実行して文字列を返すだけです。

それでは js を生成して、index.js から使えるか試します。 まず、js を生成。(gradle バージョンも確認します。)

$ gradle -version

------------------------------------------------------------
Gradle 7.6
------------------------------------------------------------

Build time:   2022-11-25 13:35:10 UTC
Revision:     daece9dbc5b79370cc8e4fd6fe4b2cd400e150a8

Kotlin:       1.7.10
Groovy:       3.0.13
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          17.0.5 (Private Build 17.0.5+8-Ubuntu-2ubuntu122.04)
OS:           Linux 5.15.0-56-generic amd64

$ gradle build

必要なら gradle wrapper しておきましょう。

結果が build/distributions/ 以下に hello_lib.js, hello_lib.js.map が生成されます。 これを使う index.js を書きます。

const helloLib = require('./build/distributions/hello_lib');

const v = helloLib.doJson();
console.log(v);

実行(バージョンも確認します。)

$ node --version
v18.12.1

$ node index.js
world

できました。

Serialization ができるか試します。 (実際は deserialize しかしてませんが、まあそれはともかく。)

Main.kt を以下に変更。

import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString

@Serializable
data class Pokemon(val name: String)

@ExperimentalJsExport
@JsExport
fun doDeserialization(): String {
    val pikachu: Pokemon = Json.decodeFromString<Pokemon>( """{"name":"Pikachu"}""" )
    return pikachu.name
}

json 文字列から Pokemon クラスを生成しています。

これを js に変換します。

$ gradle build

index.js を doDeserialization() 関数を使うように変更。

const helloLib = require('./build/distributions/hello_lib');

const v = helloLib.doDeserialization();
console.log(v);

実行:

$ node index.js
pikachu

できました。

まとめ

Kotlin Seriazation を使ったコードを Kotlin/JS でコンパイルして JS に変換して、Node.js から使えることがわかりました。 型を多用するなど JS より Kotlin の方が記述しやすい部分に使用できそう。

Liked some of this entry? Buy me a coffee, please.