go json学习

136 阅读3分钟

匿名字段场景

场景一

匿名字段和外部字段重合,且无json的tag,结构体转化为json串,匿名字段不展示

type App struct {
   AppId string
}

type OutApp struct {
   App
   AppId string
}


func main() {
   s1 := OutApp{
      App:   App{"insideApp"},
      AppId: "outApp",
   }
   b, _ := json.Marshal(s1)
   fmt.Println(string(b)) //{"AppId":"outApp"} 
}

场景二

匿名字段和外部字段重合,匿名字段的key有json的tag,且不同于外面,2者都展示,无嵌套情况

type App struct {
   AppId string `json:"app_in_key"`
}

type OutApp struct {
   App
   AppId string
}


func main() {
   s1 := OutApp{
      App:   App{"insideApp"},
      AppId: "outApp",
   }
   b, _ := json.Marshal(s1)
   fmt.Println(string(b)) //{"app_in_key":"insideApp","AppId":"outApp"}
}

场景三

匿名字段和外部字段重合,匿名字段有json的tag,且不同于外面,2者都展示,有嵌套情况

type App struct {
   AppId string 
}

type OutApp struct {
   App `json:"Anonymous_APP"`
   AppId string
}


func main() {
   s1 := OutApp{
      App:   App{"insideApp"},
      AppId: "outApp",
   }
   b, _ := json.Marshal(s1)
   fmt.Println(string(b)) //{"Anonymous_APP":{"AppId":"insideApp"},"AppId":"outApp"}
}

源码学习

#encoding/json/encode.go
func typeFields(t reflect.Type) structFields {
    for len(next) > 0 {
        for i := 0; i < f.typ.NumField(); i++ {
           sf := f.typ.Field(i)
           if sf.Anonymous {
             //匿名字段
           }
            //构造fields 

            tag := sf.Tag.Get("json")
            if tag == "-" {
                continue
            }
            name, opts := parseTag(tag)
            #.....
            if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
                ....
            }

            // Record new anonymous struct to explore in next round.(记录新的匿名结构以在下一轮中进行探索。打平匿名结构的字段)
            nextCount[ft]++
            if nextCount[ft] == 1 {
               //放到next队列中,相当于嵌套情况,放在后面处理
               next = append(next, field{name: ft.Name(), index: index, typ: ft})
            }
        }
    }


    sort.Slice(fields, func(i, j int) bool {
       //对fields按照结构体聚合排序
       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)
    })

    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.
       fi := fields[i]
       name := fi.name
       for advance = 1; i+advance < len(fields); advance++ {
          fj := fields[i+advance]
          if fj.name != name {
             break
          }
       }
       if advance == 1 { // Only one field with this name
          //相同的field.name只保证用前面的
          out = append(out, fi)
          continue
       }
       dominant, ok := dominantField(fields[i : i+advance])
       if ok {
          out = append(out, dominant)
       }
    }



    for i := range fields {
       f := &fields[i]
       f.encoder = typeEncoder(typeByIndex(t, f.index)) //如果嵌套结构体包含json的tag,会在执行整个方法,匿名结构体则不回
    }
    
}

结论

  • json.Marshal是从外到里依次处理的, 外面的优先级高于里面(相同tag情况)
  • 如果匿名结构体无json的tag,则匿名结构的字段会被打平,如场景二

omitempty

func (se structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
    if f.omitEmpty && isEmptyValue(fv) {
       continue
    }
}

重写MarshalJSON

import (
   "encoding/json"
   "fmt"
   "strings"
   "time"
)

type ModelData struct {
   Id        int    `json:"id"`
   CreatedAt Mytime `json:"created_at"`
}

type Mytime time.Time

const TimeLayOut = "2006-01-02 15:04:05"

func (m Mytime) MarshalJSON() ([]byte, error) {

   str := time.Time(m).Format(TimeLayOut)
   return []byte(`"` + str + `"`), nil

}
func (m *Mytime) UnmarshalJSON(data []byte) (err error) {
   d, _ := time.Parse(TimeLayOut, strings.Trim(string(data), "\""))
   *m = Mytime(d)
   return nil
}

func main() {
   t := time.Now()
   m := ModelData{
      Id:        1,
      CreatedAt: Mytime(t),
   }
   s, e := json.Marshal(m)
   //CreatedAt是time.Time, {"id":1,"created_at":"2023-05-06T23:36:35.626723+08:00"}
   //CreatedAt是Mytime 实现自定义MarshalJSON方法 :{"id":1,"created_at":"2023-05-06 23:35:21"} nil
   fmt.Println(string(s), e)

   var mNew ModelData
   e = json.Unmarshal(s, &mNew)
   fmt.Println(time.Time(mNew.CreatedAt).Format(TimeLayOut))

}

可能会用到的地方

  1. 将db的中包含time的字段以正确的格式返回出去

其他json包性能比较

image.png

  1. easyjson 无论是序列化还是反序列化都是最优的,序列化提升了1倍,反序列化提升了3倍
  2. jsoniter 性能也很好,接近于easyjson,关键是没有预编译过程,100%兼容原生库
  3. ffjson 的序列化提升并不明显,反序列化提升了1倍
  4. codecjson 和原生库相比,差不太多,甚至更差
  5. jsonparser 不太适合这样的场景,性能提升并不明显,而且没有反序列化

综合考虑,建议大家使用 jsoniter,如果追求极致的性能,考虑 easyjson(要在代码中定义结构体的marshal和unmarshal方法,同时还需要执行代码生成步骤)

文章转自segmentfault.com/a/119000001…