When passing data, you need to code first; the commonly used method is JSON codec (see "golang's JSON processing》). But sometimes you need to read some fields before you know the specific type. At this time, you can use the mapstructure library.
mapstructure library
Mapstructure can be easily implementedmap[string]interface{}
andstruct
Transform between; before use, you need to import the library:
go get /mitchellh/mapstructure
Field Tags
By default, mapstructure uses the field name to make a matching map (that is, find field values with the field name as key value in the map); note that case is ignored when matching. You can also set the field map name through the tag:
type Person struct { Name string `mapstructure:"userName"` }
Inline structure
Structures in go can be nested at will; after nesting, they are considered to have corresponding fields. However, by default mapstructure only processes fields defined by the current structure. To automatically handle embedded fields, you need to add tagssquash
:
type Student struct { Person `mapstructure:",squash"` Age int }
Unmapped fields
If there is an unmapped value in the source data (that is, there is no corresponding field in the structure), mapstructure will ignore it by default. A special field can be defined in a structure (type ofmap[string]interface{}
, and the label should be set tomapstructure:",remain"
) to store all fields that cannot be mapped.
type Student struct { Name string Age int Other map[string]interface{} `mapstructure:",remain"` }
Metadata
Metadata can be used in mapstructure to collect some useful information that will be generated when decoding.
// type Metadata struct { Keys []string // The key that was successfully decoded Unused []string // Keys that exist in the source data but do not exist in the target structure Unset []string // Unset (missed in source data) key}
In order to obtain this information, DecodeMetadata needs to be used to decode:
var metadata
err := (m, &p, &metadata)
Weak type input
Sometimes, you don't want to do the struct field type andmap[string]interface{}
The corresponding key value is strongly consistent in type verification. At this time, you can use the WeakDecode/WeakDecodeMetadata methods, and they will try to do type conversion:
- Boolean to string: true = "1", false = "0";
- Boolean to number: true = 1, false = 0;
- Number to Boolean: true if value != 0;
- String to Boolean: acceptable,
- True: 1, t, T, TRUE, true, True
- False: 0, f, F, FALSE, false, False
- Number to string: automatic base10 conversion;
- Negative numbers are converted to unsigned numbers (overflow);
- String to number: convert according to prefix (such as 0x, etc.);
- The empty array and the empty map are rotated together;
- Convert a single value to slice;
Reverse conversion
In addition to converting maps into structures, mapstructure can also decode structures in reverse asmap[string]interface{}
. In reverse decoding, we can set mapstructure: ",omitempty" for certain fields, which will not appear in the map when these fields are default values:
p := &Student{ Name: "Mike", Age: 12, } var m map[string]interface{} (p, &m)
Decoder
mapstructure provides a decoder, which can flexibly and conveniently control decoding:
type DecoderConfig struct { // If set, it is called before any decoding or type conversion (WeaklyTypedInput is set); for the embedded fields with squash set, the overall call will be once; if an error is returned, the entire decoding will fail DecodeHook DecodeHookFunc // If set, if unused fields exist in the source data, an error will be reported ErrorUnused bool // If set, if some fields are not set, an error will be reported ErrorUnset bool // If set, clear the field before setting (the old data will be cleaned up for map and other types) ZeroFields bool // If set, it supports conversion between types WeaklyTypedInput bool // Squash will squash embedded structs. Squash bool // Metadata is the struct that will contain extra metadata about // the decoding. If this is nil, then no metadata will be tracked. Metadata *Metadata // Result is a pointer to the struct that will contain the decoded // value. Result interface{} // The tag name that mapstructure reads for field names. This // defaults to "mapstructure" TagName string // IgnoreUntaggedFields ignores all struct fields without explicit // TagName, comparable to `mapstructure:"-"` as default behaviour. IgnoreUntaggedFields bool // MatchName is the function used to match the map key to the struct // field name or tag. Defaults to ``. This can be used // to implement case-sensitive tag values, support snake casing, etc. MatchName func(mapKey, fieldName string) bool }
An example that supports weak type conversion: put the result to be retrieved into the config result
Name string Age int } func decoderConfig() { m := map[string]interface{}{ "name": 123, "age": "12", "job": "programmer", } var p Person var metadata decoder, err := (&{ WeaklyTypedInput: true, Result: &p, Metadata: &metadata, }) if err != nil { (err) } err = (m) if err == nil { ("Result: %#v", p) ("keys:%#v, unused:%#v\n", , ) } else { ("decode fail:", err) } }
Example
Through a messageData structure, the action will indicate the final data type. After receiving the data, first parse out the atcion and then convert it to the real type according to the action.
Because it is a structure (it will be converted to a time string when serialized by json), mapstructure cannot be processed correctly, so it is recommended to use a timestamp.
In order to correctly parse embedded DataBasic, it needs to be marked as squash.
import "/mitchellh/mapstructure" type DataBasic struct { DataId string `json:"dataId"` UpdateTime int64 `json:"updateTime"` } type AddedData struct { DataBasic `mapstructure:",squash"` Tag string `json:"tag"` AddParams map[string]any `json:"addParams"` } type messageData struct { Action int `json:"action"` SeqId uint64 `json:"seqId"` Data any `json:"data"` } func decodeData() { add := &AddedData{ DataBasic: DataBasic{ DataId: "a2", UpdateTime: ().UnixMilli(), }, Tag: "tag", AddParams: map[string]any{"dataId": "c2", "otherId": "t2"}, } data := &messageData{ Action: 1, Data: add, } js, err := (data) if err != nil { ("marshal fail: %v", err) return } got := &messageData{} err = (js, got) if err != nil { ("unmarshal fail: %v", err) return } param := new(AddedData) err = (, param) if err != nil { ("unmarshal fail: %v", err) return } ("param: %+v", param) }
This is the end of this article about the in-depth and detailed explanation of the structure mapping mapstructure library in Golang. For more related Go mapstructure content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!