写在前面
在开发业务的时候,碰到了对接的两个系统返回的json数据大部分字段都是一样的,但是有个别字段名称一样但是类型却不一样的情况。为了偷懒少写代码,就在思考有没有复用结构体的办法。
Go 匿名嵌套struct
从go语言层面来看,使用匿名嵌套struct是可以做到这一点的,比如
type Base struct { Field int F2 string F3 float64 } type Type1 struct { Base Field string }
- 由于外部struct的同名字段将会覆盖内部struct的同名字段,通过Type1.Field访问到的将是?string?类型,而不会是?int?类型
- 这样我们就可以把大部分相同的字段放在Base结构体中,其余的不同的字段放在各自自定义的结构体中,以达到复用的目的
现在在语言层面我们是可以做到这个了,但是我们是在多个系统之前进行交互的啊,这中间必然涉及到数据序列化了,那么问题来了,由于我们使用的json格式,json对这种形式的数据将会如何处理?支不支持?
json基本用法
在http请求中,json是最常见的传输格式,在go语言中,直接有该语言的标准库。在?encoding/json?包中,提供了?Marshal?和?Unmarshal?两种基本方法用来序列化和反序列化。
我们对上面的定义类型测试一下
d := Type1{ Base: Base{ Field: 123, F2: "Base_F2", F3: 0.45, }, Field: "UP_LOAD", } bytes, _ := json.Marshal(d) fmt.Println(string(bytes)) d2 := Type1{} _ = json.Unmarshal(bytes, &d2) fmt.Printf("%#v", d2)
输出结果
{"F2":"Base_F2","F3":0.45,"Field":"UP_LOAD"} util.Type1{Base:util.Base{Field:0, F2:"Base_F2", F3:0.45}, Field:"UP_LOAD"}
从序列化结果出来看,当出现重名的字段时,默认是外层的struct覆盖内层的struct,匿名的struct的字段序列化层级和外层的struct的字段是在同一级。反序列化时,也是如此,外层的覆盖内层的字段。
所以在此实验中,我们已经可以很好的实现之前的问题了。但是作为一个有追求的(害怕出锅)的开发来说,我们还需要确定这是设计就是如此,还是有些别的原因导致的,顺便可以学习下json的实现原理。
json序列化一些规则
在?encoding/json.Marshal?中注释写的比较详细,这里总结介绍下
- 每个字段可以使用tag中的?json?这个key控制序列化的过程
- 第一个?,?之前表示的字段序列化后的名称,之后的表示其他属性
- 如果字段的tag写成了?-?,那么该字段就永远会被忽略,不过需要注意的是,如果写成了?-,?,那么还是会序列化,只是序列化出来的名称是?-?
- 可以使用的属性有
- ?
- ?
- 有些字段类型(?channel,complex,function?)是不允许直接出现json结构体中的(不过可以上面的忽略规则进行忽略)
- 可以被序列化的字段满足go语言的字段可见性
- 如果字段以小写命名,不会被序列化
- 在同一层上有多个相同名称的字段,具体代码?encoding/json.dominatField?
- 只会考虑最外层的struct层的字段
- 如果有tag的,将会只考虑有tag的字段
- 被过滤之后剩余的字段如果还有多个,该字段就会被忽略
json序列化
json序列化过程是一个递归过程
先看下序列化的入口处
该方法首先会从?全局编码池?获取?encoding/json.encodeState?,该结构体可以复用内存,编码过程中都会使用该编码状态,编码完成后重新放回编码池
从上述调用栈来看,最后调用的都是typeEncoder,该方法会递归调用所有类型找到对应的编码方法
- 在json库中有一个全局的类型->编码方法的cache,如果之前已经解析过了,那么就不需要再次解析了,以空间换时间
- 在该方法中,比较奇怪的使用了?WaitGroup?这个同步,从注释可以很清晰的了解到,这个是为了解决递归类型的,由于json序列化是递归的,所以如果一个结构体中是递归类型,那么在?encode?时就会无限递归下去失败
接下来会使用?newTypeEncoder?方法对该类型进行编码,主要分为几个过程
- 如果需要编码类型实现了?json.Marshaler?或者?encoding.TextMarshaler?接口,那么就使用自定义的编码方式
- 否则的话,使用内置的JSON编码器,支持的编码器
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 }
简单的编码器如boolEncoder做的事比较简单,直接将?true/false?写入到?encoding/json.encodeState?即可
复杂的编码器如structEncoder需要做的事比较多,需要先解析出结构,依照结构每个字段进行编码
先看编码过程,
func (se structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { next := byte(‘{‘) FieldLoop: // 遍历每个字段,字段进行递归编码 for i := range se.fields.list { f := &se.fields.list[i] // Find the nested struct field by following f.index. // 对于内嵌的结构体,f.index有多个,比方说对于上面的Type1的Base.Field该字段来说 // index值将是[0, 0] fv := v for _, i := range f.index { if fv.Kind() == reflect.Ptr { if fv.IsNil() { continue FieldLoop } fv = fv.Elem() } fv = fv.Field(i) } if f.omitEmpty && isEmptyValue(fv) { continue } e.WriteByte(next) next = ‘,‘ if opts.escapeHTML { e.WriteString(f.nameEscHTML) } else { e.WriteString(f.nameNonEsc) // 写入名称,格式是`"name":` } opts.quoted = f.quoted f.encoder(e, fv, opts) // 使用字段的编码器进行编码 } if next == ‘{‘ { e.WriteString("{}") // 空结构 } else { e.WriteByte(‘}‘) } }
再看下解析结构过程,通过bfs算法遍历struct的每个字段
// typeFields returns a list of fields that JSON should recognize for the given type. // The algorithm is breadth-first search over the set of structs to include - the top struct // and then any reachable anonymous structs. func typeFields(t reflect.Type) structFields { // Anonymous fields to explore at the current level and the next. current := []field{} next := []field{{typ: t}} // Count of queued names for current level and the next. var count, nextCount map[reflect.Type]int // Types already visited at an earlier level. visited := map[reflect.Type]bool{} // Fields found. var fields []field // Buffer to run HTMLEscape on field names. var nameEscBuf bytes.Buffer for len(next) > 0 { current, next = next, current[:0] count, nextCount = nextCount, map[reflect.Type]int{} for _, f := range current { if visited[f.typ] { continue } visited[f.typ] = true // Scan f.typ for fields to include. for i := 0; i < f.typ.NumField(); i++ { sf := f.typ.Field(i) isUnexported := sf.PkgPath != "" if sf.Anonymous { t := sf.Type if t.Kind() == reflect.Ptr { t = t.Elem() } if isUnexported && t.Kind() != reflect.Struct { // Ignore embedded fields of unexported non-struct types. continue } // Do not ignore embedded fields of unexported struct types // since they may have exported fields. } else if isUnexported { // Ignore unexported non-embedded fields. continue } // 解析tag,如果是"-",该字段直接忽略,检查需要开启string选项 ... // Record found field and index sequence. if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { // 初始化field的属性 tagged := name != "" if name == "" { name = sf.Name } field := field{ name: name, tag: tagged, ... fields = append(fields, field) if count[f.typ] > 1 { // If there were multiple instances, add a second, // so that the annihilation code will see a duplicate. // It only cares about the distinction between 1 or 2, // so don‘t bother generating any more copies. fields = append(fields, fields[len(fields)-1]) } continue } // Record new anonymous struct to explore in next round. nextCount[ft]++ if nextCount[ft] == 1 { next = append(next, field{name: ft.Name(), index: index, typ: ft}) } } } } //按照name, len(index)(嵌套struct的深度), tag进行排序 // 为了之后方便过滤掉有重复名称的属性 sort.Slice(fields, func(i, j int) bool { x := fields // sort field by name, breaking ties with depth, then // breaking ties with "name came from json tag", then // breaking ties with index sequence. if x[i].name != x[j].name { return x[i].name < x[j].name } if len(x[i].index) != len(x[j].index) { return len(x[i].index) < len(x[j].index) } if x[i].tag != x[j].tag { return x[i].tag } return byIndex(x).Less(i, j) }) out := fields[:0] for advance, i := 0, 0; i < len(fields); i += advance { // One iteration per name. // Find the sequence of fields with the name of this first field. .... } fields = out sort.Sort(byIndex(fields)) for i := range fields { f := &fields[i] f.encoder = typeEncoder(typeByIndex(t, f.index)) // 递归遍历每个field的编码器 } ... return structFields{fields, nameIndex} }