Go语言encoding/json库源码分析

Go语言encoding/json库源码分析

encoding/json库的意义相对其他语言来说在Golang中尤其重要,因为它还扮演着struct转换器的角色,即对于字段列表存在公共子集的不同结构体之间进行转换(如果你不知道为什么要做这件事,这是另一个话题,需要单独讨论)。目前来看使用Marshal和Unmarshal方法是最优解(不要告诉我你要对字段进行one by one的赋值,虽然这种low逼方法有时候更加有效)。

基本规则

  • 只有合法的json串才能解析(废话)
key只能是string;
map的形式必须是map[string]T,T是Go的基本类型;
Channel, complex, function 不能进行编码;
循环数据不支持;
指针会被编码成指针的值,nil是null。
  • 只有可导出(exported)的字段才能解析
即首字符大写的字段才能显示在json对象中,解析规则有如下顺序,例如一个key:Foo
1.先找tag是Foo的
2.找字段名是Foo的
3.找FOO或者FoO等
  • 只能解码在目标对象存在的字段
1. 这个特性其实是很有用的,你可以设计一个大而全的兼容结构体,当你希望从里面挑选一些字段时就可以利用它;
2. 这也意味着目标对象中unexported的字段不受Unmarshal的影响;
  • Unmarshal可以自动对对象内部的指针赋值
//这个特性是指一个struct包含其他的引用类型的字段,你不需要先创建它
//看下面的例子,对于Parents这个数组你不需要提前声明,有值就自动创建好,没值就是nil
type FamilyMember struct {
	Name    string	`json:"name"`
	Age     int	`json:"age"`
	Parents []string
}
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
var m FamilyMember
err := json.Unmarshal(b, &m)

类库解析

主要的接口和结构

  • Marshaler

核心的encode接口,为第三方提供了encode的可能性。

// Marshaler is the interface implemented by types that
// can marshal themselves into valid JSON.
type Marshaler interface {
	MarshalJSON() ([]byte, error)
}
  • Unmarshaler

同上,核心的decode接口

// Unmarshaler is the interface implemented by types
// that can unmarshal a JSON description of themselves.
// The input can be assumed to be a valid encoding of
// a JSON value. UnmarshalJSON must copy the JSON data
// if it wishes to retain the data after returning.
//
// By convention, to approximate the behavior of Unmarshal itself,
// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op.
type Unmarshaler interface {
	UnmarshalJSON([]byte) error
}
  • Decoder / Encoder:解码输入流和编码json到输出流的对象
  • UnmarshalTypeError:最主要的错误类型,表示一个json值不能转化为特定的go类型的值,还提供了其他若干Error类型,不一一列举了。
  • Number:实际上是json的number类型的包装类,解析时提供对应方法取得值
  • RawMessage:保持原本的json对象。实现了Marshaler和Unmarshaler接口,用于延迟json的解码或者预计算json的编码。前面是注释的翻译,其实就是个[]byte,delay和预计算没理解什么意思,这个类型在encode和decode过程中也没有用到。

重要方法

其中最主要的就是Marshal和Unmarshal两个方法,有很多细节需要注意。

Marshal

