0
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

Kotlin Serialization + Jersey で RESTful API

はじめに

この記事はなんか書く太郎三日目の記事です
二日目 - 四日目

Kotlin Serialization 使えば Kotlin/JS で作ったフロントエンドでも同じ型を使い回せて便利そうだなと思って軽い気持ちで試してみたので雑にまとめときます

MessageBodyWriter の実装を書く

Jersey の探索パッケージの下に, Content-Type が application/json のときは kotlinx.serialization.json.Json をつかってハンドラの返し値を JSON にシリアライズするやつをおきます

private infix fun Class<*>.isA(superClazz: Class<*>): Boolean =
        superClazz.isAssignableFrom(this)

@Provider
@Produces("application/json")
class KotlinSerializableWriter : MessageBodyWriter<Any> {
    override fun isWriteable(
            type: Class<*>?, genericType: Type?, annotations: Array<out Annotation>?,
            mediaType: MediaType?
    ): Boolean {
        return genericType?.let { this.canSerialize(it) } ?: false
    }

    @UseExperimental(ImplicitReflectionSerializer::class)
    private fun canSerialize(type: Type): Boolean = when (type) {
        is GenericArrayType -> this.canSerialize(type.genericComponentType)
        is Class<*> -> type.isArray || type.kotlin.let {
            it.compiledSerializer() ?: it.defaultSerializer()
        } != null
        is ParameterizedType -> {
            val clazz = type.rawType as Class<*>
            when {
                clazz isA List::class.java || clazz isA Set::class.java  ->
                    this.canSerialize(type.actualTypeArguments[0])
                clazz isA Map::class.java -> type.actualTypeArguments.let {
                    this.canSerialize(it[0]) && this.canSerialize(it[1])
                }
                else -> false
            }
        }
        else -> false
    }

    @UseExperimental(ImplicitReflectionSerializer::class)
    override fun writeTo(
            t: Any?, type: Class<*>?, genericType: Type?, annotations: Array<out Annotation>?,
            mediaType: MediaType?, httpHeaders: MultivaluedMap<String, Any>?,
            entityStream: OutputStream?
    ) {
        PrintWriter(entityStream).use {
            t ?: return it.print("null")
            val serializer = type?.kotlin?.serializer() as? KSerializer<Any>
                    ?: throw IllegalArgumentException()
            it.print(Json.stringify(serializer, t))
        }
    }
}

Dependency に書いとくといい感じにしてくれるやつあるかもって思ったけど Google 力低くて見つけれなかった

UseExperimentalしているので build.gradle に

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.Experimental"]
}

って書いておきます

送る型を書く

今回はとりあえずこんな感じにしておきます

@Serializable
data class Hoge(
        val id: Int,
        val fugas: List<Fuga>
)

@Serializable
data class Fuga(
        val id: Int,
        val hello: String = "hello"
)

送るメソッドを書く

今回はこんな感じ

@Path("/hello")
class HelloResource {
    @Path("/")
    @Produces("application/json")
    @GET
    fun hello(): Hoge = Hoge(2, listOf(
            Fuga(3),
            Fuga(5),
            Fuga(7)
    ))
}

叩いてみる

% curl localhost:8080/backend/hello -s | jq '.'
{
  "id": 2,
  "fugas": [
    {
      "id": 3,
      "hello": "hello"
    },
    {
      "id": 5,
      "hello": "hello"
    },
    {
      "id": 7,
      "hello": "hello"
    }
  ]
}

まとめ

そのうち Kotlin/JS から使ってみるやつやりたいお気持ち

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
0
Help us understand the problem. What are the problem?