A quick tour of JSON libraries in Scala

Update (18.11.2015): added spray-json-shapeless library
Update (06.11.15): added circe library

Some time ago I wrote a post on relational database access in Scala since I was looking for a library and there were many of them available, making it hard to make a choice. It turns out that the situation is similar if not worse when it comes to JSON libraries in Scala.

There are just plenty of them. You have no idea. (me neither until I wrote this post)

The following is an attempt to provide a quick overview at how a subset of the libraries I found does a few of the most common things one would likely need to do in regard to JSON:

  • parsing it from a raw string
  • browsing the AST
  • building an AST
  • mapping to a case class

There are of course plenty more valid use-cases but this post is not going to cover those.

Let’s get started!

Scala JSON libraries

play-json

The Play Framework comes with a JSON library that covers most of the common use-cases one would encounter when building web applications:

Parsing raw JSON

 
 
1
2
3
4
5
6
7
8
9
scala> import play.api.libs.json._
import play.api.libs.json._
 
scala> val rawJson = """{"hello": "world", "age": 42}"""
rawJson: String = {"hello": "world", "age": 42}
 
scala> Json.parse(rawJson)
res0: play.api.libs.json.JsValue = {"hello":"world","age":42}
 

Browsing the AST

 
 
1
2
3
scala> (res0 \ "hello").as[String]
res1: String = world
 

Building a JSON AST

 
 
1
2
3
scala> Json.obj("hello" -> "world", "age" -> 42)
res2: play.api.libs.json.JsObject = {"hello":"world","age":42}
 

Mapping to a case class

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
scala> case class Model(hello: String, age: Int)
defined class Model
 
scala> implicit val modelFormat = Json.format[Model]
modelFormat: play.api.libs.json.OFormat[Model] = play.api.libs.json.OFormat$$anon$1@81116d
 
scala> Json.fromJson[Model](res0)
res3: play.api.libs.json.JsResult[Model] = JsSuccess(Model(world,42),)
 
scala> res3.get
res4: Model = Model(world,42)
 
scala> Json.toJson(Model("bar", 23))
 
res5: play.api.libs.json.JsValue = {"hello":"bar","age":23}
 

lift-json

lift-json is the JSON library of the Lift framework. It is one of the oldest one out there if I’m not mistaken.

Parsing raw JSON

 
 
1
2
3
4
5
6
7
8
9
scala> import net.liftweb.json._
import net.liftweb.json._
 
scala> val rawJson = """{"hello": "world", "age": 42, "nested": { "deeper": { "treasure": true }}}"""
rawJson: String = {"hello": "world", "age": 42, "nested": { "deeper": { "treasure": true }}}
 
scala> parse(rawJson)
res0: net.liftweb.json.JValue = JObject(List(JField(hello,JString(world)), JField(age,JInt(42)), JField(nested,JObject(List(JField(deeper,JObject(List(JField(treasure,JBool(true))))))))))
 

Browsing the AST

 
 
1
2
3
scala> res0 \ "nested" \ "deeper" \ "treasure"
res1: net.liftweb.json.JsonAST.JValue = JBool(true)
 

Building a JSON AST

 
 
1
2
3
4
5
6
scala> import net.liftweb.json.JsonDSL._
import net.liftweb.json.JsonDSL._
 
scala> ("hello" -> "world") ~ ("age" -> 42)
res2: net.liftweb.json.JsonAST.JObject = JObject(List(JField(hello,JString(world)), JField(age,JInt(42))))
 

Mapping to a case class

 
 
1
2
3
4
5
6
7
8
9
10
11
12
object LiftJsonExample {
  def main(args: Array[String]): Unit = {
    import net.liftweb.json._
    implicit val formats = DefaultFormats
 
    case class Model(hello: String, age: Int)
    val rawJson = """{"hello": "world", "age": 42}"""
 
    println(parse(rawJson).extract[Model])
  }
}
 

spray-json

spray rols its own JSON library that focuses on working with case classes:

Parsing raw JSON

 
 
1
2
3
4
5
6
7
8
9
10
11
12
scala> import spray.json._
import spray.json._
 
scala> import DefaultJsonProtocol._
import DefaultJsonProtocol._
 