func Marshal(v interface{}) ([]byte, error) {

Marshal函数返回v的json编码。

数组和切片类型的值编码为json数组,但[]byte编码为base64编码字符串;nil切片编码为null;结构体编码为json对象。每一个导出字段变成该对象的一个成员,除非:

1. 字段的标签是"-"
2. 字段是空值,而其标签指定了omitempty //空值包括false、0、""、nil指针、nil接口、长度为0的数组、切片、映射。(经我测试,空值是可以正常解析并显示的,tag里加上omitempty后就会被忽略掉)

"string"选项标记一个字段在编码json时应编码为字符串。它只适用于字符串、浮点数、整数类型的字段。(这在对象转换过程中非常有用,避免了类型必须一致的尴尬,可动态调整目标对象,提高了灵活性)

id int64 `json:"id,string"`

map类型的值编码为json对象,键必须是字符串,对象的键直接使用map的key。指针类型的值编码为其指向的值,nil指针编码为null。接口类型的值编码为接口内保持的具体类型的值,nil接口编码为null。

Unmarshal

func Unmarshal(data []byte, v interface{}) error {

Unmarshal函数解析json编码的数据并将结果存入v指向的值。

要将json数据解码写入一个指针,Unmarshal函数首先处理json数据是json字面值null的情况。此时,函数将指针设为nil;否则,函数将json数据解码写入指针指向的值;如果指针本身是nil,函数会先申请一个值并使指针指向它。

要将json数据解码写入一个结构体,函数会匹配输入对象的键和Marshal使用的键(结构体字段名或者它的标签指定的键名),优先选择精确的匹配,但也接受大小写不敏感的匹配。

JSON的null值解码为go的接口、指针、切片时会将它们设为nil

Compact:将json编码的src中无用的空白字符剔除后写入dst

func Compact(dst *bytes.Buffer, src []byte) error

Indent:将json编码的调整缩进之后写入dst,说白了就是美化一下,加个缩进啥的

func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error

源码分析

接下来看看Marshal和Unmarshal具体是如何工作的,重要和需要解释的地方我都加了相应的注释。

Marshal

func Marshal(v interface{}) ([]byte, error) {
	e := &encodeState{}  //一个内部结构,其实就是Buffer的包装
        //具体将v递归转换为json串并写入结构e的buffer中
	err := e.marshal(v, encOpts{escapeHTML: true})
	if err != nil {
		return nil, err
	}
        //Buffer的Bytes方法返回,即拿到JSON串
	return e.Bytes(), nil 
}
//==> 
func (e *encodeState) marshal(v interface{}, opts encOpts) (err error) {
    //defer 错误
    //...
    e.reflectValue(reflect.ValueOf(v), opts)
}
//==> 
func valueEncoder(v reflect.Value) encoderFunc {
    //判断v是否是value,0值返回false
	if !v.IsValid() {
	    //实际上是写null
		return invalidValueEncoder
	}
	return typeEncoder(v.Type())
}
//==>
func typeEncoder(t reflect.Type) encoderFunc {
//load... 
// Compute the real encoder and replace the indirect func with it.
	f = newTypeEncoder(t, true)
//store...
}

//==> 
newTypeEncoder是核心方法递归根据类型回写buffer对应的json字符串buf.WriteString

// newTypeEncoder constructs an encoderFunc for a type.
// The returned encoder only checks CanAddr when allowAddr is true.
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
	if t.Implements(marshalerType) {
		return marshalerEncoder //优先处理nil情况
	}
	if t.Kind() != reflect.Ptr && allowAddr {
                //对可以寻址的,调用m.MarshalJSON(),即addrMarshalerEncoder方法里通过调用Addr拿到具体值,否则继续回到这个方法
		if reflect.PtrTo(t).Implements(marshalerType) {
			return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
		}
	}
	if t.Implements(textMarshalerType) {
		return textMarshalerEncoder
	}
	if t.Kind() != reflect.Ptr && allowAddr {
		if reflect.PtrTo(t).Implements(textMarshalerType) {
			return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false))
		}
	}
        //对各种类型进行encode,复合类型继续调用typeEncoder递归处理
	switch t.Kind() {
	case reflect.Bool:
		return boolEncoder
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return intEncoder
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return uintEncoder
	case reflect.Float32:
		return float32Encoder
	case reflect.Float64:
		return float64Encoder
	case reflect.String:
		return stringEncoder
	case reflect.Interface:
		return interfaceEncoder
	case reflect.Struct:
		return newStructEncoder(t)
	case reflect.Map:
		return newMapEncoder(t)
	case reflect.Slice:
		return newSliceEncoder(t)
	case reflect.Array:
		return newArrayEncoder(t)
	case reflect.Ptr:
		return newPtrEncoder(t)
	default:
		return unsupportedTypeEncoder
	} 
	//... ...
}
//通过bool的encoder看看最终的json串赋值操作:是不是也挺low逼的?
func boolEncoder(e *encodeState, v reflect.Value, opts encOpts) {
	if opts.quoted {
		e.WriteByte('"')
	}
	if v.Bool() {
		e.WriteString("true")
	} else {
		e.WriteString("false")
	}
	if opts.quoted {
		e.WriteByte('"')
	}
}

