[关闭]
@xtccc 2017-06-26T21:05:11.000000Z 字数 14332 阅读 4003

JSON

给我写信
GitHub

此处输入图片的描述

开发技巧



目录:


1. 基本用法


1.1 遍历Json中的key-value pairs

参考 Read JSON with JAVA using Google-gson library

  1. public class Student {
  2. public String name;
  3. public String age;
  4. public String home;
  5. public String gender;
  6. public Student() {
  7. }
  8. public Student(String name, String age, String home, String gender) {
  9. this.name = name;
  10. this.age = age;
  11. this.home = home;
  12. this.gender = gender;
  13. }
  14. }
  15. public static void main(String[] args) {
  16. Student t = new Student("xiao", "31", null, "");
  17. String json = obj2Json(t);
  18. System.out.println(json);
  19. System.out.println("--------------------");
  20. IterateJson(json);
  21. }
  22. public static String obj2Json(Student t) {
  23. return new Gson().toJson(t);
  24. }
  25. /**
  26. * 遍历JSON中的元素(只在第一层遍历)
  27. */
  28. public static void IterateJson(String json) {
  29. JsonParser parser = new JsonParser();
  30. JsonElement jsonElem = parser.parse(new StringReader(json));
  31. JsonObject jsonObj = (JsonObject)jsonElem;
  32. Set<Map.Entry<String, JsonElement>> ens = jsonObj.entrySet();
  33. for (Map.Entry<String, JsonElement> en : ens) {
  34. System.out.println(en.getKey() + " -> " + en.getValue());
  35. }
  36. }

运行结果为:
QQ20151117-0@2x.png-18.9kB




2. 构造JSON


2.1 Primary Date Type -> JSON

对于常见的基本数据类型(如Array, Map),可以将其直接转换为JSON。

  1. import com.google.gson.Gson
  2. val gson = new Gson()
  3. val a = new Array[Int](5)
  4. for (i <- 0 until a.size)
  5. a(i) = i * 100
  6. var json = gson.toJson(a)
  7. println(s"a is converted to $json")
  8. val b = new java.util.TreeMap[String, Array[Int]]()
  9. b.put("张三", Array.fill[Int](3)(3))
  10. b.put("李四", Array.fill[Int](4)(4))
  11. b.put("王五", Array.fill[Int](5)(5))
  12. json = gson.toJson(b)
  13. println(s"b is converted to $json")

运行结果为:
QQ20160229-0@2x.png-22.9kB


2.2 Custom Data Type -> JSON

如果是较为特殊的数据类型,例如:

  1. { "name" : "Jack",
  2. "weight": 76.6,
  3. "salaries" : {
  4. "January" : 11111.1,
  5. "February": 22222.2,
  6. "March" : 33333.3
  7. },
  8. "children" : [
  9. "Tom","Merry","Rose"
  10. ]
  11. }



我们可以使用case class来很方便地实现以上需求。

  1. /** 使用case class来定义我们的custom data type */
  2. case class Person(name: String, weight: Float,
  3. salaries: util.HashMap[String, Float],
  4. children: Array[String]) {}
  5. def Serialize_CustomTypes(): Unit = {
  6. val salaries = new util.HashMap[String, Float]()
  7. salaries.put("January" , 11111.1f)
  8. salaries.put("February" , 22222.2f)
  9. salaries.put("March" , 33333.3f)
  10. val children = Array("Tom", "Merry", "Rose")
  11. val jack = Person("Jack", 76.6f, salaries, children)
  12. val json = new Gson().toJson(jack)
  13. println(s"jack is converted to $json")
  14. }

注意: case class的定义不要放在 Serialize_CustomTypes 中,否则会报异常 java.lang.InternalError: Malformed class name




3. Spary Json


在Scala语言中,利用spray-json可以更加简单地处理JSON,参考 Scala and JSON generation

3.1 Object -> JSON

