共计 3418 个字符,预计需要花费 9 分钟才能阅读完成。
前言
在之前实现的 JSON
解析器中过后只实现了将一个 JSON 字符串转换为一个 JSONObject
,并没有将其映射为一个具体的 struct
;如果想要获取值就须要先做断言将其转换为 map
或者是切片再来获,会比拟麻烦。
decode, err := gjson.Decode(`{"glossary":{"title":"example glossary","age":1}}`)
assert.Nil(t, err)
glossary := v["glossary"].(map[string]interface{})
assert.Equal(t, glossary["title"], "example glossary")
assert.Equal(t, glossary["age"], 1)
但其实转念一想,局部场景咱们甚至咱们只须要拿到 JSON
中的某个字段的值,这样还须要先申明一个 struct
会略显麻烦。
通过查问发现曾经有了一个相似的库来解决该问题,https://github.com/tidwall/gjson 并且 star 数还很多(甚至名字都是一样的😂),阐明这样的需要大家还是很强烈的。
于是我也打算减少相似的性能,应用形式如下:
最初还加上了一个四则运算的性能。
面向对象的形式操作 JSON
因为性能相似,所以我参考了 tidwall
的 API
但去掉一些我感觉临时用不上的个性,并调整了一点语法。
以后这个版本只能通过确定的 key
加上 .
点符号拜访数据,如果是数组则用 [index]
的形式拜访下标。[]
符号拜访数组我感觉要更合乎直觉一些。
以下是一个蕴含多重嵌套 JSON
的拜访示例:
str := `
{
"name": "bob",
"age": 20,
"skill": {
"lang": [
{
"go": {
"feature": [
"goroutine",
"channel",
"simple",
true
]
}
}
]
}
}`
name := gjson.Get(str, "name")
assert.Equal(t, name.String(), "bob")
age := gjson.Get(str, "age")
assert.Equal(t, age.Int(), 20)
assert.Equal(t, gjson.Get(str,"skill.lang[0].go.feature[0]").String(), "goroutine")
assert.Equal(t, gjson.Get(str,"skill.lang[0].go.feature[1]").String(), "channel")
assert.Equal(t, gjson.Get(str,"skill.lang[0].go.feature[2]").String(), "simple")
assert.Equal(t, gjson.Get(str,"skill.lang[0].go.feature[3]").Bool(), true)
这样的语法应用集体感觉还是满合乎直觉的,置信对使用者来说也比较简单。
返回值参考了 tidwall
应用了一个 Result
对象,它提供了多种办法能够不便的获取各种类型的数据
func (r Result) String() string
func (r Result) Bool() bool
func (r Result) Int() int
func (r Result) Float() float64
func (r Result) Map() map[string]interface{}
func (r Result) Array() *[]interface{}
func (r Result) Exists() bool
比方应用 Map()/Array()
这两个函数能够将 JSON
数据映射到 map
和切片中,当然前提是传入的语法返回的是一个非法 JSONObject
或数组。
实现原理
在实现之前须要先定义一个根本语法,次要反对以下四种用法:
- 单个
key
的查问:Get(json,"name")
- 嵌套查问:
Get(json,"obj1.obj2.obj3.name")
- 数组查问:
Get(json,"obj.array[0]")
- 数组嵌套查问:
Get(json,"obj.array[0].obj2.obj3[1].name")
语法很简略,合乎咱们日常接触到语法规定,这样便能够拜访到 JSON
数据中的任何一个值。
其实实现过程也不简单,咱们曾经在上一文中实现将 JSON
字符串转换为一个 JSONObject
了。
这次只是额定再解析方才定义的语法为 token
,而后解析该 token
的同时再从生成好的 JSONObject
中获取数据。
最初在解析完 token
时拿到的 JSONObject
数据返回即可。
咱们以这段查问代码为例:
首先第一步是对查问语法做词法剖析,最终失去下图的 token
。
在词法剖析过程中也能够做简略的语法校验;比方如果蕴含数组查问,并不是以 ]
符号结尾时就抛出语法错误。
接着咱们遍历语法的 token。如下图所示:
每当遍历到 token
类型为 Key
时便从以后的 JSONObject 对象中获取数据,并用获取到的值替笼罩为以后的 JSONObject。
其中每当遇到 .
[
]
这样的 token 时便消耗掉,直到咱们将 token 遍历结束,这时将以后 JSONObject
返回即可。
在遍历过程中当遇到非法格局时,比方 obj_list[1.]
便会返回一个空的 JSONObject
。
语法校验这点其实也很容易办到,因为依据咱们的语法规定,Array
中的 index
后肯定紧接的是一个 EndArray
,只有不是一个 EndArray
便能晓得语法不非法了。
有趣味的能够看下解析过程的源码:
https://github.com/crossoverJie/gjson/blob/cfbca51cc9bc0c77e6cb9c9ad3f964b2054b3826/json.go#L46
对 JSON 做四则运算
str := `{"name":"bob", "age":10,"magic":10.1, "score":{"math":[1,2]}}`
result := GetWithArithmetic(str, "(age+age)*age+magic")
assert.Equal(t, result.Float(), 210.1)
result = GetWithArithmetic(str, "(age+age)*age")
assert.Equal(t, result.Int(), 200)
result = GetWithArithmetic(str, "(age+age) * age + score.math[0]")
assert.Equal(t, result.Int(), 201)
result = GetWithArithmetic(str, "(age+age) * age - score.math[0]")
assert.Equal(t, result.Int(), 199)
result = GetWithArithmetic(str, "score.math[1] / score.math[0]")
assert.Equal(t, result.Int(), 2)
最初我还扩大了一下语法,能够反对对 JSON
数据中的整形 (int、float)
做四则运算,尽管这是一个小众需要,但做完我感觉还挺有意思的,目前在市面上我还没发现有相似性能的库,可能和小众需要无关🤣。
其中外围的四则运算逻辑是由之前写的脚本解释器提供的:
https://github.com/crossoverJie/gscript
独自提供了一个函数,传入一个四则运算表达式返回计算结果。
因为上一版本还不反对 float,所以这次专门适配了一下。
限于篇幅,更多对于这个四则运算的实现逻辑会在前面持续分享。
总结
至此算是我第一次利用编译原理的常识解决了一点特定畛域问题,在大学以及工作这些年始终感觉编译原理比拟浅近,所以心田始终是抗拒的,但通过这段时间的学习和实际缓缓的也把握到了一点门道。
不过目前也只是冰山一角,前面的编译原理后端更是要波及到计算机底层常识,所以仍然任重而道远。
已上都是题外话,针对于这个库我也会长期保护;为了能达到生产的应用要求,尽量进步了单测覆盖率,目前是 98%。
也欢送大家应用,提 bug🐛。
前面会持续优化,比方反对转义字符、进步性能等。
感兴趣的敌人请继续关注:
https://github.com/crossoverJie/gjson