buger/jsonparser 源码简析

最近比较闲,学习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

一边调试一边梳理流程

选定一个入口,
buger/jsonparser 源码简析

从Get方法进去,可以走到internalGet-->searchKeys
buger/jsonparser 源码简析

进到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变量等。

buger/jsonparser 源码简析

上一篇:Cocos2d-x Lua中生命周期函数


下一篇:使用IDEA上传项目到码云上(Gitee)