1. Use of sturct json tag
Format description
struct json tag is mainly used in the process of struct and json data conversion (Marshal/Unmarshal).
The tag format of json is as follows:
Key type `json:"name,opt1,opt2,opts..."`
illustrate:
- Variables must be exportable (Key initials must be capitalized), otherwise they will be ignored.
- There is no json tag or name in tag is omitted (but it cannot be missing ","), and the field name is used by default.
- When name, pay attention to the validity of naming.
- Objects such as opt1, opt2 are optional. One or combination of limited opts must be used, such as "omitempty" and "string". An error will occur when using non-limited opt.
2. Specific usage format description
Let’s first introduce several usage methods provided in the source code document:
Because Marshal and Unmarshal are opposite processes, the rules of the two are consistent. The following introduction only explains the processing when Marshal.
(1) Don't specify tag
Field int // “Filed”:0
Tags are not specified, variable names are used by default. When converted to json, the key is Filed.
(2) Ignore directly
Field int json:"-" //Note: Must be "-", not with opts
Not processed during conversion.
(3) Specify the key name
Field int json:"myName" // “myName”:0
When converting to json, the key is myName
(4) "omitempty" zero value is ignored
Field int json:",omitempty"
When converting to json, if the value is zero, it will be ignored, otherwise the key is myName
(5) Specify key and zero value is ignored
Field int json:"myName,omitempty"
When converting to json, if the value is zero, it will be ignored, otherwise the key is myName
(6) Specify the key as "-"
Field int json:"-," // “-”:0
The difference between this item and ignorance is that there is an extra "," ".
(7)“string” opt
The usages mentioned above are common, and this one is quite special.
"string" is only applicable to strings, floating points, integers, or boolean types, which means: convert the value of a field into a string; when parsing, it parses the string to the specified type. Mainly used for data conversion when communicating with JavaScript.
Notice:
There is only "string", no opts like int, number, etc. That is, fields with "string" opt can only convert strings, floating point, integer or boolean types to string types when encoding, otherwise not; strings can be converted to other types when decoding, otherwise not. Because "string" has restrictions.
Int64String int64 json:",string" // “Int64String”:“0”
The use of "string" opt can automatically convert data types in Marshal/Unmarshal, reducing the hassle of manual data conversion, but you must pay attention to the scope of use. If you use unsatisfied types, an error will be reported.
Guess what the result will be if you use "string" opt on string?
Int64String string json:",string"
We answered after understanding the source code.
2. Design and processing process of source code angle
All usage methods must be limited during design. Let’s take a look at the processing process in the source code.
In the process of realizing it, you can think about whether the use method is correct, and are there any other things to pay attention to?
We can also learn from some very good implementation ideas, which will be of great benefit to future programming learning.
For the sake of simplicity, I will skip the specific calling process and directly check the core code part. If you are interested, you can check the complete process.
In typeFields, the tags of the various usages mentioned above are processed in detail, and the processed data is stored in files, and finally encoded.
// 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 ) 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[]int // Types already visited at an earlier level. visited := map[]bool{} // Fields found. var fields []field // Buffer to run HTMLEscape on field names. var nameEscBuf for len(next) > 0 { current, next = next, current[:0] count, nextCount = nextCount, map[]int{} for _, f := range current { if visited[] {//Skip processed past types continue } visited[] = true // Scan for fields to include. for i := 0; i < (); i++ { sf := (i) isUnexported := != "" if {//Inline type processing t := if () == { t = () } if isUnexported && () != { // Ignore embedded fields of unexported non-struct types. continue//Skip directly the exported keys of non-struct structures } // Do not ignore embedded fields of unexported struct types // since they may have exported fields. } else if isUnexported { // Ignore unexported non-embedded fields. continue//Skip the keys that cannot be exported directly } tag := ("json") if tag == "-" { continue//Tag is "-" and skip it directly } name, opts := parseTag(tag) if !isValidTag(name) { name = ""//Invalid name containing special characters } index := make([]int, len()+1) copy(index, ) index[len()] = i ft := if () == "" && () == { // Follow pointer. ft = () } // Only strings, floats, integers, and booleans can be quoted. quoted := false if ("string") {//This is a special treatment of "string" opt, and the supported types are as follows: switch () { case , , reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, , reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, , reflect.Float32, reflect.Float64, : quoted = true } } // Record found field and index sequence. if name != "" || ! || () != { tagged := name != "" if name == "" { name = //Use the original field name if the specified name is not specified or the specified name is invalid } field := field{ name: name, tag: tagged, index: index, typ: ft, omitEmpty: ("omitempty"),//omitempty confirmation quoted: quoted,//Whether "string" opt is supported } = []byte() = foldFunc() // Build nameEscHTML and nameNonEsc ahead of time. //Construction of two formats () (`"`) HTMLEscape(&nameEscBuf, ) (`":`) = () = `"` + + `":` fields = append(fields, field)//Save fields if count[] > 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: (), index: index, typ: ft}) } } } } ... for i := range fields { f := &fields[i] = typeEncoder(typeByIndex(t, ))//Set the encoder of fields } nameIndex := make(map[string]int, len(fields)) for i, field := range fields { nameIndex[] = i } return structFields{fields, nameIndex} }
func newStructEncoder(t ) encoderFunc { se := structEncoder{fields: cachedTypeFields(t)} return } func (se structEncoder) encode(e *encodeState, v , opts encOpts) { next := byte('{') FieldLoop: for i := range { f := &[i] // Find the nested struct field by following . fv := v for _, i := range { if () == { if () { continue FieldLoop } fv = () } fv = (i) } if && isEmptyValue(fv) {//The ignorance of "omitempty" requires a value of zero continue } (next) next = ',' if { () } else { () } = (e, fv, opts)//Coding process according to specific type } if next == '{' { ("{}") } else { ('}') } }
The following is an example of int type intEncoder:
func intEncoder(e *encodeState, v , opts encOpts) { b := ([:0], (), 10) if {//Add quotes with "string" opt ('"') } (b) if { ('"') } }
For numeric types, if with **"string"**, add quotes before and after writing the official value.
For string types, if you have **"string"**, the original string value will be added to the quotation marks when encoded, and then the result will be added to the format, and the original value needs to be encoded first.
func stringEncoder(e *encodeState, v , opts encOpts) { if () == numberType { numStr := () // 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) { (("json: invalid number literal %q", numStr)) } (numStr) return } if { sb, err := Marshal(())//Note the processing here if err != nil { (err) } (string(sb), ) } else { ((), ) } } func (e *encodeState) string(s string, escapeHTML bool) { ('"')//Add quotes start := 0 for i := 0; i < len(s); { if b := s[i]; b < {//Escape processing when special characters exist in string if htmlSafeSet[b] || (!escapeHTML && safeSet[b]) { i++ continue } if start < i { (s[start:i]) } ('\\') switch b { case '\\', '"': (b) case '\n': ('n') case '\r': ('r') case '\t': ('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. (`u00`) (hex[b>>4]) (hex[b&0xF]) } i++ start = i continue } c, size := (s[i:]) if c == && size == 1 { if start < i { (s[start:i]) } (`\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 /json-isnt-a-javascript-subset for discussion. if c == '\u2028' || c == '\u2029' { if start < i { (s[start:i]) } (`\u202`) (hex[c&0xF]) i += size start = i continue } i += size } if start < len(s) { (s[start:]) } ('"') }
After understanding the process of source code, we will answer the questions mentioned earlier. Adding "string" opt to a field of type string, what you get is:
Int64String string json:",string" // “Int64String”: "“1234"”
3. Summary
This article mainly explains why struct json tags are used in this way from the perspective of source code, and what should be paid attention to when using them. Finally, repeat the important points:
- The fields must be exportable before tags are meaningful
- Ignore json:"-" must be used, and must not have opts, otherwise the key will become "-"
- "string" opt is only applicable to string, floating point, integer and boolean types, meaning that these types of data can be used as string type, or string type data Unmarshal to these types.
Do not abuse it, especially for data that is already of string type.
This is the article about the use of golang struct json tags. For more related content on using golang struct json tags, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!