こんにちは, Web事業部の西村です
私の前回の記事 Promiseを利用して非同期に処理するようにしてみました
今回はこのLambda関数で kotlinx.serialization
を用いてJsonを返すようにしてみたいと思います
目次
過去の記事
なぜ kotlinx.serialization?
まず kotlinx.serialization
を用いる理由です
ここまでの記事を読んでくださっている方々であれば "わざわざないでしょ" と思っている方もいると思います
その通りで, json()
と JSON.stringifiy()
関数を用いればJsonを返すことができます
しかし, kotlinx.serialization
を用いることにより, Client側でも同じクラスを利用することができ, 開発する際に便利であるといえます
またマルチプラットフォームであるため様々な環境で利用できるというのもメリットです
注意事項
この記事では kotlinx.serialization
に関する詳しい解説は行いません
詳しく知りたい方は下記のリポジトリをご覧ください
github.com
開発環境
この記事では下記の環境で開発を行っています
- AdoptOpenJDK 1.8.0_222-b10
- IntelliJ IDEA Community 2019.2.2
- Kotlin 1.3.50
プロジェクト
前回の記事 まで作成したプロジェクトを利用します
プロジェクトの構成は下記のようになっています
KotlinLambda ├─.gradle/ ├─.idea/ ├─build/ ├─gradle/ ├─src/ │ └─main/ │ └─kotlin/ │ └─jp.co.seeds_std.lambda.kotlin/ │ └─handler.kt ├─build.gradle.kts ├─compress.zip ├─gradlew ├─gradlew.bat └─settings.gradle.kts
また, handler.ktは下記のようになっています
package jp.co.seeds_std.lambda.kotlin import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay import kotlinx.coroutines.promise import kotlin.js.Json import kotlin.js.json @JsExport @JsName("handler") fun handler(event: Json, context: Json) = GlobalScope.promise { val tasks = (1..10).map { async { delay(1000) it } } val results = tasks.awaitAll() return@promise Response(body = "Result: ${results.sum()} - Coroutines") } data class Response( val statusCode: Int = 200, val body: String = "{}", val isBase64Encoded: Boolean = false, val headers: Json = json(), val multiValueHeaders: Json = json() )
Jsonを返すようにする
目標
今回は下記のようなJsonを返すようにしてみます
{ "id": 0, // ID "text": "", // 文字列 "random_numbers": [5, 38, 24] // 適当な数値 }
依存関係を追加する
kotlinx.serialization
は外部ライブラリですので依存関係を追加する必要があります
また, 専用のプラグインも必要であるためプラグインの追加も行います
build.gradle.kts / pluginsブロック
plugins { kotlin("js") version "1.3.50" kotlin("plugin.serialization") version "1.3.50" }
依存関係の追加
build.gradle.kts / dependenciesブロック
sourceSets["main"].dependencies { implementation(kotlin("stdlib-js")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.3.2") implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:0.13.0") // 追加 }
リポジトリに jcenter
を追加する必要があるので追加します
build.gradle.kts / repositories
repositories {
mavenCentral()
jcenter() // 追加
}
クライアント側に返すクラスを作成する
作成するクラスが今回のJsonに変換するクラスとなるので各種アノテーションも付与します
今回は Responseクラス
の下に作成します(本当は別ファイルにするほうがいいです)
handler.kt
@Serializable data class ResponseBody( @SerialName("id") val id: Int = 0, @SerialName("text") val text: String = "", @SerialName("random_numbers") val randomNumbers: List<Int> = emptyList() )
@Serializable
これがついているクラスがシリアライズ/デシリアライズすることができます@SerialName(String)
引数で決められた文字列がkeyとなるようになります, ない場合は変数名が利用されるようになるのでなくても問題ないです
Jsonを返す準備をする
まずは前回までに書いていたコードの一部を削除しスッキリさせます
handler.kt
@JsExport @JsName("handler") fun handler(event: Json, context: Json) = GlobalScope.promise { return@promise Response(body = "") }
続いて, 先ほど作ったクラスを文字列に変換するために kotlinxのJsonオブジェクト
を作成します
handler.kt
@JsExport @JsName("handler") fun handler(event: Json, context: Json) = GlobalScope.promise { val json = kotlinx.serialization.json.Json(JsonConfiguration.Stable) // 追加 return@promise Response(body = "") }
⚠注意1
今回の記事ではすでにkotlinが実装しているJsonクラス
をインポートしているため同じ名前であるkotlinxのJsonクラス
をインポートできません
kotlinのJson
とkotlinxのJson
とを間違わないよう注意してください
⚠注意2
JsonConfiguration
にはStable
以外の設定もありますが, 実験的機能となっていますので利用はお勧めしません
Jsonを返す
まずは先ほど作成した ResponseBodyクラス
のオブジェクトを作成します
handler.kt
@JsExport @JsName("handler") fun handler(event: Json, context: Json) = GlobalScope.promise { val json = kotlinx.serialization.json.Json(JsonConfiguration.Stable) val responseBody = ResponseBody(1, "Lambda Json!!!", List(3) { Random.nextInt(100) }) // 追加 return@promise Response(body = "") }
最後に stringify関数
を用いてクラスを文字列に変換し レスポンスのbody
に設定します
handler.kt
@JsExport @JsName("handler") fun handler(event: Json, context: Json) = GlobalScope.promise { val json = kotlinx.serialization.json.Json(JsonConfiguration.Stable) val responseBody = ResponseBody(1, "Lambda Json!!!", List(3) { Random.nextInt(100) }) return@promise Response(body = json.stringify(ResponseBody.serializer(), responseBody)) // 変更 }
ResponseBody.serializer()
この関数は@Serializable
アノテーションがついているクラスの場合コンパイラが自動的に作成してくれます
動作確認
関数をデプロイし, API GatewayのURLにアクセスし {"id":1,"text":"Lambda Json!!!","random_numbers":[ランダムな数字が3つ]}
表示されれば成功です
最後に
いかがでしたでしょうか
LambdaでJsonを返すのはありがちです
これを利用することによりKotlinで作成するマルチプラットフォームなアプリケーションではかなり強力になりうると考えます
また, ここまで4つの記事に分け Kotlin/JS
をもちいて AWS Lambda関数
を作って便利にしてみました
これがベストプラクティスかどうかはプロジェクトによると思いますが、1つの参考になるとうれしい限りです
ここまで読んでいただきありがとうございました
蛇足
JSON.stringify() を使ってJsonを返してみる
JavaScriptにもとから存在する, JSON.stringify
を用いてもJsonを返すことができます
handler.js
@JsExport @JsName("handler") fun handler(event: Json, context: Json) = GlobalScope.promise { val responseBody = ResponseBody(1, "Lambda Json!!!", listOf(0, 1, 2)) return@promise Response(body = JSON.stringify(responseBody)) }
実行結果
{"id":1,"text":"Lambda Json!!!","randomNumbers":[0,1,2]}
しかしこれではJsonのKeyは指定できないので変数をすべて private
に変更すると結果が変わるので注意が必要です
privateに変更したときの実行結果
{"id_0":1,"text_0":"Lambda Json!!!","randomNumbers_0":[0,1,2]}
こういうケースもあるため kotlinx.serialization
を用いることをお勧めします
json() と JSON.stringify() を使ってJsonを返してみる
一番オーソドックスでわかりやすい返し方だと私は思います
handler.js
@JsExport @JsName("handler") fun handler(event: Json, context: Json) = GlobalScope.promise { val responseBody = json( "id" to 0, "text" to "Lambda Json!!!", "random_numbers" to listOf(0, 1, 2) ) return@promise Response(body = JSON.stringify(responseBody)) }
実行結果
{"id":1,"text":"Lambda Json!!!","random_numbers":[0,1,2]}
蛇足まで読んでいただきありがとうございました