//再看一个对结构体encode的,很明显看到进行{key:value}的封装操作:
func (se *structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
	e.WriteByte('{')
	first := true
	for i, f := range se.fields {
		fv := fieldByIndex(v, f.index)
		if !fv.IsValid() || f.omitEmpty && isEmptyValue(fv) {
			continue
		}
		if first {
			first = false
		} else {
			e.WriteByte(',')
		}
		e.string(f.name, opts.escapeHTML)
		e.WriteByte(':')
		opts.quoted = f.quoted
		se.fieldEncs[i](e, fv, opts)
	}
	e.WriteByte('}')
}

Marshal的核心代码差不多就这些,没有太多玄机,基本上就是递归对对象进行json解析操作。

Unmarshal

1. 创建一个data的包装类check是否合法调用unmarshal
func Unmarshal(data []byte, v interface{}) error {
	// Check for well-formedness.
	// Avoids filling out half a data structure
	// before discovering a JSON syntax error.
	var d decodeState
	err := checkValid(data, &d.scan)
	if err != nil {
		return err
	}

	d.init(data)
	return d.unmarshal(v)
}
2. scan是一个状态机用来把byte数组分割成一个个完整的值然后调用d.value进行赋值处理
func (d *decodeState) unmarshal(v interface{}) (err error) {
	//... ...
	rv := reflect.ValueOf(v)
	if rv.Kind() != reflect.Ptr || rv.IsNil() {
		return &InvalidUnmarshalError{reflect.TypeOf(v)}
	}
        //scan是状态机,reset方法用来分割byte数组
	d.scan.reset()
	// We decode rv not rv.Elem because the Unmarshaler interface
	// test must be applied at the top level of the value.
	d.value(rv)
	return d.savedError
}
3. 主要是对不同的类型如arrayobj调用对应方法继续处理
// value decodes a JSON value from d.data[d.off:] into the value.
// it updates d.off to point past the decoded value.
func (d *decodeState) value(v reflect.Value) {
	if !v.IsValid() {
		_, rest, err := nextValue(d.data[d.off:], &d.nextscan)
		//... ...
	}
	switch op := d.scanWhile(scanSkipSpace); op {
	default:
		d.error(errPhase)
        //对复合类型继续调用d.value进行处理;
        //内部对各个元素也是使用Zero进行假赋值(没分配内存)
        // Array. Zero the rest.
	//		z := reflect.Zero(v.Type().Elem())
	//		for ; i < v.Len(); i++ {
	//			v.Index(i).Set(z)
	//		}
	case scanBeginArray:
		d.array(v)
	//对map单独进行了处理,对应子指针和map类型都使用了New分配地址,估计目的是让子对象的赋值操作正常,单个值调用了literalStore方法
        //if subv.Kind() == reflect.Ptr {
	//	if subv.IsNil() {
	//		subv.Set(reflect.New(subv.Type().Elem()))
	//	}
	//	subv = subv.Elem()
	//}
	case scanBeginObject:
		d.object(v)
    //字面量的处理,调用literalStore
	case scanBeginLiteral:
		d.literal(v)
	}
}
注意上面3个具体的decode方法都先判断实现了Unmarshaler接口的v直接调用实现的UnmarshalJSON进行处理而不走自己的流程为三方扩展提供可能性
下面的literalStore是核心方法具体将json串赋值给Value
// literalStore decodes a literal stored in item into v.
// fromQuoted indicates whether this literal came from unwrapping a
// string from the ",string" struct tag option. this is used only to
// produce more helpful error messages.
func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) {
	//...
	isNull := item[0] == 'n' // null
	//找到非空指针的value然后返回
	u, ut, pv := d.indirect(v, isNull)
	if u != nil {
		err := u.UnmarshalJSON(item)
		if err != nil {
			d.error(err)
		}
		return
	}
	//... ... 
	//核心代码,对不同类型进行value的赋值操作
	switch c := item[0]; c {
	case 'n': // null
		// The main parser checks that only true and false can reach here,
		// but if this was a quoted string input, it could be anything.
		if fromQuoted && string(item) != "null" {
			d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
			break
		}
		switch v.Kind() {
		case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
                       //重要地方:对空值这些引用类型使用了未分配地址的Zero方法
			v.Set(reflect.Zero(v.Type()))
			// otherwise, ignore null for primitives/string
		}
	case 't', 'f': // true, false
		value := item[0] == 't'
		// The main parser checks that only true and false can reach here,
		// but if this was a quoted string input, it could be anything.
		if fromQuoted && string(item) != "true" && string(item) != "false" {
			d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
			break
		}
		switch v.Kind() {
		default:
			if fromQuoted {
				d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
			} else {
				d.saveError(&UnmarshalTypeError{Value: "bool", Type: v.Type(), Offset: int64(d.off)})
			}
		case reflect.Bool:
			v.SetBool(value)
		case reflect.Interface:
			if v.NumMethod() == 0 {
				v.Set(reflect.ValueOf(value))
			} else {
				d.saveError(&UnmarshalTypeError{Value: "bool", Type: v.Type(), Offset: int64(d.off)})
			}
		}

	case '"': // string
		s, ok := unquoteBytes(item)
		if !ok {
			if fromQuoted {
				d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
			} else {
				d.error(errPhase)
			}
		}
		switch v.Kind() {
		default:
			d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.off)})
		case reflect.Slice:
			if v.Type().Elem().Kind() != reflect.Uint8 {
				d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.off)})
				break
			}
			b := make([]byte, base64.StdEncoding.DecodedLen(len(s)))
			n, err := base64.StdEncoding.Decode(b, s)
			if err != nil {
				d.saveError(err)
				break
			}
			v.SetBytes(b[:n])
		case reflect.String:
			v.SetString(string(s))
		case reflect.Interface:
			if v.NumMethod() == 0 {
				v.Set(reflect.ValueOf(string(s)))
			} else {
				d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.off)})
			}
		}

	default: // number
		if c != '-' && (c < '0' || c > '9') {
			if fromQuoted {
				d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
			} else {
				d.error(errPhase)
			}
		}
		s := string(item)
		switch v.Kind() {
		default:
			if v.Kind() == reflect.String && v.Type() == numberType {
				v.SetString(s)
				if !isValidNumber(s) {
					d.error(fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", item))
				}
				break
			}
			if fromQuoted {
				d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
			} else {
				d.error(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.off)})
			}
		case reflect.Interface:
			n, err := d.convertNumber(s)
			if err != nil {
				d.saveError(err)
				break
			}
			if v.NumMethod() != 0 {
				d.saveError(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.off)})
				break
			}
			v.Set(reflect.ValueOf(n))

		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			n, err := strconv.ParseInt(s, 10, 64)
			if err != nil || v.OverflowInt(n) {
				d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: v.Type(), Offset: int64(d.off)})
				break
			}
			v.SetInt(n)

		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
			n, err := strconv.ParseUint(s, 10, 64)
			if err != nil || v.OverflowUint(n) {
				d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: v.Type(), Offset: int64(d.off)})
				break
			}
			v.SetUint(n)

		case reflect.Float32, reflect.Float64:
			n, err := strconv.ParseFloat(s, v.Type().Bits())
			if err != nil || v.OverflowFloat(n) {
				d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: v.Type(), Offset: int64(d.off)})
				break
			}
			v.SetFloat(n)
		}
	}
}

