RisonというのはJSONライクかつURLに埋め込みやすいようURLエンコーディングが最小限になるよう設計されたデータフォーマットだそうで、Kibanaなどで使われているそうです。日本語だと以下の記事が詳しいです(自分もこの記事を見てRisonを知りました)。
すでにJavaScriptやPython、Golangなど様々な言語向けのライブラリが存在するようですが、Scala用のライブラリが見当たらなかったので久しぶりにScalaのパーサコンビネータライブラリを使って実装してみました。
こんな感じで使います。
import com.github.takezoe.rison._ val parser = new RisonParser() // parse parser.parse("(name:Lacazette,age:27)") match { case Right(node) => println(node.toScala) // => Map(name -> Lacazette, age -> 27) case Left(error) => println(error) } // convert from Scala's Map val node: RisonNode = RisonNode.fromScala( Map( "name" -> "Alexandre Lacazette", "twitter" -> "@LacazetteAlex" ) ) println(node.toRisonString) // => (name:'Alexandre Lacazette',twitter:'@LacazetteAlex') // URL encode val encoded: String = node.toUrlEncodedString println(encoded) // => (name:'Alexandre+Lacazette',twitter:'@LacazetteAlex')
// O-rison val orison: ObjectNode = parser.parseObject("name:Lacazette,age:27") println(orison.toObjectString) // => name:Lacazette,age:27 // A-rison val arison: ArrayNode = parser.parseArray("Lacazette,Aubameyang,Ozil") println(arison.toArrayString) // => Lacazette,Aubameyang,Ozil
上記のサンプルではMap
やSeq
を使っていますが、ケースクラスを使うこともできます。
// case class case class Player(name: String, age: Int) // convert from case class val node: RisonNode = RisonNode.fromScala(Player("Lacazette", 27)) println(node.toRisonString) // => (name:Lacazette',age:27) // convert to case class val obj: Player = node.to[Player] println(node.) // => Player(Lacazette, 27)
ケースクラスとの相互変換にはairframe-surfaceを使いました。
airframe-surfaceは型情報を取得してごにょごにょするためのScalaライブラリで、リフレクション関連の面倒な処理を隠蔽してくれます。Scalaだと同様の処理にはShapelessのようなライブラリを使う方法もありますが、airframe-surfaceはリフレクションならではの制限はあるものの気軽に使えて便利です。
このRisonパーサは冬休み中に作っていたのですが、パーサコンビネータやairframe-surfaceなどいろいろ試すことができてなかなか良い題材でした。