wJson 是一个Scala3 JSON库,它提供了一个简单的、直观的API,用于在Scala中处理JSON。wJson的设计参考了如下的库,并试图变得提供更为 简单的API:
wJson 基于scala3,也是我学习Scala3的一个实践项目,通过 wJson,全面熟悉一下 scala3 的语法,以及新的 Macro 系统的使用。
以 sbt 为例:
libraryDependencies += "com.github.wangzaixiang" %% "wjson" % "0.1.0"
在使用 wjson 库之前,简单引入一下:
import wjson.*
-
JSON 基本类型API
enum JsVal: case JsNull case JsBoolean(value: Boolean) case JsNumber(value: Double) case JsString(value: String) case JsArray(elements: List[JsVal]) case JsObject(fields: Map[String, JsVal])
使用 enum + ADT 对数据结构进行建模,是一种很简洁的实现方式。
-
解析JSON字符串、构造JsVal
val jsonStr = """{"name":"John","age":18}""" val jsval0 = JsParser.parse( JsonInput(jsonStr) ) // 使用 JsonParser 进行 JSON 解析 val jsval1: JsVal = JsVal.parse(jsonStr) // 使用 JsVal.parse API val jsval2: JsVal = jsonStr.parseJson // 使用扩展方法 String.parseJson val jsval3 = json"""{"name":"John","age":18}""" // 使用 json"..." 字符串插值 val jsval4 = JsObj(Map("name" -> JsString("John"), "age" -> JsNumber(18))) // 使用 JsObj 构造器 val jsval5 = JsObj("name" -> JsString("John"), "age" -> JsNumber(18)) // 使用 JsObj 构造器 val jsval6 = JsObj("name" -> "John", "age" -> 18) // 使用 JsObj 构造器 assert( jsval1 == jsval0 && jsval2 == jsval0 && jsval3 == jsval0 && jsval4 == jsval0 && jsval5 == jsval0 && jsval6 == jsval0 )
这7中方式,都可以解析出同一个 JSON 对象。
-
序列化与反序列化
case class Person(name: String, age: Int) val jsval = json"""{"name":"John","age":18}""" val person = jsval.convertTo[Person] // jsval 具有扩展方法 to[T] 可以反序列化为 T(必须是case class) val jsval2 = person.toJson // case class 具有扩展方法 toJson 可以序列化为 JsVal
wjson 的序列化和反序列化功能,它支持:
- 基本类型的序列化,包括 Byte、Short、Char、Int、Long、Float、Double、Boolean、String 的序列化
- BigInt、BigDecimal
- Option[T]: 其中 T 必须是可序列化的(递归游戏)
- List[T]、Seq[T]、Set[T]、SortedSet[T]、Map[String, T], SortedMap[String, T]、Array[T]: 其中 T 必须是可序列化的.(递归游戏)
- case class: 其中所有的字段必须是可序列化的(递归游戏)
只要符合上述的类型规则,那么就可以使用 wjson 的API:
jsVal.convertTo[T]
、(value:T).toJson
来进行对象的序列化、反序列化, 而无需编写任何的代码。相比 spray-json 来说,wjson的API要简单很多,功能要强大很多(不再需要编写Protocol, Reader, Writer)。 -
json 插值能力
val jsonStr1 = json"""{"name":"John","age":18}""" // 标准 JSON 语法 val jsonStr2 = json"{name: 'John', age:18, } " // 扩展 JSON 语法 val name = "John" val age = 18 val jsonStr3 = json"{name: $name, age: $age}" // 扩展 JSON 语法 + 插值
json"..." 字符串插值,可以让我们更加简洁的构造 JSON 对象。其除了支持标准的 JSON 语法之外,还支持扩展的javascript语法, 包括:
- 字段名,可以直接使用 name,age 等标识府,而无需加引号。(javascript语法)
- 字符串,可以直接使用单引号,或者双引号。(javascript语法)
- 数组、对象的最后一个成员后,可以添加逗号。(javascript语法)
- 使用${} 来表示插值,嵌入动态的内容。
json插值除了可以构造一个 JsVal 对象之外,还可以作为 Pattern 来使用,用于匹配一个JsVal,并从中提取出某些值。
val jsval1 = json"{name: 'John', age: 18, }" jsval match { case json"{name: $name, age: $age}" => println(name, age) // John 18 case _ => println("not match") } json"{ name: 'John', age: 18, address: { city: 'Beijing', country: 'China' }, scores: [80,90,100] }" match { case json"{ address: {city: $city}, scores:[80,$score1,$score2]}" => println(city, score1, score2) // Beijing 90 100 case _ => println("not match") }
使用 json 的插值匹配功能,可以一次性匹配一个复杂的JSON 对象,并且按照结构提取出某些值。这可以在不进行JSON反序列化的情况下,快速的 对JSON进行匹配、字段提取、数据验证工作。
-[ ] wjson 还计划提供一个更为强大的 JSON 模式匹配语言,这个目前尚且在设计之中,将在外下一个版本中提供。 -[X] JsValue.show() 方法,用于输出 JsValue 对象的字符串表示。