Unmarshal的代码稍微复杂一点点,引入了一个状态机scan对[]byte进行分割,其他还是一个套路,递归,利用反射把json串写入Value中,最终还原结构体。

几个重要的reflect方法

在encode特别是decode的过程中,有几个反射方法非常重要,会直接影响json转换的结果稍后在分析一个三方库的时候会做比较,这里先抛出在Golang的反射中几个比较重要的概念。

根据类型新建一个值
// New returns a Value representing a pointer to a new zero value
// for the specified type. That is, the returned Value's Type is PtrTo(typ).
func New(typ Type) Value {
	if typ == nil {
		panic("reflect: New(nil)")
	}
	ptr := unsafe_New(typ.(*rtype))
	fl := flag(Ptr)
	return Value{typ.common().ptrTo(), ptr, fl} //注意此处和下面方法的比较
}

对一个空值进行假赋值返回值不可寻址也不能set值没有分配内存
// Zero returns a Value representing the zero value for the specified type.
// The result is different from the zero value of the Value struct,
// which represents no value at all.
// For example, Zero(TypeOf(42)) returns a Value with Kind Int and value 0.
// The returned value is neither addressable nor settable.
func Zero(typ Type) Value {
	if typ == nil {
		panic("reflect: Zero(nil)")
	}
	t := typ.common()
	fl := flag(t.Kind())
	if ifaceIndir(t) {
		return Value{t, unsafe_New(typ.(*rtype)), fl | flagIndir}
	}
	return Value{t, nil, fl} //直接赋值nil,导致这个Value不可寻址不能Set
}

