最近用go写web api时遇到一个关于[]byte类型JSON序列化的问题,做一下记录。

事情的起因是我要做一个web服务,用来保存包含任何JSON内容的记录,并正常读出。这个需求其实很简单,由于JSON字段都是文本,可以直接读写JSON文本就可以了。但是考虑到JSON文本会涉及到转义的问题,我采用直接存储[]byte类型的数据。数据库也都支持这种类型的存储。模型结构大概是这样:

type record struct{
    Content []byte`json:"content"`
}

用这个接口对于创建请求来说,将http body绑定到这个结构没有问题,但是读取的时候如果返回这个结构,由于Content类型是[]byte,最终的响应里Content内容其实是被base64编码的字符串。经过同事的提点,将[]byte类型换成json.RawMessage就好了。可是json.RawMessage其实只是[]byte的另一个名字,为何会有这样的区别呢?带着疑问看了下go的json序列化细节才恍然大悟。

go内置的encoding/json包序列化JSON逻辑的核心在encoding/json/encode.go文件的newTypeEncoder函数:


// 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.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(marshalerType) {
		return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
	}
	if t.Implements(marshalerType) {
		return marshalerEncoder
	}
	if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(textMarshalerType) {
		return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false))
	}
	if t.Implements(textMarshalerType) {
		return textMarshalerEncoder
	}

	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.Pointer:
		return newPtrEncoder(t)
	default:
		return unsupportedTypeEncoder
	}
}

简而言之就是代码会通过反射获取要被序列化成json的对象的类型,然后newTypeEncoder中根据不同的类型返回不同的encoderFunc。而[]byte类型和json.RawMessage类型不同的处理逻辑就在这里分叉。对[]byte类型,会被识别成reflect.Slice, return newSliceEncoder(t)。我们接着看:

func newSliceEncoder(t reflect.Type) encoderFunc {
	// Byte slices get special treatment; arrays don't.
	if t.Elem().Kind() == reflect.Uint8 {
		p := reflect.PointerTo(t.Elem())
		if !p.Implements(marshalerType) && !p.Implements(textMarshalerType) {
			return encodeByteSlice
		}
	}
	enc := sliceEncoder{newArrayEncoder(t)}
	return enc.encode
}

在这里, 对于[]byte会直接返回encodeByteSlice,这是一种特殊处理,其他类型的slice不会会返回sliceEncoder。而在encodeByteSlice中会对于[]byte进行base64编码处理。

不要忘了,json.RawMessage底层也是[]byte类型,为什么它不会呢?因为它实现了json.Marshaler, 所以在newTypeEncoder中会返回marshalerEncoder, 而这里的实现对于[]byte来说处理有所不同:

func marshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) {
	if v.Kind() == reflect.Pointer && v.IsNil() {
		e.WriteString("null")
		return
	}
	m, ok := v.Interface().(Marshaler)
	if !ok {
		e.WriteString("null")
		return
	}
	b, err := m.MarshalJSON()
	if err == nil {
		// copy JSON into buffer, checking validity.
		err = compact(&e.Buffer, b, opts.escapeHTML)
	}
	if err != nil {
		e.error(&MarshalerError{v.Type(), err, "MarshalJSON"})
	}
}

首先会调用类型自己的MarshalJSON,而json.RawMessageMarshalJSON实现逻辑是直接返回[]bytemarshalerEncoder中会对返回的[]byte进行compact处理。

一切真相大白,虽然都是[]byte类型,但由于encoding/json包中的特殊处理,json.RawMessage返回的是经过compact的字符串而不是base64编码的字符串。