匿名字段场景
场景一
匿名字段和外部字段重合,且无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))
}
可能会用到的地方
- 将db的中包含time的字段以正确的格式返回出去
其他json包性能比较
- easyjson 无论是序列化还是反序列化都是最优的,序列化提升了1倍,反序列化提升了3倍
- jsoniter 性能也很好,接近于easyjson,关键是没有预编译过程,100%兼容原生库
- ffjson 的序列化提升并不明显,反序列化提升了1倍
- codecjson 和原生库相比,差不太多,甚至更差
- jsonparser 不太适合这样的场景,性能提升并不明显,而且没有反序列化
综合考虑,建议大家使用 jsoniter,如果追求极致的性能,考虑 easyjson(要在代码中定义结构体的marshal和unmarshal方法,同时还需要执行代码生成步骤)