先日AWS LambdaでScala 3を走らせる方法を解説した。
解説したというか、何事もなく動いたという感じだ。
今回は安定してJSONを入出力するテクがわかってきたので解説する。
完成品はこんな感じになる:
import java.io.InputStream import java.io.OutputStream import com.amazonaws.services.lambda.runtime.Context import scala.io.Source case class Input(foo: Int, bar: String, buzz: Seq[Double]) def handler(in: InputStream, out: OutputStream, ctx: Context): Unit = { val jsonString = Source.fromInputStream(in).mkString val parsedInput: Either[io.circe.Error, Input] = decode[Input](jsonString) println(parsedInput) in.close() out.flush() out.close() }
ハンドラ
AWS LambdaのJavaランタイムは、自動的に入力のJSONをPOJOに変換する機能が付いてくるのだが、この動作の振る舞いがとても微妙で、Scalaのcase classにうまく変換できない上に、文字列として受け取りたくても勝手に変換しようとするのでたいしてアテにならない。
ありがたいことに、ハンドラのシグネチャを特定の形式にするとJSON変換を通さずにそのまま入力を返してくれるようになる。
import java.io.InputStream import java.io.OutputStream import com.amazonaws.services.lambda.runtime.Context def handler(in: InputStream, out: OutputStream, ctx: Context): Unit = ???
InputStream
とOutputStream
、最後にContext
を受け取る形にするのがミソ。こうすることで、Lambdaランタイムは入出力ストリームとしてそのまま手つかずのバイトストリームを扱えるようになる。
もちろん最後にはストリームを閉じなければならない:
in.close() out.flush() out.close()
入力のJSON変換
今回はCirceを使うが、まぁ好みのものがあればそれを使えばよい。scala.io.Source
を使うとjava.io.InputStream
をよしなに文字列に変換できるので、これをcirceのdecodeに放り込むとデコードが完了する。
import scala.io.Source case class Input(foo: Int, bar: String, buzz: Seq[Double]) val jsonString = Source.fromInputStream(in).mkString val parsedInput: Either[io.circe.Error, Input] = decode[Input](jsonString)
出力
出力も要するにjava.io.OutputStream
に書き込むだけなので難しいことはない。バイトストリームなのでgetBytes()
してエンコードしてやる必要があることだけ注意。
out.write("hello, world".getBytes())
Circeから直接OutputStream
に書き込む方法があるかな、と思ったけど見付けられなかった。
まとめ
- LambdaのJavaランタイムはJava向けの構成になっているのでScalaだと使いづらいことがある
- JSONの入力をしたいときは
InputStream
を使って直接バイトストリームを得るのがおすすめ