CaseClass.scala

  1. import spray.json.DefaultJsonProtocol
  2. case class Record(id: Int, description: String)
  3. case class MoreRecord(ip: String, r: Record)
  4. object MyJsonProtocol extends DefaultJsonProtocol {
  5. implicit val recordFormat = jsonFormat2(Record)
  6. implicit val moreRecordFormat = jsonFormat2(MoreRecord)
  7. }



TestSprayJson.scala

  1. import MyJsonProtocol._
  2. import spray.json._
  3. object TestSprayJson {
  4. def main(args: Array[String]): Unit = {
  5. val record = Record(1, "description here")
  6. val moreRecord = MoreRecord("127.0.0.1", record)
  7. val json: JsValue = moreRecord.toJson
  8. val jsonString = json.toString()
  9. println(jsonString)
  10. }
  11. }


3.2 在HttpRequest中转换Json

Def.scala

  1. import spray.httpx.SprayJsonSupport
  2. import spray.json.DefaultJsonProtocol
  3. case class RequestClusterParams(
  4. accessKey : String,
  5. secreteKey : String,
  6. userName : String,
  7. region : String,
  8. price : String,
  9. slaveNodes : String,
  10. instanceType : String,
  11. amiID : String,
  12. launchGroup : String,
  13. securityKey : String,
  14. securityGroup : String,
  15. subnetID : String)
  16. case class SubmitRequestResult(
  17. status : Boolean,
  18. message : String,
  19. params : RequestClusterParams )
  20. object JsonProtocol extends DefaultJsonProtocol with SprayJsonSupport{
  21. implicit val RequestClusterParamsFormat = jsonFormat12(RequestClusterParams)
  22. implicit val SubmitRequestResultFormat = jsonFormat3(SubmitRequestResult)
  23. }


SprayServer.scala

  1. import akka.actor.{Actor, ActorLogging}
  2. import spray.http.{HttpEntity, _}
  3. import spray.routing.HttpService
  4. import JsonProtocol._
  5. import spray.json._
  6. class Server extends Actor with RouteService with ActorLogging {
  7. def actorRefFactory = context
  8. def receive = runRoute(myRoute)
  9. }
  10. trait RouteService extends HttpService {
  11. this: Server =>
  12. val myRoute = { pathPrefix("v1") {
  13. path("request-path") {
  14. (post & entity(as[RequestClusterParams])) { params =>
  15. val result = SubmitRequestResult(····)
  16. complete(HttpResponse(StatusCodes.OK,
  17. HttpEntity(ContentTypes.`application/json`, result.toJson.prettyPrint.getBytes)))
  18. }
  19. }
  20. }
  21. }
  22. }

当收到的POST请求的BODY为一个JSON字符串时,会试图将其转换成对应的RequestClusterParams;当我们在程序中创建一个类型为RequestClusterParams的实例result之后,通过result.toJson.toString可以将其转换成JSON字符串。

值得注意的是,如果在定义object JsonProtocol时,没有让它扩展trait SprayJsonSupport,则可以会出现错误:

