本文最后更新于:2023年8月17日 凌晨
前言
开发中偶然发现,结构体中声明了[]byte类型的字段,json marshal后,[]byte类型的字段会被base64编码,下面通过一个例子来找找base64编码的原因
排查过程
例子
package main
import (
	"encoding/json"
	"testing"
)
type Reply struct {
	Name    string
	Content []byte
}
func TestJSONMarshal(t *testing.T) {
	var r = Reply{
		Name:    "lam",
		Content: []byte("pongpong"),
	}
	res, _ := json.Marshal(&r)
	t.Log(string(res))
}输出
=== RUN   TestJSONMarshal
    client_test.go:26: {"Name":"lam","Content":"cG9uZ3Bvbmc="}
--- PASS: TestJSONMarshal (0.00s)
PASS跟着debug进入encoding/json 包内部,发现注释部分已经说明[]byte类型会被base64编码:
// ...
// Array and slice values encode as JSON arrays, except that
// []byte encodes as a base64-encoded string, and a nil slice
// encodes as the null JSON value.
// ...继续根据方法调用链找到具体的编码位置:
func (e *encodeState) marshal(v any, opts encOpts) (err error) {
	defer func() {
		if r := recover(); r != nil {
			if je, ok := r.(jsonError); ok {
				err = je.error
			} else {
				panic(r)
			}
		}
	}()
	e.reflectValue(reflect.ValueOf(v), opts)
	return nil
}func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) {
	valueEncoder(v)(e, v, opts)
}
func valueEncoder(v reflect.Value) encoderFunc {
	if !v.IsValid() {
		return invalidValueEncoder
	}
	return typeEncoder(v.Type())
}func typeEncoder(t reflect.Type) encoderFunc {
        // ...
	// Compute the real encoder and replace the indirect func with it.
	f = newTypeEncoder(t, true)
	wg.Done()
	encoderCache.Store(t, f)
	return f
}func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
        // ...
	switch t.Kind() {
        // ...
	case reflect.Slice:
		return newSliceEncoder(t)
        // ...
	default:
		return unsupportedTypeEncoder
	}
}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
}该方法会根据slice的长度选择效率更高的字符串拼接方式
func encodeByteSlice(e *encodeState, v reflect.Value, _ encOpts) {
	if v.IsNil() {
		e.WriteString("null")
		return
	}
	s := v.Bytes()
	e.WriteByte('"')
	encodedLen := base64.StdEncoding.EncodedLen(len(s))
	if encodedLen <= len(e.scratch) {
		// If the encoded bytes fit in e.scratch, avoid an extra
		// allocation and use the cheaper Encoding.Encode.
		dst := e.scratch[:encodedLen]
		base64.StdEncoding.Encode(dst, s)
		e.Write(dst)
	} else if encodedLen <= 1024 {
		// The encoded bytes are short enough to allocate for, and
		// Encoding.Encode is still cheaper.
		dst := make([]byte, encodedLen)
		base64.StdEncoding.Encode(dst, s)
		e.Write(dst)
	} else {
		// The encoded bytes are too long to cheaply allocate, and
		// Encoding.Encode is no longer noticeably cheaper.
		enc := base64.NewEncoder(base64.StdEncoding, e)
		enc.Write(s)
		enc.Close()
	}
	e.WriteByte('"')
}总结
至此,[]byte被base64编码的代码已经找着了,思考了下go之所以选择编码[]byte,是为了避免使用utf-8进行编码转换的时候出现非预期的字符,导致“乱码”问题;类似的在AES加密等场景下,也会选择把加密后的二进制数组使用base64或其他方式的编码为字符串进行传输和存储,待使用时进行base64解码,再操作原始的[]byte数据。
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!