預備知識
json4s的數據結構AST (Abstract Syntax Tree)。
sealed abstract class JValue
case object JNothing extends JValue // 'zero' for JValue
case object JNull extends JValue
case class JString(s: String) extends JValue
case class JDouble(num: Double) extends JValue
case class JDecimal(num: BigDecimal) extends JValue
case class JInt(num: BigInt) extends JValue
case class JBool(value: Boolean) extends JValue
case class JObject(obj: List[JField]) extends JValue
case class JArray(arr: List[JValue]) extends JValuetype JField = (String, JValue)
我們可以通過 json4s 對json所做的操作如下圖所示,中間為 Json AST (簡單理解就是一個用JValue表示的 JSON)。
另外,org.json4s下定義了很多scala原生數據轉JValue的隱式轉換(即多數操作下可以把原生數據當做JValue直接使用)
但是注意:Tuple不能自動轉為JValue,在需要轉換的時候,render先轉一下,如:json merge render("height",175)
一、 創建json對象
{"name":"luca", "id": "1q2w3e4r5t", "age": 26, "url":"http://www.nosqlnocry.wordpress.com"}
方式1:parse函數
// parse from string example,得到的是JValue對象
var json = parse("""{"name":"luca", "id": "1q2w3e4r5t", "age": 26, "url":"http://www.nosqlnocry.wordpress.com"}""")
方式2:dsl創建
org.json4s中定義了從tuple到JValue的操作符
// DSL example,是JObject對象
var json = ("name","luca") ~ ("id","1q2w3e4r5t") ~ ("age",26) ~ ("url","http://www.nosqlnocry.wordpress.com")
// tuples can be defined also like this: ("id" -> "1q2w3e4r5t")
println(json)
JObject(List((name,JString(luca)), (id,JString(1q2w3e4r5t)), (age,JInt(26)), (url,JString(http://www.nosqlnocry.wordpress.com))))
二、常用操作
2.1 新增一個field
注意,以下兩個方法都不會覆蓋,若原json中已有height,則新json中會有兩個height.
//法1:使用dsl,JObject才有~方法
json = json ~ ("height" -> 175)
//法2: 使用merge,要求類型和左邊一致,所以json為parse出來的JValue時,要用render生成JValue再merge
json = json merge render("height",175)
2.2 更新一個field
使用 transformField
json = json transformField {case JField("name", _) => ("NAME", JString("Luca")) //還可重命名case JField("age", JInt(age)) => ("age", JInt(age+1))//更新值
}json = json merge render("age",26) //若json 中已有"age"
2.3 刪除一個field
json = json removeField {case JField("NAME", _) => true //被刪除case _ => false
}
// 或等價的
json = json filterField {case JField("NAME", _) => false case _ => true //被保留}
2.4 獲取一個field
println(compact(json \\ "age")) // 27 嵌套獲取-見下
println(compact(json \ "age")) // 27
println(compact(json.children(1))) // 27
三、高階操作
{"name": "luca","id": "1q2w3e4r5t","age": 26,"url": "http://www.nosqlnocry.wordpress.com","url": "https://nosqlnocry.wordpress.com","loginTimeStamps": [1434904257,1400689856,1396629056],"messages": [{"id": 1,"content": "Please like this post!"},{"id": 2,"content": "Forza Roma!"}],"profile": {"id": "my-nickname","score": 123,"avatar": "path.jpg"}
}
3.1 選取field
println(JSON)
//JObject(List((name,JString(luca)), (id,JString(1q2w3e4r5t)), (age,JInt(26)), (url,JString(http://www.nosqlnocry.wordpress.com)), (url,JString(https://nosqlnocry.wordpress.com)), (loginTimeStamps,JArray(List(JInt(1434904257), JInt(1400689856), JInt(1396629056)))), (messages,JArray(List(JObject(List((id,JInt(1)), (content,JString(Please like this post!)))), JObject(List((id,JInt(2)), (content,JString(Forza Roma!))))))), (profile,JObject(List((id,JString(my-nickname)), (score,JInt(123)), (avatar,JString(path.jpg)))))))println(JSON\\"id") //獲取所有嵌套的id數據
// prints: JObject(List((id,JString(1q2w3e4r5t)), (id,JInt(1)), (id,JInt(2)), ...println(JSON\"id")//獲取第一層的id數據
// prints: JString(1q2w3e4r5t)println(JSON\"url") //如果第一層有多個,則返回JArray
// prints: JArray(List(JString(http://www...), JString(https://nosqlnocry...val messagesIds = (JSON \ "messages") \ "id" //獲取JAray中的id數據
println(messagesIds)
// prints: JArray(List(JInt(1), JInt(2)))
println(messagesIds.values)
// prints: List(1,2)
//或用for語句
val messagesIds2= for {JObject(child) <- JSONJField("id", JInt(id)) <- child} yield idprintln(messagesIds2)// prints: List(1,2)
for語句的<-
在JValue中做了特殊處理,會返回所有匹配項。
for {JObject(child) <- JSON //這回匹配所有JObject,不管是不是嵌套}{println(child)}
//List((name,JString(luca)), (id,JString(1q2w3e4r5t)), (age,JInt(26)), (url,JString(http://www.nosqlnocry.wordpress.com)), (url,JString(https://nosqlnocry.wordpress.com)), (loginTimeStamps,JArray(List(JInt(1434904257), JInt(1400689856), JInt(1396629056)))), (messages,JArray(List(JObject(List((id,JInt(1)), (content,JString(Please like this post!)))), JObject(List((id,JInt(2)), (content,JString(Forza Roma!))))))), (profile,JObject(List((id,JString(my-nickname)), (score,JInt(123)), (avatar,JString(path.jpg))))))
//List((id,JInt(1)), (content,JString(Please like this post!)))
//List((id,JInt(2)), (content,JString(Forza Roma!)))
//List((id,JString(my-nickname)), (score,JInt(123)), (avatar,JString(path.jpg)))
JValue的<-
調用的是
def foreach(f: JValue => Unit): Unit =self.filter(p).foreach(f)
Array的<-
調用的是
def foreach[U](f: A => U): Unit = {var i = 0val len = lengthwhile (i < len) { f(this(i)); i += 1 }}
3.2 取出field
println(compact(render(JSON \ "messages")))
// prints: [{"id":1,"content":"Please like this post!"},{"id":2,"content":"Forza Roma!"}]
println(pretty(render((JSON \ "messages")\"content")))
// prints: [ "Please like this post!", "Forza Roma!" ] // note it is not compacted anymoreprintln(pretty(render(JSON \ "age")))
// prints: 26println(compact(render(JSON \ "name")))
// prints: "luca" // note the apostrophesvar name = for { JString(x) <- (JSON \\ "name") } yield x //或用for表達式去掉雙引號
println(name(0))
// prints: lucavar name = (JSON \ "name") //或用values去掉雙引號,保留對應基本類型時,推薦這種方法
println(name.values)
// prints: lucaimplicit val formats = DefaultFormats
val name = (JSON \ "name").extract[String]//或直接extract,已知需要的類型時推薦這種方法
println(name)
// prints: luca
name.values 原理:
3.3 查找和過濾filed
//返回第一個遇到的元素
val URL = JSON findField {
case JField("url", _) => true
case _ => false
}
println(URL)
// prints: Some((url,JString(http://www.nosqlnocry.wordpress.com)))// 返回所有符合條件的元素
val URLs = JSON filterField {
case JField("url", _) => true
case _ => false
}
println(URLs)
// prints: List((url,JString(http://www.nosqlnocry...)), (url,JString(https://nosqlnocry...)
3.4 合并與差異另一個Json2:merge和diff
{"messages": [{"id": 3,"content": "how to merge?"}],"url": "anotherURL","loginTimeStamps": 1400689856,"profile": {"avatar": "new.jpg"},"new": "new value"
}
Json1 merge Json2
- 如果字段法Json1/f1與Json2/f1結構不同,或者僅具有簡單結構,則Json2會替換Json1的f1
- 若結構相同且為復雜結構,則會合并
- 若Json2/f1在Json1中不存在,則新增
diff 獲取兩個JSon間的不同(用得少):
val newUserJSON = """{"name":"luca","id": "anotherID","age": 26,"url":"http://www.nosqlnocry.wordpress.com", "profile":{"id":"another-nickname", "score":99999, "avatar":"path.jpg"}
}
"""
val Diff(changed, added, deleted) = JSON diff parse(newUserJSON)println(compact(render(changed)))
println(added)
println(pretty(render(deleted)))
/* print:
{"id":"anotherID","profile":{"id":"another-nickname","score":99999}}
JNothing
{"url" : "https://nosqlnocry.wordpress.com","loginTimeStamps" : [ 1434904257, 1400689856, 1396629056 ],"messages" : [ {"id" : 1,"content" : "Please like this post!"}, {"id" : 2,"content" : "Forza Roma!"} ]
}*/
3.5 類和JSon間的轉換:decompose, extract, write和read
case class Item(info: String, rank: Int)
case class Item2(info: String, rank: Int, name:String)
implicit val formats: Formats = DefaultFormatsval vMap=Map("info" -> "abcd", "rank" -> 123, "other" -> "dsf")
val jsonStr = write(vMap)
println(jsonStr)
//{"info":"abcd","rank":123,"other":"dsf"}val json = parse(jsonStr)
println(json)val json2 = Extraction.decompose(vMap)//可以理解為等價于parse(write(vMap))
println(json2)val json=parse(jsonStr)
//val json2=
println(json.extract[Map[String,Any]])
//Map(info -> abcd, rank -> 123, other -> dsf)
println(read[Map[String,Any]](jsonStr))//可理解為和json.extract效果一樣,但是跳過了將str轉為JValue對象的過程
//Map(info -> abcd, rank -> 123, other -> dsf)println(json.extract[Item])//case class 的字段名要和json的field一致,可少不可多與json有的field
//Item(abcd,123)
println(read[Item](jsonStr))
//Item(abcd,123)println(json.extract[Item2])//不可多于json有的field
//報錯,org.json4s.MappingException: No usable value for nameprintln(read[Item2](jsonStr))
//報錯,org.json4s.MappingException: No usable value for name
不用默認格式:(非scala基類作為父類的話,默認格式解析會出錯)
trait Animalcase class Dog(name: String) extends Animalcase class Fish(weight: Double) extends Animalcase class Animals(animals: List[Animal])implicit val formats1: Formats = DefaultFormatsval formats2: Formats = Serialization.formats(ShortTypeHints(List(classOf[Dog], classOf[Fish])))implicit val mf = manifest[Animals]val ser1 = write(Animals(Dog("pluto") :: Fish(1.2) :: Nil))(formats1)val ser2 = write(Animals(Dog("pluto") :: Fish(1.2) :: Nil))(formats2)println(ser1)//{"animals":[{"name":"pluto"},{"weight":1.2}]}println(ser2)//{"animals":[{"jsonClass":"BasicTest$Dog","name":"pluto"},{"jsonClass":"BasicTest$Fish","weight":1.2}]}println(read[Animals](ser2)(formats2, mf))//Animals(List(Dog(pluto), Fish(1.2)))println(parse(ser2).extract[Animals](formats2,mf))//Animals(List(Dog(pluto), Fish(1.2)))println( read[Animals](ser2)(formats1,mf))// 報錯//org.json4s.MappingException: No usable value for animals,No constructor for type Animal, JObject(List((jsonClass,JString(BasicTest$Dog)), (name,JString(pluto))))println( read[Animals](ser1))//等價于println( read[Animals](ser1)(formats1,mf)) ,報錯//org.json4s.MappingException: No usable value for animals No constructor for type Animal, JObject(List((name,JString(pluto))))println(parse(ser1).extract[Animals])//報錯//org.json4s.MappingException: No usable value for animals No constructor for type Animal, JObject(List((name,JString(pluto))))println(parse(ser2).extract[Animals])//報錯//org.json4s.MappingException: No constructor for type Animal, JObject(List((jsonClass,JString(BasicTest$Dog)), (name,JString(pluto))))
參考
官方教程
WORKING WITH JSON IN SCALA USING THE JSON4S LIBRARY (PART ONE)
WORKING WITH JSON IN SCALA USING THE JSON4S LIBRARY (PART TWO)
Purpose of render
in json4s