Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

CirceでShapelessのHListとJSONとを相互変換するにはcirce.shapesを使うとよい

Circeというライブラリがある。ScalaでJSONまわりの変換とかをやってくれるライブラリだ。

circe.github.io

dev.classmethod.jp

他方、Shapelessというライブラリがある。ScalaでHeterogeneous List、つまりリストなんだけど異なるデータ型を含むリストを扱うためのライブラリで、代数的データ型(ADTs)を操作することができる。 Shapelessがあると、異なるcase class同士を相互変換したり、データ型に依存せずに構造にのみ注目した記述を行えるようになる。

github.com

blog.foresta.me

JSONを直和型に写す

で、例えばこういうことをやりたいときがある:

  • とあるJSONをデコードしたい
  • JSONは、文字列または数値である

素朴に「文字列または数値」をScalaの型で表現すると Either[String, Double]になるけれど、これがさらに増えると大変:

  • JSONは、文字列または数値または文字列の集合である
    • Either[String, Either[Double, Set[String]]]
    • 大変

そこで、shapelessのCoproductを使って、「文字列または数値または文字列の集合」を表現して、そこに向けてJSONをデコードすることを考える:

import shapeless._
import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._

/* String or Double or Set of String */
type SDSS = String :+: Double :+: Set[String] :+: CNil

val s = """12345.6""" // Double
val Right(j) = decode[SDSS](s) // 動かない!!!

circe.shapes

色々と調べてみると、circeがcirce.shapesというモジュールを提供していることがわかった。

    libraryDependencies ++= Seq(
      "io.circe" %% "circe-core",
      "io.circe" %% "circe-generic",
      "io.circe" %% "circe-parser",
      "io.circe" %% "circe-shapes" // 追加した
    ).map(_ % circeVersion),
    libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.9"

そこでcirce.shapesを依存に加えて以下のように書くと、ShapelessのHList/CoproductとJSONとを相互変換できた:

import shapeless._
import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._
import io.circe.shapes._

type IS = Int :: String :: HNil

val myList: IS = 30 :: "car" :: HNil
// myList: IS = 30 :: "car" :: HNil
val listJson = myList.asJson
// listJson: Json = JArray(value = Vector(JNumber(value = JsonLong(value = 30L)), JString(value = "car")))
val ljs = listJson.toString
// ljs: String = """[
//   30,
//   "car"
// ]"""

val Right(ml2) = decode[IS](ljs)
// ml2: IS = 30 :: "car" :: HNil

myList == ml2
// res0: Boolean = true

type IORS = Int :+: String :+: CNil
val s = """123"""
// s: String = "123"
val s2 = """"death""""
// s2: String = "\"death\""
val Right(iors) = decode[IORS](s)
// iors: IORS = Inl(head = 123)
val Right(iors2) = decode[IORS](s2)
// iors2: IORS = Inr(tail = Inl(head = "death"))

やったー。

あわせてよみたい

blog.3qe.us

blog.3qe.us

blog.3qe.us

blog.3qe.us

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?