最近比较闲,学习golang。自己写了一个json 生成器,打算写一个json解析器,一时没啥思路。去github上查找了一下go 的json工具,发现了jsonparser
这个工具。于是搞到自己的项目中,把玩调试了一番,简单梳理一下其内部是如何解析json的。
版本:github.com/buger/jsonparser v1.1.1
jsonparser 的简介
根据README.md上面的介绍,他比json标准库10倍,并且在解析的过程中,不分配内存。我们先来运行一个例子玩玩。
package main
import (
"fmt"
"github.com/buger/jsonparser"
)
func main() {
data := []byte(`{
"person": {
"name": {
"first": "Leonid",
"last": "Bugaev",
"fullName": "Leonid Bugaev"
},
"github": {
"handle": "buger",
"followers": 109
},
"avatars": [
{ "url": "https://avatars1.githubusercontent.com/u/14009?v=3&s=460", "type": "thumbnail" }
]
},
"company": {
"name": "Acme"
}
}`)
// You can specify key path by providing arguments to Get function
v,dataType,offset,err:=jsonparser.Get(data, "person", "name", "first")
if err!=nil{
panic(err)
}
fmt.Println(string(v))
fmt.Println(dataType.String())
fmt.Println(offset)fmt.Println("-------------------------")
//// There is `GetInt` and `GetBoolean` helpers if you exactly know key data type
//jsonparser.GetInt(data, "person", "github", "followers")
//
//// When you try to get object, it will return you []byte slice pointer to data containing it
//// In `company` it will be `{"name": "Acme"}`
v1,dataType1,offset1,err1:=jsonparser.Get(data, "person","github")
if err1!=nil{
panic(err1)
}
fmt.Println(string(v1))
fmt.Println(dataType1.String())
fmt.Println(offset1)
}
//输出的结果是:
//Leonid
//string
//50
//-------------------------
//{
// "handle": "buger",
// "followers": 109
// }
//object
//179
一边调试一边梳理流程
选定一个入口,
从Get方法进去,可以走到internalGet-->searchKeys
进到searchKeys中,看代码发现此时它会一个byte一个byte的遍历 data,然后判断每个byte的值是否为 " { [ :
等符号,并且在遍历data的时候,会先创建变量来记录 level (这个level,我的理解就是记录 {{ }} 像这样的括号的层级的),然后在匹配到 { }
的时候进行level操作。但是它缺少了空格的匹配,这样会导致空循环。
func searchKeys(data []byte, keys ...string) int {
keyLevel := 0 //记录key的层级
level := 0 //记录 { 的层级
i := 0
ln := len(data)
lk := len(keys) //key 的层级,如上的例子就是 person -> name -> first 这样一层一层的查找值的
lastMatched := true
if lk == 0 {
return 0
}
var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
for i < ln {
switch data[i] {
case ‘"‘:
i++
keyBegin := i
...
case ‘{‘:
// in case parent key is matched then only we will increase the level otherwise can directly
// can move to the end of this block
if !lastMatched {
end := blockEnd(data[i:], ‘{‘, ‘}‘)
if end == -1 {
return -1
}
i += end - 1
} else {
level++
}
case ‘}‘:
level--
if level == keyLevel {
keyLevel--
}
case ‘[‘:
// If we want to get array element by index
if keyLevel == level && keys[level][0] == ‘[‘ {
var keyLen = len(keys[level])
if keyLen < 3 || keys[level][0] != ‘[‘ || keys[level][keyLen-1] != ‘]‘ {
return -1
....
} else {
// Do not search for keys inside arrays
if arraySkip := blockEnd(data[i:], ‘[‘, ‘]‘); arraySkip == -1 {
return -1
} else {
i += arraySkip - 1
}
}
case ‘:‘: // If encountered, JSON data is malformed
return -1
}
i++
}
return -1
}
继续看匹配到 { }
时,只是进行level的记录操作,没有其他动作。
case ‘{‘:
// in case parent key is matched then only we will increase the level otherwise can directly
// can move to the end of this block
if !lastMatched { //如果是最后的一个
end := blockEnd(data[i:], ‘{‘, ‘}‘)
if end == -1 {
return -1
}
i += end - 1
} else {
level++
}
case ‘}‘:
level--
if level == keyLevel {
keyLevel--
}
当匹配到 " 时,说明已经遍历到key或者value了,
case ‘"‘:
i++
keyBegin := i //记录data的index,这里是截取key的起始位置
strEnd, keyEscaped := stringEnd(data[i:]) // stringEnd方法来返回字符串结束的位置,见后续内容
if strEnd == -1 {
return -1
}
i += strEnd // 更新 i
keyEnd := i - 1 //得到key的结束位置
valueOffset := nextToken(data[i:]) // nextToken 就是空格,换行符等过滤 见后续内容
if valueOffset == -1 {
return -1
}
i += valueOffset //更新 i
// if string is a key
if data[i] == ‘:‘ { //接下来的是 : 表示此时的string 为key
if level < 1 {
return -1
}
key := data[keyBegin:keyEnd] //截取data,得到key的值
// for unescape: if there are no escape sequences, this is cheap; if there are, it is a
// bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize
var keyUnesc []byte
if !keyEscaped {
keyUnesc = key //将key 赋值给 keyUnesc
} else if ku, err := Unescape(key, stackbuf[:]); err != nil {
return -1
} else {
keyUnesc = ku
}
if level <= len(keys) { //level的层级还在keys 内部时
if equalStr(&keyUnesc, keys[level-1]) { //比较 keyUnesc 是否和keys中按顺序的key相等
lastMatched = true //标记此次匹配成功,
// if key level match
if keyLevel == level-1 { //判断key的层级和level的层级是否正常
keyLevel++ //正常则表示可以匹配第准备去匹配 keys 的下一个了
// If we found all keys in path
if keyLevel == lk { //如果相等,表示已经找到了所有的keys 了
return i + 1
}
}
} else {
lastMatched = false
}
} else {
return -1
}
} else {
i--
}
继续查看上面的 stringEnd 方法
func stringEnd(data []byte) (int, bool) { //接受传递过来的data
escaped := false
for i, c := range data {
if c == ‘"‘ { //此时 i 的位置为 "xxx" 的末尾
if !escaped { //
return i + 1, false //在本例子中,从此处返回
} else {
j := i - 1 // 此时 j 的位置为 "xxx 的末尾,相当于去掉了 "
for {
if j < 0 || data[j] != ‘\\‘ {
return i + 1, true // even number of backslashes
}
j--
if j < 0 || data[j] != ‘\\‘ {
break // odd number of backslashes
}
j--
}
}
} else if c == ‘\\‘ {
escaped = true
}
}
return -1, escaped
}
跳过 ‘ ‘, ‘\n‘, ‘\r‘, ‘\t‘ 这些byte
// Find position of next character which is not whitespace
func nextToken(data []byte) int {
for i, c := range data {
switch c {
case ‘ ‘, ‘\n‘, ‘\r‘, ‘\t‘:
continue
default:
return i
}
}
return -1
}
当查到了符合层级的key后就回到了internalGet
func internalGet(data []byte, keys ...string) (value []byte, dataType ValueType, offset, endOffset int, err error) {
if len(keys) > 0 {
if offset = searchKeys(data, keys...); offset == -1 { //查到了符合层级的key,返回offset
return nil, NotExist, -1, -1, KeyPathNotFoundError
}
}
// Go to closest value
nO := nextToken(data[offset:]) // 跳过 ‘ ‘, ‘\n‘, ‘\r‘, ‘\t‘ 这些byte
if nO == -1 {
return nil, NotExist, offset, -1, MalformedJsonError
}
offset += nO //更新offset
value, dataType, endOffset, err = getType(data, offset) //开始读取value的值,并返回dataType
if err != nil {
return value, dataType, offset, endOffset, err
}
// Strip quotes from string values
if dataType == String {
value = value[1 : len(value)-1]
}
return value[:len(value):len(value)], dataType, offset, endOffset, nil
}
进入getType,读取value的值。
func getType(data []byte, offset int) ([]byte, ValueType, int, error) {
var dataType ValueType
endOffset := offset
// if string value
if data[offset] == ‘"‘ { // 匹配 "
dataType = String
if idx, _ := stringEnd(data[offset+1:]); idx != -1 { //开始匹配下一个 " 然后返回index
endOffset += idx + 1 //确定了index就直接走到return了
} else {
return nil, dataType, offset, MalformedStringError
}
} else if data[offset] == ‘[‘ { // if array value
dataType = Array
// break label, for stopping nested loops
endOffset = blockEnd(data[offset:], ‘[‘, ‘]‘)
if endOffset == -1 {
return nil, dataType, offset, MalformedArrayError
}
endOffset += offset
} else if data[offset] == ‘{‘ { // if object value
dataType = Object
// break label, for stopping nested loops
endOffset = blockEnd(data[offset:], ‘{‘, ‘}‘)
if endOffset == -1 {
return nil, dataType, offset, MalformedObjectError
}
endOffset += offset
} else {
// Number, Boolean or None
end := tokenEnd(data[endOffset:])
if end == -1 {
return nil, dataType, offset, MalformedValueError
}
value := data[offset : endOffset+end]
switch data[offset] {
case ‘t‘, ‘f‘: // true or false
if bytes.Equal(value, trueLiteral) || bytes.Equal(value, falseLiteral) {
dataType = Boolean
} else {
return nil, Unknown, offset, UnknownValueTypeError
}
case ‘u‘, ‘n‘: // undefined or null
if bytes.Equal(value, nullLiteral) {
dataType = Null
} else {
return nil, Unknown, offset, UnknownValueTypeError
}
case ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘, ‘-‘:
dataType = Number
default:
return nil, Unknown, offset, UnknownValueTypeError
}
endOffset += end
}
return data[offset:endOffset], dataType, endOffset, nil // 通过切片截取数据,返回
}
如上就返回了指定的value 。
总结
jsonparser 主要是通过遍历给定的json字节数组,然后按层级来查找和匹配 { " : 等符号,然后通过数组切片截取来取值。它主要是维护了几个变量,例如层级变量、offset变量等。