しおしお

IntelliJ IDEAのことなんかを書いてます

Spring Boot + MVCでkotlinx.serializationを使ってリクエスト・レスポンスのシリアライズとデシリアライズをしてみる

Spring Boot + MVCでkotlinx.serializationを使ってリクエスト・レスポンスのシリアライズとデシリアライズを試してみました。

使用バージョンなど

↓のバージョンを使って試してみました。

  • Spring Bootは、2.5.4
  • kotlinx.serialization

build.gradle.ktsはこのようになっています。

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.5.4"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.5.30"
    kotlin("plugin.spring") version "1.5.30"
    kotlin("plugin.serialization") version "1.5.30"
}

// 〜省略〜
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

お試ししてみる

コード

実装的には、kotlinx.serializationを使うためにdata classに@Serializableアノテーションが設定されている感じですね。 なお、kotlinx-serialization-jsonに関連するクラスがクラスパス上にあると、自動的にkotlinx.serialization用のHttpMessageConverterが使われるようになります。(優先度的には、Jacksonより高くなる感じです。)

今回は、リクエストボディとレスポンスボディの変換でkotlinx.serializationが使われることを確認できるコードを書いてみました。

@RestController
@RequestMapping("/hello")
class HelloController {

    private val logger = LoggerFactory.getLogger(HelloController::class.java)

    @GetMapping
    fun get(): Message {
        return Message("hello!!!")
    }

    @PostMapping
    fun post(@RequestBody message: Message) {
        logger.info("mesage: {}", message)
    }
}

@Serializable
data class Message(
    val text: String
)

実行結果

レスポンスボディの変換

kotlinx.serializationを使ってるか非常に分かりづらいですが、レスポンスとして指定したdata classの内容がJSONで返ってきていますね。

$ curl  localhost:8080/hello
{"text":"hello!!!"}

リクエストボディの変換

curl -X POST -H 'content-type: application/json' localhost:8080/hello -d '{"text": "siosio!"}'

ログにリクエストボディを保持しているdata classの内容が出力されていますね。

2021-09-05 15:38:02.745  INFO 43536 --- [nio-8080-exec-3] s.b.HelloController                      : mesage: Message(text=siosio!)

カスタマイズして使ってみる

デフォルトの構成では何も設定が入っていない状態で使われるため、data classに定義されていない項目がリクエストのJSONにあったりすると400 Bad Requestが返されたりしちゃいます。 例えば、次のリクエストを投げると、Could not read JSON: Unexpected JSON token at offset 21: Encountered an unknown key 'hoge'. なメッセージのログが出力され400エラーとなります。

curl -X POST -H 'content-type: application/json' localhost:8080/hello -d '{"text": "siosio!", "hoge": "fuga"}'

これでは、ちょっと使い勝手が悪いですよね。これは設定をカスタマイズしたkotlinx.serialization用のHttpMessageConverterを登録するだけで解決します。 カスタマイズしたHttpMessageConverterは、WebMvcConfigurerをimplementsしたクラスを作ってconfigureMessageConvertersをオーバライドすることで登録できます。
↓のコードのように、KotlinSerializationJsonHttpMessageConverterに設定をカスタマイズしたものを指定し、convertersの先頭に追加します。(末尾に追加してしまうと、デフォルト設定のKotlinSerializationJsonHttpMessageConverterが使われちゃうので注意です。)

@Configuration
class WebConfig : WebMvcConfigurer {

    override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
        converters.add(0, KotlinSerializationJsonHttpMessageConverter(Json {
            ignoreUnknownKeys = true
        }))
    }
}