Json4s简明指南

488 阅读1分钟
原文链接: zhuanlan.zhihu.com

1. Ammonite简明指南

curl -L -o $HOME/bin/amm https://git.io/vHr1V && chmod +x $HOME/bin/amm

$HOME/bin/amm 可以启动 REPL,下文的代码均可以在 amm 上跑。如果要引入依赖,可以在 index.scala-lang.org 找。macOS用户请用Homebrew安装,GNU/Linux请参考官网(文末)安装最新版。

2. 我选择 Jackson

3. JSON String, JValue, Scala对象

3.1 JSON String -> JValue

import $ivy.`org.json4s::json4s-jackson:3.6.2`
import org.json4s.jackson.JsonMethods.parse
// 正常的例子
parse(""" { "numbers" : [1, 2, 3, 4] } """)
// null
parse(""" { "code": 0, "error": null } """)

3.2 JValue -> JSON String

import $ivy.`org.json4s::json4s-jackson:3.6.2`
import org.json4s.jackson.JsonMethods.render
import org.json4s.jackson.JsonMethods.compact
import org.json4s.jackson.JsonMethods.pretty
import org.json4s.JsonDSL._

// primitive type 会被隐式转换成 JValue,
// 比如这里的 List(1, 2, 3)
// 就是 JArray(List(JInt(1), JInt(2), JInt(3)))
compact(render(List(1, 2, 3)))

// JValue
val joe = ("name" -> "joe") ~ ("age" -> Some(35))
pretty(render(joe))

3.3 JValue -> Scala对象

import $ivy.`org.json4s::json4s-jackson:3.6.2`
import org.json4s._
import org.json4s.JsonDSL._

// 什么时候需要用隐式转换?
// 为什么3.1,3.2不需要用隐式转换?
implicit val formats = DefaultFormats

case class Person(name: String, age: Option[Int])
val joe = ("name" -> "joe") ~ ("age" -> Some(35))
joe.extract[Person]

3.4 Scala对象 -> JValue

import $ivy.`org.json4s::json4s-jackson:3.6.2`
import org.json4s.Extraction
import org.json4s.DefaultFormats

implicit val formats = DefaultFormats

case class Person(name: String, age: Option[Int])
Extraction.decompose(Person("joe", Some(11)))

3.5 序列化

import $ivy.`org.json4s::json4s-jackson:3.6.2`
import org.json4s._
import org.json4s.jackson.Serialization
import org.json4s.jackson.Serialization.{read, write}

implicit val formats = Serialization.formats(NoTypeHints)

case class Person(firstName: String)
write(Person("joe"))

read[Person](""" {"firstName": "joe"} """)

case class Status(code: Int, error: String)
read[Status](""" {"code": 0, "error": null} """)

4. Option 和 Either

  • Option可以用来表示可有可无的字段。Option是合理的。
  • Either可以用来表示恶心的需求。使用了Either说明api定义不规范。

5. Play with JValue

5.1 Snakize and Camelize

import $ivy.`org.json4s::json4s-jackson:3.6.2`
import org.json4s.JsonDSL._

val camelJoe = ("firstName" -> "joe") ~ ("age" -> 23)
camelJoe.snakizeKeys
val snakeJoe = ("first_name" -> "joe") ~ ("age" -> 23)
snakeJoe.camelizeKeys

在序列化和反序列化的过程中,如果字段名的风格不一致,就需要通过JValue这种中间状态转换字段名的风格。

5.2 XPath

如果只需要JSON中的部分字段,那么就用XPath和Pattern Matching,如果所有的字段都需要用到,那就用extract to case class的方式。

5.3 For Comprehension(不推荐)

而for-comprehension的可读性比较差,不推荐使用。在这种场景下,for-comprehension的语义被复杂化了,个人觉得是易用性上的缺陷。如果你要用for-comprehension,你必须清楚地知道整个ast的结构,知道每个<-的不同含义。

6. Reflections

XPath这一节有一些问题,这里就直接贴之前在V2EX做抽奖活动用的代码,不啰嗦了。有别的问题,请在评论区指出:

import $ivy.`com.lihaoyi::requests:0.1.4`
import $ivy.`org.json4s::json4s-jackson:3.6.2`
import org.json4s.jackson.JsonMethods.parse
import org.json4s._
import org.json4s.JsonDSL._
import scala.util.Random

// 一些常量
val url = "https://www.v2ex.com/api/replies/show.json?topic_id=493356"
val numberOfWinners = 6

// 从 V2EX 获取数据
val source = requests.get(url)

// 解析 JSON,获取所有参与抽奖的用户
implicit val formats = DefaultFormats
val json = parse(source.text)
case class Profile(username: String, github: String)
case class Member(member: Profile)
val members = json.extract[List[Member]].map(_.member.username).distinct

// 抽奖
Random.shuffle(members).take(numberOfWinners)

References

Ammonite官网ammonite.io图标The Original Postsadhen.com
JSONPath - XPath for JSONgoessner.net
代码:Formats.scalagithub.com

Reviewed by