scala> val rawJson = """{"hello": "world", "age": 42}"""
rawJson: String = {"hello": "world", "age": 42}
 
scala> rawJson.parseJson
res0: spray.json.JsValue = {"hello":"world","age":42}
 

Browsing the AST

No can do?

Building a JSON AST

No can do?

Mapping to a case class

 
 
1
2
3
4
5
6
7
8
9
scala> case class Model(hello: String, age: Int)
defined class Model
 
scala> implicit val modelFormat = jsonFormat2(Model)
modelFormat: spray.json.RootJsonFormat[Model] = spray.json.ProductFormatsInstances$$anon$2@7bc880f8
 
scala> res1.convertTo[Model]
res4: Model = Model(world,42)
 

spray-json-shapeless

spray-json-shapeless derives spray-json’s JsonFormat-s automatically using shapeless (no need to define an implicit JsonFormat

Mapping to a case class

 
 
1
2
3
4
5
6
7
8
9
10
11
12
scala> import spray.json._
import spray.json._
 
scala> import fommil.sjs.FamilyFormats._
import fommil.sjs.FamilyFormats._
 
scala> case class Model(hello: String, age: Int)
defined class Model
 
scala> Model("hello", 42).toJson
res0: spray.json.JsValue = {"hello":"hello","age":42}
 

This is quite useful, it removes the boilerplate formats hanging around

json4s

json4s is a bit like slf4j in the sense that it tries to unite all kind of rogue libraries serving the same purpose by providing a common interface. But not every library uses it, which means that chances are high that your project will contain json4s in addition to another (few) Scala JSON libraries. Hopefully it will one day succeed with its slogan “One AST to rule them all”.

json4s has its roots in lift-json so this will look familiar:

Parsing raw JSON

 
 
1
2
3
4
5
6
7
8
9
10
11
12
scala> import org.json4s._
import org.json4s._
 
scala> import org.json4s.native.JsonMethods._
import org.json4s.native.JsonMethods._
 
scala> val rawJson = """{"hello": "world", "age": 42}"""
rawJson: String = {"hello": "world", "age": 42}
 
scala> parse(rawJson)
res0: org.json4s.JValue = JObject(List((hello,JString(world)), (age,JInt(42))))
 

Browsing the AST

 
 
1
2
3
scala> res0 \ "hello"
res1: org.json4s.JValue = JString(world)
 

Building a JSON AST tree

 
 
1
2
3
scala> ("hello" -> "world") ~ ("age" -> 42)
res2: org.json4s.JsonAST.JObject = JObject(List((hello,JString(world)), (age,JInt(42))))
 

Mapping to a case class

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
object Json4sExample {
  def main(args: Array[String]): Unit = {
    import org.json4s._
    import org.json4s.native.JsonMethods._
    implicit val formats = DefaultFormats
 
    case class Model(hello: String, age: Int)
    val rawJson = """{"hello": "world", "age": 42}"""
 
    println(parse(rawJson).extract[Model])
  }
}
 

argonaut

Argonaut promotes “purely functional JSON in Scala”. It uses scalaz under the hood and

Parsing raw JSON

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import scalaz._, Scalaz._
import scalaz._
import Scalaz._
 
scala> import argonaut._, Argonaut._
import argonaut._
import Argonaut._
 
scala> val rawJson = """{"hello": "world", "age": 42, "nested": { "deeper": { "treasure": true }}}"""
rawJson: String = {"hello": "world", "age": 42, "nested": { "deeper": { "treasure": true }}}
 
scala> rawJson.parseOption
res0: Option[argonaut.Json] = Some({"hello":"world","age":42,"nested":{"deeper":{"treasure":true}}})
 

Browsing the AST

There are several mechanisms available, let’s use a lense. Those are funky:

 
 
1
2
3
4
5
6
scala> val ohMyLens = jObjectPL >=> jsonObjectPL("nested") >=> jObjectPL >=> jsonObjectPL("deeper") >=> jObjectPL >=> jsonObjectPL("treasure") >=> jBoolPL
ohMyLens: scalaz.PLensFamily[argonaut.Json,argonaut.Json,Boolean,Boolean] = scalaz.PLensFamilyFunctions$$anon$2@8c894ab
 
scala> ohMyLens.get(res0.get)
res1: Option[Boolean] = Some(true)
 

Building a JSON AST tree

 
 
1
2
3
scala> ("hello", jString("world")) ->: ("age", jNumber(42)) ->: jEmptyObject
res2: argonaut.Json = {"age":42,"hello":"world"}
 

Mapping to a case class

 
 
1
2
3
4
5
6
7
8
9
scala> case class Model(hello: String, age: Int)
defined class Model
 
scala> implicit def ModelCodecJson: CodecJson[Model] = casecodec2(Model.apply, Model.unapply)("hello", "age")
ModelCodecJson: argonaut.CodecJson[Model]
 
scala> rawJson.decodeOption[Model]
res3: Option[Model] = Some(Model(world,42))
 

circe

Circe is a fork of Argonaut that uses cats instead of scalaz and uses shapeless to generate codecs.

Parsing raw JSON

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
scala> import io.circe._, io.circe.generic.auto._, io.circe.parse._, io.circe.syntax._      
import io.circe._        
import io.circe.generic.auto._      
import io.circe.parse._      
import io.circe.syntax._        
scala> val rawJson = """{"hello": "world", "age": 42, "nested": { "deeper": { "treasure": true }}}"""        
rawJson: String = {"hello": "world", "age": 42, "nested": { "deeper": { "treasure": true }}}        
scala> parse(rawJson).getOrElse(Json.empty)      
res0: io.circe.Json =        
{        
"hello" : "world",      
"age" : 42,        
"nested" : {        
"deeper" : {        
"treasure" : true      
}      
}      
}
 

Browsing the AST

So far there’s only support for cursors which let you move around the JSON tree and do changes if you would like, not for Lenses yet:

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
scala> val cursor = res0.cursor      
cursor: io.circe.Cursor =        
CJson({      
"hello" : "world",      
"age" : 42,        
"nested" : {        
"deeper" : {        
"treasure" : true      
}      
}      
})      
scala> for {        
| nested <- cursor.downField("nested")      
| deeper <- nested.downField("deeper")      
| treasure <- deeper.downField("treasure")      
| } yield treasure.as[Boolean]      
res1: Option[io.circe.Decoder.Result[Boolean]] = Some(Right(true))
 

Mapping to a case class hierarchy

I didn’t get this to work with the example below. The example in the documentation works though so here is that one:

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
scala> sealed trait Foo      
defined trait Foo        
scala> case class Bar(xs: List[String]) extends Foo      
defined class Bar        
scala> case class Qux(i: Int, d: Option[Double]) extends Foo        
defined class Qux        
scala> val foo: Foo = Qux(13, Some(14.0))        
foo: Foo = Qux(13,Some(14.0))        
scala> foo.asJson.noSpaces      
res0: String = {"Qux":{"d":14.0,"i":13}}        
scala> decode[Foo](foo.asJson.spaces4)      
res1: cats.data.Xor[io.circe.Error,Foo] = Right(Qux(13,Some(14.0)))
 

sphere-json

The sphere-json library focuses on providing an easy way to get de/serializers for entire families of case classes. This is really useful when working with any kind of protocol-related system. I am using it in a CQRS system where commands and events are travelling back and forth between the server and a Javascript UI. Instead of having to define a codec for each one of my case classes, I simply have them extend a common abstract class and let the library take care of the rest. Watch for yourselves:

Mapping to a case class hierarchy

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import io.sphere.json.generic._
import io.sphere.json._
 
object Messages {
 
  sealed abstract class Message
  case class Hello(hello: String) extends Message
  case class Age(age: Int) extends Message
 
}
object SphereJsonExample {
  import Messages._
 
  val rawHello = """{ "hello": "world", "type": "Hello" }"""
  val rawAge = """{ "age": 42, "type": "Age" }"""
 
  implicit val allYourJson = deriveJSON[Message]
 
  def magic(json: String) = fromJSON[Message](json).fold(
    fail => println("Oh noes: " + fail),
    hi => println(hi)
  )
 
  def main(args: Array[String]): Unit = {
    magic(rawHello)
    magic(rawAge)
  }
 
}
 

jawn

jawn is a library that focuses on speed. It defines a lightweight AST and is compatible with all kind of other ASTs that we have seen here.

Parsing raw JSON

 
 
1
2
3
4
5
6
7
8
9
10
11
12
scala> import jawn._
import jawn._
 
scala> import jawn.ast._
import jawn.ast._
 
scala> val rawJson = """{"hello": "world", "age": 42}"""
rawJson: String = {"hello": "world", "age": 42}
 
scala> jawn.ast.JParser.parseFromString(rawJson).get
res0: jawn.ast.JValue = {"age":42,"hello":"world"}
 

rapture

rapture’s json library is the ultimate Scala JSON library. It doesn’t really do anything with JSON itself, instead, it abstracts over the following JSON libraries (which it callsbackends):

  • Argonaut
  • Jackson
  • Jawn
  • JSON4S
  • Lift
  • Play
  • Scala standard library JSON
  • Spray

Parsing raw JSON

 
 
1
2
3
4
5
6
7
8
scala> import rapture.json._
 
scala> import rapture.json.jsonBackends.play._
import rapture.json.jsonBackends.play._
 
scala> Json.parse(rawJson)
res2: rapture.json.Json = {"hello":"world","age":42}
 

Browsing the AST

Rapture is using Scala’s Dynamic trait, which makes this fun:

 
 
1
2
3
scala> res2.hello.as[String]
res3: String = world
 

Building a JSON AST tree

 
 
1
2
3
scala> json"""{ "hello": "world", "age": 42}"""
res6: rapture.json.Json = {"hello":"world","age":42}
 

The Scala standard library

Up until recently I did not know that Scala had a JSON utility in its standard library. But here it is!

Parsing raw JSON

 
 
1
2
3
4
5
6
7
8
9
10
scala> import scala.util.parsing.json._
import scala.util.parsing.json._
 
scala> val rawJson = """{"hello": "world", "age": 42}"""
rawJson: String = {"hello": "world", "age": 42}
 
scala> JSON.parseFull(rawJson)
warning: there was one deprecation warning; re-run with -deprecation for details
res0: Option[Any] = Some(Map(hello -> world, age -> 42.0))
 

Browsing the “AST”

 
 
1
2
3
scala> res0.get.asInstanceOf[Map[String, Any]]("hello").asInstanceOf[String]
res4: String = world
 

Even more!

I am forgetting a ton of libraries here I am sure. That, and I am tired and my glass ofUigeadail is getting empty.

So let me mention a few more:

Great, now which one to pick?

Honestly I don’t have any good advice here. These days I am sticking to play-json and sphere-json, but that’s for no particular reason other than these libraries being there and doing what I need to do (parse and write JSON, traverse and some rare times transform the AST, bind to case classes, make it possible to support custom Java types). If play-json had support for hierarchies out of the box I would probably not have even looked for anything else.

Because for all the joy there seems to be in implementing JSON libraries in Scala, one thing has to be said: JSON de/serialization is boring. It’s this annoying thing that you have to do in order to get your application to talk to another computerized system, period.

I have never met a developer who told me how much pleasure they derived from turning classes into JSON strings and back. That’s just not a thing.

I have, however, met more than one developer that has run into trouble getting library X to cover one of the simple use-cases outlined above. Believe me, there is nothing more frustrating than having to spend time on the annoying task of setting up JSON de/serialization in order to do the boring thing of tossing strings back and forth the network. That’s time you will never get back.

Good night, and good luck.

原文

A quick tour of JSON libraries in Scala的更多相关文章

  1. Avro schemas are defined with JSON . This facilitates implementation in languages that already have JSON libraries.

    https://avro.apache.org/docs/current/ Introduction Apache Avro™ is a data serialization system. Avro ...

  2. 开发工具-scala处理json格式利器-json4s

    1.为什么是json4s 从json4s的官方描述 At this moment there are at least 6 json libraries for scala, not counting ...

  3. scala读取解析json文件

    import scala.util.parsing.json.JSON._ import scala.io.Source object ScalaJsonParse { def main(args: ...

  4. scala解析json —— json4s 解析json方法汇总

    使用json4s的框架,包括spark,flink 1.org.json4s 引入pom的方法 对于本地支持,引入以下依赖项添加到pom中 <dependency> <groupId ...

  5. Awesome Go精选的Go框架,库和软件的精选清单.A curated list of awesome Go frameworks, libraries and software

    Awesome Go      financial support to Awesome Go A curated list of awesome Go frameworks, libraries a ...

  6. scala vs java 相同点和差异

    本贴是我摘抄自国外网站,用作备忘,也作为分享! Similarities between Scala and Java Following are some of the major similari ...

  7. scala攻略--简介

    在个人学习scala的过程中,产生了写一系列随笔的想法,这些随笔包括:翻译自官网.其他英文网站的文章以及自己的心得体会,本文章作为这个系列中的第一个. 由于本人能力所限,以及对scala还处于初级阶段 ...

  8. spark2.1操作json(save/read)

    建筑物配置信息: case class BuildingConfig(buildingid: String, building_height: Long, gridcount: Long, gis_d ...

  9. 数据序列化导读(3)[JSON v.s. YAML]

    前面两节介绍了JSON和YAML,本文则对下面的文章做一个中英文对照翻译. Comparison between JSON and YAML for data serialization用于数据序列化 ...

随机推荐

  1. matlab练习程序(Sepia Tone滤镜)

    我手机上有一个软件实现了很多图像滤镜,挺有意思,我打算都尝试一下. 这个滤镜主要是实现老照片效果. 代码很短,我就不详细介绍了. 原图: 处理后效果: matlab代码如下: clear all;cl ...

  2. HTML概况性介绍

    HTML(HyperText Markup Language)汉语的意思是:超文本标记语言. ”超文本”是指.html页面内不仅仅可以包含文字,还可以包含图片.链接,甚至音乐.程序等非文字元素. “标 ...

  3. 【SSH】 之 Struts2环境搭建及简单应用开发

    在上一篇文章中,我们一起了解了一下struts2的工作机制原理,接下来让我们进行一下简单应用的开发 (一)配置环境 1.建立web项目 2.导入jar包 其中struts2中有很多jar包,我们不需要 ...

  4. Redis主从自动failover

    Redis主从架构持久化存在一个问题,即前次测试的结论,持久化需要配置在主实例上才能跨越实例保证数据不丢失,这样以来主实例在持久化数据到硬 盘的过程中,势必会造成磁盘的I/O等待,经过实际测试,这个持 ...

  5. Loadrunner中参数化实战(8)-Unique+Each occurrence

    参数化数据30条: 脚本如下,演示登录,投资,退出操作是,打印手机号: 首先验证Vugen中迭代: Unique+Each occurrence 设置迭代4次Action 结果如下:

  6. 2016.9.13 JavaScript入门之六基础函数

    1.Math.random()函数: 2.math.floor(x)返回小于参数x的最大整数,即对浮点数向下取整. 例如:random本身只产生(0~1)之间的小数,random()*10 意思是产生 ...

  7. css整理-04 基本视觉格式化

    基本框 假定每一个元素都会生成一个火多个矩形框,为元素框 元素框中心有一个内容区,周围有内边距,边距和外边距 内容的背景会应用到内边距,外边距是透明的,可以看到父元素的背景 内边距不能是负值,外边距可 ...

  8. PDA手持移动POS销售开单软件(网络版)、PDA手持设备小票机

    背景描述: 一家大中型批发及门店销售企业,经销多种冻食品,业务范围覆盖周边众多区域和城市.成立以来,随着业务量的扩大,产品销售分两大渠道:多门店销售和仓库批发,各门店每天都有大量的零散客户和老客户进行 ...

  9. HDU3157 Crazy Circuits(有源汇流量有上下界网络的最小流)

    题目大概给一个电路,电路上有n+2个结点,其中有两个分别是电源和负载,结点们由m个单向的部件相连,每个部件都有最少需要的电流,求使整个电路运转需要的最少电流. 容量网络的构建很容易,建好后就是一个有源 ...

  10. Java classpath 如何自动添加web-content /lib下的jar包

    右键属性--Java build path--Libraries--add library--Web app libraries -- next --next -- ok