乐趣区

关于goland:用面向对象的方式操作-JSON-甚至还能做四则运算-JSON-库

前言

在之前实现的 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

因为性能相似,所以我参考了 tidwallAPI 但去掉一些我感觉临时用不上的个性,并调整了一点语法。

以后这个版本只能通过确定的 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

退出移动版