golang?struct?json?tag的使用以及深入講解
一、sturct json tag的使用
1.tag格式說(shuō)明
struct json tag主要在struct與json數(shù)據(jù)轉(zhuǎn)換的過(guò)程(Marshal/Unmarshal)中使用。
json的tag格式如下:
Key type `json:"name,opt1,opt2,opts..."`
說(shuō)明:
- 變量必須是可導(dǎo)出的(Key首字母必須大寫(xiě)),否則會(huì)被忽略處理。
- 沒(méi)有json tag或者tag中name省略(但不能少了","),默認(rèn)使用字段名。
- name要注意命名的有效性。
- opt1、opt2等項(xiàng)為可選項(xiàng),必須使用有限的幾個(gè)限定的opt的一個(gè)或組合,如"omitempty"、"string",使用非限定的opt會(huì)發(fā)生錯(cuò)誤。
2.具體使用格式說(shuō)明
我們先介紹下源碼文檔中提供的幾種使用方式:
因Marshal與Unmarshal是相反的過(guò)程,兩者規(guī)則是一致的,以下介紹中僅說(shuō)明了Marshal時(shí)的處理。
(1)不指定tag
Field int // “Filed”:0
不指定tag,默認(rèn)使用變量名稱。轉(zhuǎn)換為json時(shí),key為Filed。
(2)直接忽略
Field int json:"-" //注意:必須為"-",不能帶有opts
轉(zhuǎn)換時(shí)不處理。
(3)指定key名
Field int json:"myName" // “myName”:0
轉(zhuǎn)換為json時(shí),key為myName
(4)"omitempty"零值忽略
Field int json:",omitempty"
轉(zhuǎn)換為json時(shí),值為零值則忽略,否則key為myName
(5)指定key且零值忽略
Field int json:"myName,omitempty"
轉(zhuǎn)換為json時(shí),值為零值則忽略,否則key為myName
(6)指定key為"-"
Field int json:"-," // “-”:0
此項(xiàng)與忽略的區(qū)別在于多了個(gè)”,“。
(7)“string” opt
以上提到的用法都是常見(jiàn)的,這個(gè)比較特殊。
"string"僅適用于字符串、浮點(diǎn)、整數(shù)或布爾類型,表示的意思是:將字段的值轉(zhuǎn)換為字符串;解析時(shí),則是將字符串解析為指定的類型。主要用于與javascript通信時(shí)數(shù)據(jù)的轉(zhuǎn)換。
注意:
僅且僅有"string",沒(méi)有int、number之類的opt。即帶"string" opt的字段,編碼時(shí)僅能將字符串、浮點(diǎn)、整數(shù)或布爾類型轉(zhuǎn)換為string類型,反之則不然;解碼時(shí)可以將string轉(zhuǎn)換為其他類型,反之不然。因?yàn)?quot;string"有限制。
Int64String int64 json:",string" // “Int64String”:“0”
“string” opt的使用可以在Marshal/Unmarshal時(shí)自動(dòng)進(jìn)行數(shù)據(jù)類型的轉(zhuǎn)換,減少了手動(dòng)數(shù)據(jù)轉(zhuǎn)換的麻煩,但是一定要注意使用的范圍,對(duì)不滿足的類型使用,是會(huì)報(bào)錯(cuò)的。
猜下對(duì)string使用"string" opt的結(jié)果會(huì)是如何呢?
Int64String string json:",string"
我們?cè)诹私庠创a后解答。
二、源碼角度的設(shè)計(jì)處理過(guò)程
一切的使用方式肯定在設(shè)計(jì)時(shí)就已限定,我們現(xiàn)在看看源碼中的處理過(guò)程。
在看實(shí)現(xiàn)的過(guò)程中,可以思考下使用的方式對(duì)不對(duì),還有要注意的地方嗎?
對(duì)某些地方非常好的實(shí)現(xiàn)思路,我們也可以借鑒下,對(duì)以后的編程學(xué)習(xí)大有裨益。
此處為了簡(jiǎn)潔,具體調(diào)用過(guò)程略過(guò)不講,直接查看核心代碼部分,有興趣的話,可以查看下完整過(guò)程。
1.typeFields
在typeFields中詳細(xì)的對(duì)上面提到的各種用法的tag做了處理,處理后的數(shù)據(jù)存入fileds,最后在進(jìn)行編碼。
// 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] {//已處理的過(guò)類型跳過(guò) 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 {//內(nèi)嵌類型的處理 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//非struct結(jié)構(gòu)的不能導(dǎo)出的key直接跳過(guò) } // Do not ignore embedded fields of unexported struct types // since they may have exported fields. } else if isUnexported { // Ignore unexported non-embedded fields. continue//不能導(dǎo)出的key直接跳過(guò) } tag := sf.Tag.Get("json") if tag == "-" { continue//tag為"-"直接跳過(guò) } name, opts := parseTag(tag) if !isValidTag(name) { name = ""http://包含特殊字符的無(wú)效name } index := make([]int, len(f.index)+1) copy(index, f.index) index[len(f.index)] = i ft := sf.Type if ft.Name() == "" && ft.Kind() == reflect.Ptr { // Follow pointer. ft = ft.Elem() } // Only strings, floats, integers, and booleans can be quoted. quoted := false if opts.Contains("string") {//此處為"string" opt的特殊處理,支持的類型如下: switch ft.Kind() { case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.String: quoted = true } } // Record found field and index sequence. if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { tagged := name != "" if name == "" { name = sf.Name//未指定或者指定name無(wú)效的使用原field的name } field := field{ name: name, tag: tagged, index: index, typ: ft, omitEmpty: opts.Contains("omitempty"),//omitempty確認(rèn) quoted: quoted,//是否支持"string" opt } field.nameBytes = []byte(field.name) field.equalFold = foldFunc(field.nameBytes) // Build nameEscHTML and nameNonEsc ahead of time. //兩種格式的構(gòu)建 nameEscBuf.Reset() nameEscBuf.WriteString(`"`) HTMLEscape(&nameEscBuf, field.nameBytes) nameEscBuf.WriteString(`":`) field.nameEscHTML = nameEscBuf.String() field.nameNonEsc = `"` + field.name + `":` fields = append(fields, field)//存入fields 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}) } } } } ... for i := range fields { f := &fields[i] f.encoder = typeEncoder(typeByIndex(t, f.index))//設(shè)置fields的encoder } nameIndex := make(map[string]int, len(fields)) for i, field := range fields { nameIndex[field.name] = i } return structFields{fields, nameIndex} }
2.encode
func newStructEncoder(t reflect.Type) encoderFunc { se := structEncoder{fields: cachedTypeFields(t)} return se.encode } 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. 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) {//"omitempty"的忽略處理,需要值為零值 continue } e.WriteByte(next) next = ',' if opts.escapeHTML { e.WriteString(f.nameEscHTML) } else { e.WriteString(f.nameNonEsc) } opts.quoted = f.quoted f.encoder(e, fv, opts)//根據(jù)具體類型的編碼處理 } if next == '{' { e.WriteString("{}") } else { e.WriteByte('}') } }
以下以int類型intEncoder為例:
func intEncoder(e *encodeState, v reflect.Value, opts encOpts) { b := strconv.AppendInt(e.scratch[:0], v.Int(), 10) if opts.quoted {//帶有"string" opt添加引號(hào) e.WriteByte('"') } e.Write(b) if opts.quoted { e.WriteByte('"') } }
對(duì)于數(shù)字類型,如果帶有**“string”**則在寫(xiě)入正式值前后添加引號(hào)。
對(duì)于字符串類型,如果帶有**“string”**,原string值再編碼時(shí)會(huì)添加引號(hào),再對(duì)結(jié)果添加引號(hào),則格式異常,因此需要先對(duì)原值進(jìn)行編碼。
func stringEncoder(e *encodeState, v reflect.Value, opts encOpts) { if v.Type() == numberType { numStr := v.String() // In Go1.5 the empty string encodes to "0", while this is not a valid number literal // we keep compatibility so check validity after this. if numStr == "" { numStr = "0" // Number's zero-val } if !isValidNumber(numStr) { e.error(fmt.Errorf("json: invalid number literal %q", numStr)) } e.WriteString(numStr) return } if opts.quoted { sb, err := Marshal(v.String())//注意此處處理 if err != nil { e.error(err) } e.string(string(sb), opts.escapeHTML) } else { e.string(v.String(), opts.escapeHTML) } } func (e *encodeState) string(s string, escapeHTML bool) { e.WriteByte('"')//添加引號(hào) start := 0 for i := 0; i < len(s); { if b := s[i]; b < utf8.RuneSelf {//字符串中存在特殊的字符時(shí)的轉(zhuǎn)義處理 if htmlSafeSet[b] || (!escapeHTML && safeSet[b]) { i++ continue } if start < i { e.WriteString(s[start:i]) } e.WriteByte('\\') switch b { case '\\', '"': e.WriteByte(b) case '\n': e.WriteByte('n') case '\r': e.WriteByte('r') case '\t': e.WriteByte('t') default: // This encodes bytes < 0x20 except for \t, \n and \r. // If escapeHTML is set, it also escapes <, >, and & // because they can lead to security holes when // user-controlled strings are rendered into JSON // and served to some browsers. e.WriteString(`u00`) e.WriteByte(hex[b>>4]) e.WriteByte(hex[b&0xF]) } i++ start = i continue } c, size := utf8.DecodeRuneInString(s[i:]) if c == utf8.RuneError && size == 1 { if start < i { e.WriteString(s[start:i]) } e.WriteString(`\ufffd`) i += size start = i continue } // U+2028 is LINE SEPARATOR. // U+2029 is PARAGRAPH SEPARATOR. // They are both technically valid characters in JSON strings, // but don't work in JSONP, which has to be evaluated as JavaScript, // and can lead to security holes there. It is valid JSON to // escape them, so we do so unconditionally. // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. if c == '\u2028' || c == '\u2029' { if start < i { e.WriteString(s[start:i]) } e.WriteString(`\u202`) e.WriteByte(hex[c&0xF]) i += size start = i continue } i += size } if start < len(s) { e.WriteString(s[start:]) } e.WriteByte('"') }
在了解完源碼的處理過(guò)程后,我們對(duì)之前提到的問(wèn)題做個(gè)解答。對(duì)string類型的字段添加"string" opt,得到的是:
Int64String string json:",string" // “Int64String”: "“1234"”
三、總結(jié)
本文主要從源碼的角度說(shuō)明struct json tag的為什么這么使用,以及使用時(shí)需要注意的地方。最后重復(fù)下重要的幾點(diǎn):
- 字段必須可導(dǎo)出,tag才有意義
- 忽略必須使用json:"-",不得帶有opts,否則key將會(huì)變成"-"
- "string" opt僅適用于字符串、浮點(diǎn)、整數(shù)及布爾類型,意思是可以將這些類型的數(shù)據(jù)Marshal為string類型,或者將string類型的數(shù)據(jù)Unmarshal為這些類型。
請(qǐng)勿濫用,尤其是對(duì)已經(jīng)是string類型的數(shù)據(jù)使用。
到此這篇關(guān)于golang struct json tag使用的文章就介紹到這了,更多相關(guān)golang struct json tag的使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
GoLang strings.Builder底層實(shí)現(xiàn)方法詳解
自從學(xué)習(xí)go一個(gè)月以來(lái),我多少使用了一下strings.Builder,略有心得。你也許知道它,特別是你了解bytes.Buffer的話。所以我在此分享一下我的心得,并希望能對(duì)你有所幫助2022-10-10超實(shí)用的Golang通道指南之輕松實(shí)現(xiàn)并發(fā)編程
Golang?中的通道是一種高效、安全、靈活的并發(fā)機(jī)制,用于在并發(fā)環(huán)境下實(shí)現(xiàn)數(shù)據(jù)的同步和傳遞。本文主要介紹了如何利用通道輕松實(shí)現(xiàn)并發(fā)編程,需要的可以參考一下2023-04-04Golang實(shí)現(xiàn)自己的orm框架實(shí)例探索
這篇文章主要為大家介紹了Golang實(shí)現(xiàn)自己的orm框架實(shí)例探索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01golang 進(jìn)度條功能實(shí)現(xiàn)示例
這篇文章主要介紹了golang 進(jìn)度條功能實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08詳解如何在golang鏡像中設(shè)置指定時(shí)區(qū)
這篇文章主要為大家詳細(xì)介紹了如何在golang鏡像中設(shè)置指定時(shí)區(qū),文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的可以了解一下2023-04-04解決golang sync.Wait()不執(zhí)行的問(wèn)題
這篇文章主要介紹了解決golang sync.Wait()不執(zhí)行的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12