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