could not find implicit value for parameter um: spray.httpx.unmarshalling.FromRequestUnmarshaller[RequestClusterParams]
(post & entity(as[RequestClusterParams])) { params =>

因为trait SprayJsonSupport提供了自动的JSON marshalling/unmarshalling


3.3 JSON -> Object

  1. import spray.json.DefaultJsonProtocol
  2. import spray.json._
  3. case class CA(flag: String, list: List[Int])
  4. case class Bean(a: String, b: Int, ca: CA)
  5. object BeanJsonProtocol extends DefaultJsonProtocol {
  6. implicit val CAFormat = jsonFormat2(CA)
  7. implicit val BeanFormat = jsonFormat3(Bean)
  8. }
  9. import BeanJsonProtocol._
  10. object Deserialize extends App {
  11. val json =
  12. """
  13. { "a": "A",
  14. "b": 100,
  15. "ca" : {
  16. "flag" : "FLAG!!",
  17. "list": [1,2,3,4,5]
  18. }
  19. }"""
  20. // get Abstract Syntax Tree
  21. val ast: JsValue = json.parseJson
  22. // convert AST to object
  23. val obj: Bean = ast.convertTo[Bean]
  24. println(obj)
  25. }


3.4 处理 non case class

上面展示了怎样处理Scala中的case class,如果想处理其他的class呢?例如想处理Java实现的org.joda.time.DateTimeZone类。。。

我们需要自己为org.joda.time.DateTimeZone实现一个format,这个format里面要实现write(序列化)和read两种方法(反序列化)。

由于DateTimeFormat的核心数据是id,因此我们只需要利用id即可创建出一个新的实例。

  1. package cn.gridx.scala.lang.json.sprayjson
  2. import org.joda.time.DateTimeZone
  3. import spray.json._
  4. object DateTimeZoneFormat extends RootJsonFormat[DateTimeZone] {
  5. // 序列化
  6. def write(tz: DateTimeZone): JsValue = {
  7. JsObject("id" -> JsString(tz.getID))
  8. }
  9. // 反序列化
  10. def read(value: JsValue): DateTimeZone = {
  11. value.asJsObject.getFields("id") match {
  12. case Seq(JsString(id)) =>
  13. DateTimeZone.forID(id)
  14. case _ =>
  15. throw new RuntimeException("ERROR")
  16. }
  17. }
  18. }
  19. object JavaJsonProtocols extends DefaultJsonProtocol {
  20. implicit val __DateTimeZone = DateTimeZoneFormat
  21. }
  22. /**
  23. * Created by tao on 1/11/17.
  24. */
  25. object customization extends App {
  26. import JavaJsonProtocols._
  27. val tz = DateTimeZone.forID("America/Los_Angeles")
  28. val json = tz.toJson
  29. val jString = json.toString
  30. println("序列化后的字符为:")
  31. println(jString)
  32. println("\n反序列化的结果为:")
  33. val ast = jString.parseJson
  34. val obj = ast.convertTo[DateTimeZone]
  35. println(obj)
  36. }



运行结果为

序列化后的字符为:
{"id":"America/Los_Angeles"}

反序列化的结果为:
America/Los_Angeles



也可以通过json4s来实现对non-case class的序列化和反序列化
参考 Serializing non-supported types

  1. import org.json4s._
  2. import org.json4s.native.Serialization.{write, writePretty}
  3. class A(val a1: String, val a2: Int, val a3: Option[Boolean]) {
  4. }
  5. class SA extends CustomSerializer[A](format => (
  6. { // deserialize
  7. case JObject(JField("a1", JString(a1)) :: JField("a2", JInt(a2))
  8. :: JField("a3", JObject(a3)) :: Nil ) =>
  9. new A(a1, a2.intValue(),
  10. if (a3 == JNothing) None else Some(a3.asInstanceOf[Boolean]))
  11. },
  12. { // srialize
  13. case a: A =>
  14. JObject(JField("a1", JString(a.a1)) ::
  15. JField("a2", JInt(a.a2)) ::
  16. JField("a3", if (a.a3.isEmpty) JNothing else JBool(a.a3.get)) ::
  17. Nil)
  18. }
  19. )
  20. )
  21. object TestSerialize {
  22. def main(args: Array[String]): Unit = {
  23. implicit val formats = DefaultFormats + new SA()
  24. val a = new A("AAAA", 100, None)
  25. println(write(a))
  26. println(writePretty(a))
  27. }
  28. }




4. GSON


4.1. 自定义Serializer

4.1.1 重命名Field Name

在将一个class转换成json时,key一般就是class field的名字。如果希望将key进行自定义,那么可以通过@SerializedName的方式来实现 :

  1. public class ST {
  2. @SerializedName("my name")
  3. public String Name;
  4. @SerializedName("my age")
  5. public int Age;
  6. public ST(String name, int age) {
  7. this.Name = name;
  8. this.Age = age;
  9. }
  10. };
  11. String json = new Gson().toJson(new ST("Jack Tomcat", 300));

生成的json是:

    {"my name":"Jack Tomcat","my age":300}


4.2 使用JsonSerializer

参考



重命名field name是非常简单的需求,我们可以通过JsonSerializer来实现对一个给定class定义更加复杂的序列化机制,使得在将该class转化为JSON串时,能够自定义其格式和内容(包括:rename fields, add/delete fields, format contents等)。

  1. import java.lang.reflect.Type
  2. import com.google.gson._
  3. val car = Car("BMW", "X5", 3.0F, 88F, Array[String]("张三", "李四", "王二"), false)
  4. val gsonBuilder = new GsonBuilder
  5. // 为该class注册自定义的json serializer
  6. gsonBuilder.registerTypeAdapter(classOf[Car], new CarSerializer)
  7. gsonBuilder.setPrettyPrinting
  8. val gson = gsonBuilder.create
  9. val json = gson.toJson(car)
  10. println(json)
  11. // 给定的class
  12. case class Car(brand: String, series: String, displacement: Float,
  13. price: Float, owners: Array[String], newCar: Boolean)
  14. // 为类Car自定义json serializer
  15. class CarSerializer extends JsonSerializer[Car] {
  16. override def serialize(car: Car, typeOfSrc: Type,
  17. context: JsonSerializationContext): JsonElement = {
  18. val ret = new JsonObject
  19. ret.addProperty("品 牌", car.brand)
  20. ret.addProperty("是否新车", if (car.newCar) "Yes" else "No")
  21. ret.addProperty("历任车主数量", car.owners.size)
  22. val jsonArr = new JsonArray
  23. for (owner <- car.owners)
  24. jsonArr.add(new JsonPrimitive(owner))
  25. ret.add("车主 (包含二手车主)", jsonArr)
  26. val gson = new Gson()
  27. // 加入Map类型的项
  28. // `JsonObject#add` 可以加入一个 `JsonElement` 类型的property
  29. // 借此, 可以加入map等复杂类型
  30. val map = new util.HashMap[String, Double]()
  31. map.put("人民币", car.price)
  32. map.put("美 元", car.price/8)
  33. map.put("英 镑", car.price/10.4)
  34. ret.add("价 格", gson.toJsonTree(map))
  35. ret
  36. }
  37. }



输出为:

  1. {
  2. "品 牌": "BMW",
  3. "是否新车": "No",
  4. "历任车主数量": 3,
  5. "车主 (包含二手车主)": [
  6. "张三",
  7. "李四",
  8. "王二"
  9. ],
  10. "价 格": {
  11. "人民币": 880000.0,
  12. "英 镑": 84615.38461538461,
  13. "美 元": 110000.0
  14. }
  15. }




#




5. JSON4S


JSON4S是最灵活、最强大的json工具,值得一看。

参考

5.1 序列化


5.2 反序列化

  1. import org.json4s._
  2. import org.json4s.jackson.JsonMethods
  3. implicit val format = org.json4s.DefaultFormats
  4. val obj = JsonMethods.parse("""["a","b"]""").extract[List[String]]
  5. println(obj)



5.3 Field Serializer

有时,一个case class里面的某个field是自定义的类型,没有现成的serializer,我们可以为它单独添加一个serializer。

例如,DateTimeZone没有现成的serializer,如果它是某个class的field,则该class无法直接被json4s序列化:

  1. import org.joda.time.DateTimeZone
  2. import org.json4s._
  3. import org.json4s.jackson.Json
  4. /**
  5. * Created by tao on 4/10/17.
  6. */
  7. object SerializeFields {
  8. implicit val formats = org.json4s.DefaultFormats
  9. val JSON = Json(formats)
  10. def main(args: Array[String]): Unit = {
  11. val req = GlobalDataRequest("A", 100, DateTimeZone.forID("America/Los_Angeles"))
  12. val json: String = JSON.writePretty(req)
  13. println(json)
  14. val x = JSON.parse(json).extract[GlobalDataRequest]
  15. println(x)
  16. }
  17. }
  18. case class GlobalDataRequest(a: String, b: Int, c: DateTimeZone)

运行结果:image_1bedl25tt1kb1lr04rq1tdc1otgg.png-470.6kB


现增加一个field serializer:

  1. import org.joda.time.DateTimeZone
  2. import org.json4s._
  3. import org.json4s.jackson.Json
  4. object SerializeFields {
  5. implicit val formats = org.json4s.DefaultFormats + DateTimeZoneSerializer
  6. val JSON = Json(formats)
  7. def main(args: Array[String]): Unit = {
  8. val req = GlobalDataRequest("A", 100, DateTimeZone.forID("America/Los_Angeles"))
  9. val json: String = JSON.writePretty(req)
  10. println(json)
  11. val x = JSON.parse(json).extract[GlobalDataRequest]
  12. println(x)
  13. }
  14. }
  15. /**
  16. * 这就是field serializer
  17. */
  18. case object DateTimeZoneSerializer extends CustomSerializer[DateTimeZone](format => (
  19. {
  20. case JString(s) => DateTimeZone.forID(s)
  21. case JNull => null
  22. },
  23. {
  24. case tz: DateTimeZone => JString(tz.getID)
  25. }
  26. ))
  27. case class GlobalDataRequest(a: String, b: Int, c: DateTimeZone)

现在可以通过json4s正常地对GlobalDataRequest实现序列化和反序列化。


5.4 对枚举(enum)实现序列化/反序列化

对Scala Enumeration的序列化和反序列化不能通过自定义的Filed Serializer来实现,而是需要借助于org.json4s.ext.EnumNameSerializer

参考: Serialize and Deserialize scala enumerations or case objects using json4s

  1. import org.json4s._
  2. import org.json4s.ext.EnumNameSerializer
  3. import org.json4s.jackson.Json
  4. import org.json4s.jackson.JsonMethods._
  5. object TestEnumeration {
  6. implicit lazy val formats = org.json4s.DefaultFormats + new EnumNameSerializer(WeekDay)
  7. val JSON = Json(formats)
  8. def main(args: Array[String]): Unit = {
  9. testSerialization()
  10. testDeserialization()
  11. }
  12. def testSerialization(): Unit = {
  13. val today = WeekDay.Mon
  14. val json = JSON.writePretty(Map("TODAY" -> today))
  15. println(json)
  16. }
  17. def testDeserialization(): Unit = {
  18. val json =
  19. """{
  20. | "TODAY" : "Monday"
  21. |}
  22. """.stripMargin
  23. val today = parse(json).extract[Map[String, WeekDay]]
  24. println(today)
  25. }
  26. }
  27. ///////////////////////////////////////////////////////////
  28. object WeekDay extends Enumeration {
  29. type WeekDay = Value
  30. val Mon = Value(0, "Monday")
  31. val Tue = Value(1, "Tuesday")
  32. val Wed = Value(2, "Wednesday")
  33. val Thu = Value(3, "Tuesday")
  34. val Fri = Value(4, "Friday")
  35. val Sat = Value(5, "Saturday")
  36. val Sun = Value(6, "Sunday")
  37. }


5.5 只从JSON中取出某一个field

假如有这样一个JSON string:

  1. val str = """
  2. |{
  3. | "error": "not_authorised",
  4. | "reason": "User not authorised to access virtual host",
  5. | "extra" : {
  6. | "a" : "A",
  7. | "b" : "B"
  8. | }
  9. |
  10. |} """.stripMargin

我们只想取出其中的extra,其他的不关心,怎么做?

  1. val json = parse(str)
  2. val value_error : JValue = json\\"error"
  3. val value_errors: JValue = json\\"errors"
  4. val value_extra : JValue = json\\"extra"
  5. println("field error : " + value_error.extractOpt[String])
  6. println("field reason : " + value_errors.extractOpt[String])
  7. println("field extra : " + value_extra.extractOpt[Map[String, String]])

输出结果为:

field error  : Some(not_authorised)
field reason : None
field extra  : Some(Map(a -> A, b -> B))




同名field的带来的问题

对于下面的JSON:

  1. {
  2. "meta": {
  3. "code": 200
  4. },
  5. "data": {
  6. "results": {
  7. "status": "success",
  8. "responseTime": 3452,
  9. "billStatus": [
  10. {
  11. "status": "success"
  12. },
  13. {
  14. "status": "success"
  15. }
  16. ]
  17. }
  18. }
  19. }

使用下面的代码没法取出 data\results\status:

  1. val jVal = org.json4s.jackson.JsonMethods.parse(respBody)
  2. val status = (jVal \\ "data" \\ "results" \\ "status").extractOpt[String]
  3. println(s"status = $status")

输出是 None.

解决方法
使用单反斜杠(\),而不是双反斜杠(\):

  1. val jVal = org.json4s.jackson.JsonMethods.parse(respBody)
  2. val status = (jVal \ "data" \ "results" \ "status").extractOpt[String]
  3. println(s"status = $status")



6. JSON4S对特殊类型的定制序列化


对于有些类型,JSON4S不能自动地正确实现序列化,例如HashMap和java.util.TreeMap,举例:

  1. object TestJson {
  2. implicit val formats = org.json4s.DefaultFormats
  3. def main(args: Array[String]): Unit = {
  4. val tree = new java.util.TreeMap[Any, Any]()
  5. tree.put("bill_category", "CARE_Combined")
  6. tree.put("bill_presentation_category", "audit")
  7. tree.put("bill_super_category", 12)
  8. val in1 = HashMap("y" -> tree)
  9. val in2 = HashMap(tree -> "x")
  10. println("tree = " + org.json4s.jackson.Serialization.write(tree))
  11. println("in1 = " + org.json4s.jackson.Serialization.write(in1))
  12. println("in2 = " + org.json4s.jackson.Serialization.write(in2))
  13. }
  14. }

运行结果:
image_1bji7bjej966tecn11agu18ag9.png-101.1kB


我们需要对Map和TreeMap实现自定义的序列化

  1. object MySerializer extends CustomSerializer[Any] (thisFormat =>
  2. (
  3. {
  4. case x =>
  5. throw new RuntimeException("不感兴趣")
  6. },
  7. {
  8. case m: scala.collection.immutable.HashMap[Any, Any] =>
  9. implicit val formats = org.json4s.DefaultFormats
  10. JObject(
  11. m.map({
  12. case (k, v) =>
  13. JField(k.toString, Extraction.decompose(v)(thisFormat))
  14. }).toList
  15. )
  16. case m: scala.collection.mutable.HashMap[Any, Any] =>
  17. implicit val formats = org.json4s.DefaultFormats
  18. JObject(
  19. m.map({
  20. case (k, v) =>
  21. JField(k.toString, Extraction.decompose(v)(thisFormat))
  22. }).toList
  23. )
  24. case m: java.util.TreeMap[Any, Any] =>
  25. implicit val formats = org.json4s.DefaultFormats
  26. JObject({
  27. val con = ArrayBuffer[JField]()
  28. val it = m.keySet().iterator()
  29. while (it.hasNext) {
  30. val key = it.next()
  31. con.append(JField(key.toString, Extraction.decompose(m.get(key))(thisFormat)))
  32. }
  33. con.toList
  34. }
  35. )
  36. }
  37. )
  38. )
  39. object TestJson {
  40. implicit val formats = org.json4s.DefaultFormats + MySerializer
  41. def main(args: Array[String]): Unit = {
  42. ... // 相同的测试内容
  43. ...
  44. }
  45. }

运行结果:

  1. tree = {"bill_category":"CARE_Combined","bill_presentation_category":"audit","bill_super_category":12}
  2. in1 = {"y":{"bill_category":"CARE_Combined","bill_presentation_category":"audit","bill_super_category":12}}
  3. in2 = {"{bill_category=CARE_Combined, bill_presentation_category=audit, bill_super_category=12}":"x"}




添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注