这个方法提到了可寻址的概念在Marshal的newTypeEncoder方法中用到
// CanAddr reports whether the value's address can be obtained with Addr.
// Such values are called addressable. A value is addressable if it is
// an element of a slice, an element of an addressable array,
// a field of an addressable struct, or the result of dereferencing a pointer.
// If CanAddr returns false, calling Addr will panic.
func (v Value) CanAddr() bool {
	return v.flag&flagAddr != 0
}
对应的CanSet的解释即不可寻址的Value无法Set值
// CanSet reports whether the value of v can be changed.
// A Value can be changed only if it is addressable and was not
// obtained by the use of unexported struct fields.
// If CanSet returns false, calling Set or any type-specific
// setter (e.g., SetBool, SetInt) will panic.
func (v Value) CanSet() bool {
	return v.flag&(flagAddr|flagRO) == flagAddr
}

另外需要注意这个注释0值表示没值对应的方法要么false要不不可用
// The zero Value represents no value.
// Its IsValid method returns false, its Kind method returns Invalid,
// its String method returns "<invalid Value>", and all other methods panic.
// Most functions and methods never return an invalid value.
// If one does, its documentation states the conditions explicitly.
type Value struct {

总结一下上面几个方法中提出的重要概念:

  • addressable:可寻址的,表示一个Value被分配了地址空间,即真实存在的,可以取到值也可以赋值;
  • unaddressable:反之,不可寻址,没分配内存,就是个nil,无法获取无法Set值。unaddressable的Value会导致转换的json中对应键的缺失,这也是为什么我们的项目中Unmarshal没有用标准库的原因。
  • zero value:0值,就是没值,反射包中提供的若干Value的方法都不可用。

另外,通过源码也可以看出来,递归以及反射的大量使用导致转换的效率并不高,代码的质量很一般,为实现功能而写,没有经过精雕细琢(有人可能说你说不好你咋不写一个好的?老实说,我还真写不出来。。。)。有一些开源的三方lib在性能方面做了改进,可以参考。

参考

blog.golang.org/json-an

golang.org/pkg/encoding

encoding/decode.go encode.go

reflect/value.go

编辑于 2018-05-22

文章被以下专栏收录