New upstream version 1.3.2
Shengjing Zhu
3 years ago
0 | 0 | language: go |
1 | 1 | |
2 | 2 | go: |
3 | - "1.11.x" | |
3 | - "1.14.x" | |
4 | 4 | - tip |
5 | 5 | |
6 | 6 | script: |
7 | 7 | - go test |
8 | - go test -bench . -benchmem |
0 | ## 1.3.2 | |
1 | ||
2 | * Decode into interface type with a struct value is supported [GH-187] | |
3 | ||
4 | ## 1.3.1 | |
5 | ||
6 | * Squash should only squash embedded structs. [GH-194] | |
7 | ||
8 | ## 1.3.0 | |
9 | ||
10 | * Added `",omitempty"` support. This will ignore zero values in the source | |
11 | structure when encoding. [GH-145] | |
12 | ||
13 | ## 1.2.3 | |
14 | ||
15 | * Fix duplicate entries in Keys list with pointer values. [GH-185] | |
16 | ||
17 | ## 1.2.2 | |
18 | ||
19 | * Do not add unsettable (unexported) values to the unused metadata key | |
20 | or "remain" value. [GH-150] | |
21 | ||
22 | ## 1.2.1 | |
23 | ||
24 | * Go modules checksum mismatch fix | |
25 | ||
26 | ## 1.2.0 | |
27 | ||
28 | * Added support to capture unused values in a field using the `",remain"` value | |
29 | in the mapstructure tag. There is an example to showcase usage. | |
30 | * Added `DecoderConfig` option to always squash embedded structs | |
31 | * `json.Number` can decode into `uint` types | |
32 | * Empty slices are preserved and not replaced with nil slices | |
33 | * Fix panic that can occur in when decoding a map into a nil slice of structs | |
34 | * Improved package documentation for godoc | |
35 | ||
0 | 36 | ## 1.1.2 |
1 | 37 | |
2 | 38 | * Fix error when decode hook decodes interface implementation into interface |
0 | // Package mapstructure exposes functionality to convert an arbitrary | |
1 | // map[string]interface{} into a native Go structure. | |
0 | // Package mapstructure exposes functionality to convert one arbitrary | |
1 | // Go type into another, typically to convert a map[string]interface{} | |
2 | // into a native Go structure. | |
2 | 3 | // |
3 | 4 | // The Go structure can be arbitrarily complex, containing slices, |
4 | 5 | // other structs, etc. and the decoder will properly decode nested |
5 | 6 | // maps and so on into the proper structures in the native Go struct. |
6 | 7 | // See the examples to see what the decoder is capable of. |
8 | // | |
9 | // The simplest function to start with is Decode. | |
10 | // | |
11 | // Field Tags | |
12 | // | |
13 | // When decoding to a struct, mapstructure will use the field name by | |
14 | // default to perform the mapping. For example, if a struct has a field | |
15 | // "Username" then mapstructure will look for a key in the source value | |
16 | // of "username" (case insensitive). | |
17 | // | |
18 | // type User struct { | |
19 | // Username string | |
20 | // } | |
21 | // | |
22 | // You can change the behavior of mapstructure by using struct tags. | |
23 | // The default struct tag that mapstructure looks for is "mapstructure" | |
24 | // but you can customize it using DecoderConfig. | |
25 | // | |
26 | // Renaming Fields | |
27 | // | |
28 | // To rename the key that mapstructure looks for, use the "mapstructure" | |
29 | // tag and set a value directly. For example, to change the "username" example | |
30 | // above to "user": | |
31 | // | |
32 | // type User struct { | |
33 | // Username string `mapstructure:"user"` | |
34 | // } | |
35 | // | |
36 | // Embedded Structs and Squashing | |
37 | // | |
38 | // Embedded structs are treated as if they're another field with that name. | |
39 | // By default, the two structs below are equivalent when decoding with | |
40 | // mapstructure: | |
41 | // | |
42 | // type Person struct { | |
43 | // Name string | |
44 | // } | |
45 | // | |
46 | // type Friend struct { | |
47 | // Person | |
48 | // } | |
49 | // | |
50 | // type Friend struct { | |
51 | // Person Person | |
52 | // } | |
53 | // | |
54 | // This would require an input that looks like below: | |
55 | // | |
56 | // map[string]interface{}{ | |
57 | // "person": map[string]interface{}{"name": "alice"}, | |
58 | // } | |
59 | // | |
60 | // If your "person" value is NOT nested, then you can append ",squash" to | |
61 | // your tag value and mapstructure will treat it as if the embedded struct | |
62 | // were part of the struct directly. Example: | |
63 | // | |
64 | // type Friend struct { | |
65 | // Person `mapstructure:",squash"` | |
66 | // } | |
67 | // | |
68 | // Now the following input would be accepted: | |
69 | // | |
70 | // map[string]interface{}{ | |
71 | // "name": "alice", | |
72 | // } | |
73 | // | |
74 | // DecoderConfig has a field that changes the behavior of mapstructure | |
75 | // to always squash embedded structs. | |
76 | // | |
77 | // Remainder Values | |
78 | // | |
79 | // If there are any unmapped keys in the source value, mapstructure by | |
80 | // default will silently ignore them. You can error by setting ErrorUnused | |
81 | // in DecoderConfig. If you're using Metadata you can also maintain a slice | |
82 | // of the unused keys. | |
83 | // | |
84 | // You can also use the ",remain" suffix on your tag to collect all unused | |
85 | // values in a map. The field with this tag MUST be a map type and should | |
86 | // probably be a "map[string]interface{}" or "map[interface{}]interface{}". | |
87 | // See example below: | |
88 | // | |
89 | // type Friend struct { | |
90 | // Name string | |
91 | // Other map[string]interface{} `mapstructure:",remain"` | |
92 | // } | |
93 | // | |
94 | // Given the input below, Other would be populated with the other | |
95 | // values that weren't used (everything but "name"): | |
96 | // | |
97 | // map[string]interface{}{ | |
98 | // "name": "bob", | |
99 | // "address": "123 Maple St.", | |
100 | // } | |
101 | // | |
102 | // Omit Empty Values | |
103 | // | |
104 | // When decoding from a struct to any other value, you may use the | |
105 | // ",omitempty" suffix on your tag to omit that value if it equates to | |
106 | // the zero value. The zero value of all types is specified in the Go | |
107 | // specification. | |
108 | // | |
109 | // For example, the zero type of a numeric type is zero ("0"). If the struct | |
110 | // field value is zero and a numeric type, the field is empty, and it won't | |
111 | // be encoded into the destination type. | |
112 | // | |
113 | // type Source { | |
114 | // Age int `mapstructure:",omitempty"` | |
115 | // } | |
116 | // | |
117 | // Unexported fields | |
118 | // | |
119 | // Since unexported (private) struct fields cannot be set outside the package | |
120 | // where they are defined, the decoder will simply skip them. | |
121 | // | |
122 | // For this output type definition: | |
123 | // | |
124 | // type Exported struct { | |
125 | // private string // this unexported field will be skipped | |
126 | // Public string | |
127 | // } | |
128 | // | |
129 | // Using this map as input: | |
130 | // | |
131 | // map[string]interface{}{ | |
132 | // "private": "I will be ignored", | |
133 | // "Public": "I made it through!", | |
134 | // } | |
135 | // | |
136 | // The following struct will be decoded: | |
137 | // | |
138 | // type Exported struct { | |
139 | // private: "" // field is left with an empty string (zero value) | |
140 | // Public: "I made it through!" | |
141 | // } | |
142 | // | |
143 | // Other Configuration | |
144 | // | |
145 | // mapstructure is highly configurable. See the DecoderConfig struct | |
146 | // for other features and options that are supported. | |
7 | 147 | package mapstructure |
8 | 148 | |
9 | 149 | import ( |
79 | 219 | // |
80 | 220 | WeaklyTypedInput bool |
81 | 221 | |
222 | // Squash will squash embedded structs. A squash tag may also be | |
223 | // added to an individual struct field using a tag. For example: | |
224 | // | |
225 | // type Parent struct { | |
226 | // Child `mapstructure:",squash"` | |
227 | // } | |
228 | Squash bool | |
229 | ||
82 | 230 | // Metadata is the struct that will contain extra metadata about |
83 | 231 | // the decoding. If this is nil, then no metadata will be tracked. |
84 | 232 | Metadata *Metadata |
270 | 418 | |
271 | 419 | var err error |
272 | 420 | outputKind := getKind(outVal) |
421 | addMetaKey := true | |
273 | 422 | switch outputKind { |
274 | 423 | case reflect.Bool: |
275 | 424 | err = d.decodeBool(name, input, outVal) |
288 | 437 | case reflect.Map: |
289 | 438 | err = d.decodeMap(name, input, outVal) |
290 | 439 | case reflect.Ptr: |
291 | err = d.decodePtr(name, input, outVal) | |
440 | addMetaKey, err = d.decodePtr(name, input, outVal) | |
292 | 441 | case reflect.Slice: |
293 | 442 | err = d.decodeSlice(name, input, outVal) |
294 | 443 | case reflect.Array: |
302 | 451 | |
303 | 452 | // If we reached here, then we successfully decoded SOMETHING, so |
304 | 453 | // mark the key as used if we're tracking metainput. |
305 | if d.config.Metadata != nil && name != "" { | |
454 | if addMetaKey && d.config.Metadata != nil && name != "" { | |
306 | 455 | d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) |
307 | 456 | } |
308 | 457 | |
313 | 462 | // value to "data" of that type. |
314 | 463 | func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error { |
315 | 464 | if val.IsValid() && val.Elem().IsValid() { |
316 | return d.decode(name, data, val.Elem()) | |
465 | elem := val.Elem() | |
466 | ||
467 | // If we can't address this element, then its not writable. Instead, | |
468 | // we make a copy of the value (which is a pointer and therefore | |
469 | // writable), decode into that, and replace the whole value. | |
470 | copied := false | |
471 | if !elem.CanAddr() { | |
472 | copied = true | |
473 | ||
474 | // Make *T | |
475 | copy := reflect.New(elem.Type()) | |
476 | ||
477 | // *T = elem | |
478 | copy.Elem().Set(elem) | |
479 | ||
480 | // Set elem so we decode into it | |
481 | elem = copy | |
482 | } | |
483 | ||
484 | // Decode. If we have an error then return. We also return right | |
485 | // away if we're not a copy because that means we decoded directly. | |
486 | if err := d.decode(name, data, elem); err != nil || !copied { | |
487 | return err | |
488 | } | |
489 | ||
490 | // If we're a copy, we need to set te final result | |
491 | val.Set(elem.Elem()) | |
492 | return nil | |
317 | 493 | } |
318 | 494 | |
319 | 495 | dataVal := reflect.ValueOf(data) |
437 | 613 | func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error { |
438 | 614 | dataVal := reflect.Indirect(reflect.ValueOf(data)) |
439 | 615 | dataKind := getKind(dataVal) |
616 | dataType := dataVal.Type() | |
440 | 617 | |
441 | 618 | switch { |
442 | 619 | case dataKind == reflect.Int: |
468 | 645 | } else { |
469 | 646 | return fmt.Errorf("cannot parse '%s' as uint: %s", name, err) |
470 | 647 | } |
648 | case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": | |
649 | jn := data.(json.Number) | |
650 | i, err := jn.Int64() | |
651 | if err != nil { | |
652 | return fmt.Errorf( | |
653 | "error decoding json.Number into %s: %s", name, err) | |
654 | } | |
655 | if i < 0 && !d.config.WeaklyTypedInput { | |
656 | return fmt.Errorf("cannot parse '%s', %d overflows uint", | |
657 | name, i) | |
658 | } | |
659 | val.SetUint(uint64(i)) | |
471 | 660 | default: |
472 | 661 | return fmt.Errorf( |
473 | 662 | "'%s' expected type '%s', got unconvertible type '%s'", |
677 | 866 | } |
678 | 867 | |
679 | 868 | tagValue := f.Tag.Get(d.config.TagName) |
680 | tagParts := strings.Split(tagValue, ",") | |
681 | ||
869 | keyName := f.Name | |
870 | ||
871 | // If Squash is set in the config, we squash the field down. | |
872 | squash := d.config.Squash && v.Kind() == reflect.Struct && f.Anonymous | |
682 | 873 | // Determine the name of the key in the map |
683 | keyName := f.Name | |
684 | if tagParts[0] != "" { | |
685 | if tagParts[0] == "-" { | |
874 | if index := strings.Index(tagValue, ","); index != -1 { | |
875 | if tagValue[:index] == "-" { | |
686 | 876 | continue |
687 | 877 | } |
688 | keyName = tagParts[0] | |
689 | } | |
690 | ||
691 | // If "squash" is specified in the tag, we squash the field down. | |
692 | squash := false | |
693 | for _, tag := range tagParts[1:] { | |
694 | if tag == "squash" { | |
695 | squash = true | |
696 | break | |
697 | } | |
698 | } | |
699 | if squash && v.Kind() != reflect.Struct { | |
700 | return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) | |
878 | // If "omitempty" is specified in the tag, it ignores empty values. | |
879 | if strings.Index(tagValue[index+1:], "omitempty") != -1 && isEmptyValue(v) { | |
880 | continue | |
881 | } | |
882 | ||
883 | // If "squash" is specified in the tag, we squash the field down. | |
884 | squash = !squash && strings.Index(tagValue[index+1:], "squash") != -1 | |
885 | if squash && v.Kind() != reflect.Struct { | |
886 | return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) | |
887 | } | |
888 | keyName = tagValue[:index] | |
889 | } else if len(tagValue) > 0 { | |
890 | if tagValue == "-" { | |
891 | continue | |
892 | } | |
893 | keyName = tagValue | |
701 | 894 | } |
702 | 895 | |
703 | 896 | switch v.Kind() { |
737 | 930 | return nil |
738 | 931 | } |
739 | 932 | |
740 | func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error { | |
933 | func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) (bool, error) { | |
741 | 934 | // If the input data is nil, then we want to just set the output |
742 | 935 | // pointer to be nil as well. |
743 | 936 | isNil := data == nil |
758 | 951 | val.Set(nilValue) |
759 | 952 | } |
760 | 953 | |
761 | return nil | |
954 | return true, nil | |
762 | 955 | } |
763 | 956 | |
764 | 957 | // Create an element of the concrete (non pointer) type and decode |
772 | 965 | } |
773 | 966 | |
774 | 967 | if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil { |
775 | return err | |
968 | return false, err | |
776 | 969 | } |
777 | 970 | |
778 | 971 | val.Set(realVal) |
779 | 972 | } else { |
780 | 973 | if err := d.decode(name, data, reflect.Indirect(val)); err != nil { |
781 | return err | |
782 | } | |
783 | } | |
784 | return nil | |
974 | return false, err | |
975 | } | |
976 | } | |
977 | return false, nil | |
785 | 978 | } |
786 | 979 | |
787 | 980 | func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error { |
804 | 997 | valElemType := valType.Elem() |
805 | 998 | sliceType := reflect.SliceOf(valElemType) |
806 | 999 | |
807 | valSlice := val | |
808 | if valSlice.IsNil() || d.config.ZeroFields { | |
1000 | // If we have a non array/slice type then we first attempt to convert. | |
1001 | if dataValKind != reflect.Array && dataValKind != reflect.Slice { | |
809 | 1002 | if d.config.WeaklyTypedInput { |
810 | 1003 | switch { |
811 | 1004 | // Slice and array we use the normal logic |
832 | 1025 | } |
833 | 1026 | } |
834 | 1027 | |
835 | // Check input type | |
836 | if dataValKind != reflect.Array && dataValKind != reflect.Slice { | |
837 | return fmt.Errorf( | |
838 | "'%s': source data must be an array or slice, got %s", name, dataValKind) | |
839 | ||
840 | } | |
841 | ||
842 | // If the input value is empty, then don't allocate since non-nil != nil | |
843 | if dataVal.Len() == 0 { | |
844 | return nil | |
845 | } | |
846 | ||
1028 | return fmt.Errorf( | |
1029 | "'%s': source data must be an array or slice, got %s", name, dataValKind) | |
1030 | } | |
1031 | ||
1032 | // If the input value is nil, then don't allocate since empty != nil | |
1033 | if dataVal.IsNil() { | |
1034 | return nil | |
1035 | } | |
1036 | ||
1037 | valSlice := val | |
1038 | if valSlice.IsNil() || d.config.ZeroFields { | |
847 | 1039 | // Make a new slice to hold our result, same size as the original data. |
848 | 1040 | valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len()) |
849 | 1041 | } |
1004 | 1196 | field reflect.StructField |
1005 | 1197 | val reflect.Value |
1006 | 1198 | } |
1199 | ||
1200 | // remainField is set to a valid field set with the "remain" tag if | |
1201 | // we are keeping track of remaining values. | |
1202 | var remainField *field | |
1203 | ||
1007 | 1204 | fields := []field{} |
1008 | 1205 | for len(structs) > 0 { |
1009 | 1206 | structVal := structs[0] |
1016 | 1213 | fieldKind := fieldType.Type.Kind() |
1017 | 1214 | |
1018 | 1215 | // If "squash" is specified in the tag, we squash the field down. |
1019 | squash := false | |
1216 | squash := d.config.Squash && fieldKind == reflect.Struct && fieldType.Anonymous | |
1217 | remain := false | |
1218 | ||
1219 | // We always parse the tags cause we're looking for other tags too | |
1020 | 1220 | tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",") |
1021 | 1221 | for _, tag := range tagParts[1:] { |
1022 | 1222 | if tag == "squash" { |
1023 | 1223 | squash = true |
1224 | break | |
1225 | } | |
1226 | ||
1227 | if tag == "remain" { | |
1228 | remain = true | |
1024 | 1229 | break |
1025 | 1230 | } |
1026 | 1231 | } |
1035 | 1240 | continue |
1036 | 1241 | } |
1037 | 1242 | |
1038 | // Normal struct field, store it away | |
1039 | fields = append(fields, field{fieldType, structVal.Field(i)}) | |
1243 | // Build our field | |
1244 | if remain { | |
1245 | remainField = &field{fieldType, structVal.Field(i)} | |
1246 | } else { | |
1247 | // Normal struct field, store it away | |
1248 | fields = append(fields, field{fieldType, structVal.Field(i)}) | |
1249 | } | |
1040 | 1250 | } |
1041 | 1251 | } |
1042 | 1252 | |
1077 | 1287 | } |
1078 | 1288 | } |
1079 | 1289 | |
1080 | // Delete the key we're using from the unused map so we stop tracking | |
1081 | delete(dataValKeysUnused, rawMapKey.Interface()) | |
1082 | ||
1083 | 1290 | if !fieldValue.IsValid() { |
1084 | 1291 | // This should never happen |
1085 | 1292 | panic("field is not valid") |
1091 | 1298 | continue |
1092 | 1299 | } |
1093 | 1300 | |
1301 | // Delete the key we're using from the unused map so we stop tracking | |
1302 | delete(dataValKeysUnused, rawMapKey.Interface()) | |
1303 | ||
1094 | 1304 | // If the name is empty string, then we're at the root, and we |
1095 | 1305 | // don't dot-join the fields. |
1096 | 1306 | if name != "" { |
1100 | 1310 | if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil { |
1101 | 1311 | errors = appendErrors(errors, err) |
1102 | 1312 | } |
1313 | } | |
1314 | ||
1315 | // If we have a "remain"-tagged field and we have unused keys then | |
1316 | // we put the unused keys directly into the remain field. | |
1317 | if remainField != nil && len(dataValKeysUnused) > 0 { | |
1318 | // Build a map of only the unused values | |
1319 | remain := map[interface{}]interface{}{} | |
1320 | for key := range dataValKeysUnused { | |
1321 | remain[key] = dataVal.MapIndex(reflect.ValueOf(key)).Interface() | |
1322 | } | |
1323 | ||
1324 | // Decode it as-if we were just decoding this map onto our map. | |
1325 | if err := d.decodeMap(name, remain, remainField.val); err != nil { | |
1326 | errors = appendErrors(errors, err) | |
1327 | } | |
1328 | ||
1329 | // Set the map to nil so we have none so that the next check will | |
1330 | // not error (ErrorUnused) | |
1331 | dataValKeysUnused = nil | |
1103 | 1332 | } |
1104 | 1333 | |
1105 | 1334 | if d.config.ErrorUnused && len(dataValKeysUnused) > 0 { |
1130 | 1359 | } |
1131 | 1360 | |
1132 | 1361 | return nil |
1362 | } | |
1363 | ||
1364 | func isEmptyValue(v reflect.Value) bool { | |
1365 | switch getKind(v) { | |
1366 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: | |
1367 | return v.Len() == 0 | |
1368 | case reflect.Bool: | |
1369 | return !v.Bool() | |
1370 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
1371 | return v.Int() == 0 | |
1372 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: | |
1373 | return v.Uint() == 0 | |
1374 | case reflect.Float32, reflect.Float64: | |
1375 | return v.Float() == 0 | |
1376 | case reflect.Interface, reflect.Ptr: | |
1377 | return v.IsNil() | |
1378 | } | |
1379 | return false | |
1133 | 1380 | } |
1134 | 1381 | |
1135 | 1382 | func getKind(val reflect.Value) reflect.Kind { |
4 | 4 | "testing" |
5 | 5 | ) |
6 | 6 | |
7 | type Person struct { | |
8 | Name string | |
9 | Age int | |
10 | Emails []string | |
11 | Extra map[string]string | |
12 | } | |
13 | ||
7 | 14 | func Benchmark_Decode(b *testing.B) { |
8 | type Person struct { | |
9 | Name string | |
10 | Age int | |
11 | Emails []string | |
12 | Extra map[string]string | |
13 | } | |
14 | ||
15 | 15 | input := map[string]interface{}{ |
16 | 16 | "name": "Mitchell", |
17 | 17 | "age": 91, |
40 | 40 | } |
41 | 41 | |
42 | 42 | func Benchmark_DecodeViaJSON(b *testing.B) { |
43 | type Person struct { | |
44 | Name string | |
45 | Age int | |
46 | Emails []string | |
47 | Extra map[string]string | |
48 | } | |
49 | ||
50 | 43 | input := map[string]interface{}{ |
51 | 44 | "name": "Mitchell", |
52 | 45 | "age": 91, |
62 | 55 | } |
63 | 56 | } |
64 | 57 | |
58 | func Benchmark_JSONUnmarshal(b *testing.B) { | |
59 | input := map[string]interface{}{ | |
60 | "name": "Mitchell", | |
61 | "age": 91, | |
62 | "emails": []string{"one", "two", "three"}, | |
63 | "extra": map[string]string{ | |
64 | "twitter": "mitchellh", | |
65 | }, | |
66 | } | |
67 | ||
68 | inputB, err := json.Marshal(input) | |
69 | if err != nil { | |
70 | b.Fatal("Failed to marshal test input:", err) | |
71 | } | |
72 | ||
73 | var result Person | |
74 | for i := 0; i < b.N; i++ { | |
75 | json.Unmarshal(inputB, &result) | |
76 | } | |
77 | } | |
78 | ||
65 | 79 | func Benchmark_DecodeBasic(b *testing.B) { |
66 | 80 | input := map[string]interface{}{ |
67 | "vstring": "foo", | |
68 | "vint": 42, | |
69 | "Vuint": 42, | |
70 | "vbool": true, | |
71 | "Vfloat": 42.42, | |
72 | "vsilent": true, | |
73 | "vdata": 42, | |
74 | } | |
75 | ||
76 | var result Basic | |
77 | for i := 0; i < b.N; i++ { | |
81 | "vstring": "foo", | |
82 | "vint": 42, | |
83 | "Vuint": 42, | |
84 | "vbool": true, | |
85 | "Vfloat": 42.42, | |
86 | "vsilent": true, | |
87 | "vdata": 42, | |
88 | "vjsonInt": json.Number("1234"), | |
89 | "vjsonFloat": json.Number("1234.5"), | |
90 | "vjsonNumber": json.Number("1234.5"), | |
91 | } | |
92 | ||
93 | for i := 0; i < b.N; i++ { | |
94 | var result Basic | |
78 | 95 | Decode(input, &result) |
79 | 96 | } |
80 | 97 | } |
182 | 199 | } |
183 | 200 | |
184 | 201 | func Benchmark_DecodeWeaklyTypedInput(b *testing.B) { |
185 | type Person struct { | |
186 | Name string | |
187 | Age int | |
188 | Emails []string | |
189 | } | |
190 | ||
191 | 202 | // This input can come from anywhere, but typically comes from |
192 | 203 | // something like decoding JSON, generated by a weakly typed language |
193 | 204 | // such as PHP. |
214 | 225 | } |
215 | 226 | |
216 | 227 | func Benchmark_DecodeMetadata(b *testing.B) { |
217 | type Person struct { | |
218 | Name string | |
219 | Age int | |
220 | } | |
221 | ||
222 | 228 | input := map[string]interface{}{ |
223 | 229 | "name": "Mitchell", |
224 | 230 | "age": 91, |
457 | 457 | } |
458 | 458 | } |
459 | 459 | } |
460 | ||
461 | // #103 Check for data type before trying to access its composants prevent a panic error | |
462 | // in decodeSlice | |
463 | func TestDecodeBadDataTypeInSlice(t *testing.T) { | |
464 | t.Parallel() | |
465 | ||
466 | input := map[string]interface{}{ | |
467 | "Toto": "titi", | |
468 | } | |
469 | result := []struct { | |
470 | Toto string | |
471 | }{} | |
472 | ||
473 | if err := Decode(input, &result); err == nil { | |
474 | t.Error("An error was expected, got nil") | |
475 | } | |
476 | } |
200 | 200 | // Output: |
201 | 201 | // Mitchell Hashimoto, San Francisco |
202 | 202 | } |
203 | ||
204 | func ExampleDecode_remainingData() { | |
205 | // Note that the mapstructure tags defined in the struct type | |
206 | // can indicate which fields the values are mapped to. | |
207 | type Person struct { | |
208 | Name string | |
209 | Age int | |
210 | Other map[string]interface{} `mapstructure:",remain"` | |
211 | } | |
212 | ||
213 | input := map[string]interface{}{ | |
214 | "name": "Mitchell", | |
215 | "age": 91, | |
216 | "email": "mitchell@example.com", | |
217 | } | |
218 | ||
219 | var result Person | |
220 | err := Decode(input, &result) | |
221 | if err != nil { | |
222 | panic(err) | |
223 | } | |
224 | ||
225 | fmt.Printf("%#v", result) | |
226 | // Output: | |
227 | // mapstructure.Person{Name:"Mitchell", Age:91, Other:map[string]interface {}{"email":"mitchell@example.com"}} | |
228 | } | |
229 | ||
230 | func ExampleDecode_omitempty() { | |
231 | // Add omitempty annotation to avoid map keys for empty values | |
232 | type Family struct { | |
233 | LastName string | |
234 | } | |
235 | type Location struct { | |
236 | City string | |
237 | } | |
238 | type Person struct { | |
239 | *Family `mapstructure:",omitempty"` | |
240 | *Location `mapstructure:",omitempty"` | |
241 | Age int | |
242 | FirstName string | |
243 | } | |
244 | ||
245 | result := &map[string]interface{}{} | |
246 | input := Person{FirstName: "Somebody"} | |
247 | err := Decode(input, &result) | |
248 | if err != nil { | |
249 | panic(err) | |
250 | } | |
251 | ||
252 | fmt.Printf("%+v", result) | |
253 | // Output: | |
254 | // &map[Age:0 FirstName:Somebody] | |
255 | } |
11 | 11 | type Basic struct { |
12 | 12 | Vstring string |
13 | 13 | Vint int |
14 | Vint8 int8 | |
15 | Vint16 int16 | |
16 | Vint32 int32 | |
17 | Vint64 int64 | |
14 | 18 | Vuint uint |
15 | 19 | Vbool bool |
16 | 20 | Vfloat float64 |
18 | 22 | vsilent bool |
19 | 23 | Vdata interface{} |
20 | 24 | VjsonInt int |
25 | VjsonUint uint | |
21 | 26 | VjsonFloat float64 |
22 | 27 | VjsonNumber json.Number |
23 | 28 | } |
55 | 60 | Vunique string |
56 | 61 | } |
57 | 62 | |
63 | type EmbeddedAndNamed struct { | |
64 | Basic | |
65 | Named Basic | |
66 | Vunique string | |
67 | } | |
68 | ||
58 | 69 | type SliceAlias []string |
59 | 70 | |
60 | 71 | type EmbeddedSlice struct { |
134 | 145 | type Tagged struct { |
135 | 146 | Extra string `mapstructure:"bar,what,what"` |
136 | 147 | Value string `mapstructure:"foo"` |
148 | } | |
149 | ||
150 | type Remainder struct { | |
151 | A string | |
152 | Extra map[string]interface{} `mapstructure:",remain"` | |
153 | } | |
154 | ||
155 | type StructWithOmitEmpty struct { | |
156 | VisibleStringField string `mapstructure:"visible-string"` | |
157 | OmitStringField string `mapstructure:"omittable-string,omitempty"` | |
158 | VisibleIntField int `mapstructure:"visible-int"` | |
159 | OmitIntField int `mapstructure:"omittable-int,omitempty"` | |
160 | VisibleFloatField float64 `mapstructure:"visible-float"` | |
161 | OmitFloatField float64 `mapstructure:"omittable-float,omitempty"` | |
162 | VisibleSliceField []interface{} `mapstructure:"visible-slice"` | |
163 | OmitSliceField []interface{} `mapstructure:"omittable-slice,omitempty"` | |
164 | VisibleMapField map[string]interface{} `mapstructure:"visible-map"` | |
165 | OmitMapField map[string]interface{} `mapstructure:"omittable-map,omitempty"` | |
166 | NestedField *Nested `mapstructure:"visible-nested"` | |
167 | OmitNestedField *Nested `mapstructure:"omittable-nested,omitempty"` | |
137 | 168 | } |
138 | 169 | |
139 | 170 | type TypeConversionResult struct { |
176 | 207 | input := map[string]interface{}{ |
177 | 208 | "vstring": "foo", |
178 | 209 | "vint": 42, |
210 | "vint8": 42, | |
211 | "vint16": 42, | |
212 | "vint32": 42, | |
213 | "vint64": 42, | |
179 | 214 | "Vuint": 42, |
180 | 215 | "vbool": true, |
181 | 216 | "Vfloat": 42.42, |
182 | 217 | "vsilent": true, |
183 | 218 | "vdata": 42, |
184 | 219 | "vjsonInt": json.Number("1234"), |
220 | "vjsonUint": json.Number("1234"), | |
185 | 221 | "vjsonFloat": json.Number("1234.5"), |
186 | 222 | "vjsonNumber": json.Number("1234.5"), |
187 | 223 | } |
200 | 236 | if result.Vint != 42 { |
201 | 237 | t.Errorf("vint value should be 42: %#v", result.Vint) |
202 | 238 | } |
239 | if result.Vint8 != 42 { | |
240 | t.Errorf("vint8 value should be 42: %#v", result.Vint) | |
241 | } | |
242 | if result.Vint16 != 42 { | |
243 | t.Errorf("vint16 value should be 42: %#v", result.Vint) | |
244 | } | |
245 | if result.Vint32 != 42 { | |
246 | t.Errorf("vint32 value should be 42: %#v", result.Vint) | |
247 | } | |
248 | if result.Vint64 != 42 { | |
249 | t.Errorf("vint64 value should be 42: %#v", result.Vint) | |
250 | } | |
203 | 251 | |
204 | 252 | if result.Vuint != 42 { |
205 | 253 | t.Errorf("vuint value should be 42: %#v", result.Vuint) |
227 | 275 | |
228 | 276 | if result.VjsonInt != 1234 { |
229 | 277 | t.Errorf("vjsonint value should be 1234: %#v", result.VjsonInt) |
278 | } | |
279 | ||
280 | if result.VjsonUint != 1234 { | |
281 | t.Errorf("vjsonuint value should be 1234: %#v", result.VjsonUint) | |
230 | 282 | } |
231 | 283 | |
232 | 284 | if result.VjsonFloat != 1234.5 { |
298 | 350 | } |
299 | 351 | if !reflect.DeepEqual(result, expected) { |
300 | 352 | t.Fatalf("bad: %#v", result) |
353 | } | |
354 | } | |
355 | ||
356 | func TestBasic_interfaceStruct(t *testing.T) { | |
357 | t.Parallel() | |
358 | ||
359 | input := map[string]interface{}{ | |
360 | "vstring": "foo", | |
361 | } | |
362 | ||
363 | var iface interface{} = &Basic{} | |
364 | err := Decode(input, &iface) | |
365 | if err != nil { | |
366 | t.Fatalf("got an err: %s", err) | |
367 | } | |
368 | ||
369 | expected := &Basic{ | |
370 | Vstring: "foo", | |
371 | } | |
372 | if !reflect.DeepEqual(iface, expected) { | |
373 | t.Fatalf("bad: %#v", iface) | |
374 | } | |
375 | } | |
376 | ||
377 | // Issue 187 | |
378 | func TestBasic_interfaceStructNonPtr(t *testing.T) { | |
379 | t.Parallel() | |
380 | ||
381 | input := map[string]interface{}{ | |
382 | "vstring": "foo", | |
383 | } | |
384 | ||
385 | var iface interface{} = Basic{} | |
386 | err := Decode(input, &iface) | |
387 | if err != nil { | |
388 | t.Fatalf("got an err: %s", err) | |
389 | } | |
390 | ||
391 | expected := Basic{ | |
392 | Vstring: "foo", | |
393 | } | |
394 | if !reflect.DeepEqual(iface, expected) { | |
395 | t.Fatalf("bad: %#v", iface) | |
301 | 396 | } |
302 | 397 | } |
303 | 398 | |
449 | 544 | } |
450 | 545 | } |
451 | 546 | |
547 | func TestDecode_EmbeddedNoSquash(t *testing.T) { | |
548 | t.Parallel() | |
549 | ||
550 | input := map[string]interface{}{ | |
551 | "vstring": "foo", | |
552 | "vunique": "bar", | |
553 | } | |
554 | ||
555 | var result Embedded | |
556 | err := Decode(input, &result) | |
557 | if err != nil { | |
558 | t.Fatalf("got an err: %s", err.Error()) | |
559 | } | |
560 | ||
561 | if result.Vstring != "" { | |
562 | t.Errorf("vstring value should be empty: %#v", result.Vstring) | |
563 | } | |
564 | ||
565 | if result.Vunique != "bar" { | |
566 | t.Errorf("vunique value should be 'bar': %#v", result.Vunique) | |
567 | } | |
568 | } | |
569 | ||
570 | func TestDecode_EmbeddedPointerNoSquash(t *testing.T) { | |
571 | t.Parallel() | |
572 | ||
573 | input := map[string]interface{}{ | |
574 | "vstring": "foo", | |
575 | "vunique": "bar", | |
576 | } | |
577 | ||
578 | result := EmbeddedPointer{ | |
579 | Basic: &Basic{}, | |
580 | } | |
581 | ||
582 | err := Decode(input, &result) | |
583 | if err != nil { | |
584 | t.Fatalf("err: %s", err) | |
585 | } | |
586 | ||
587 | if result.Vstring != "" { | |
588 | t.Errorf("vstring value should be empty: %#v", result.Vstring) | |
589 | } | |
590 | ||
591 | if result.Vunique != "bar" { | |
592 | t.Errorf("vunique value should be 'bar': %#v", result.Vunique) | |
593 | } | |
594 | } | |
595 | ||
452 | 596 | func TestDecode_EmbeddedSquash(t *testing.T) { |
453 | 597 | t.Parallel() |
454 | 598 | |
507 | 651 | t.Error("vunique should be present in map") |
508 | 652 | } else if !reflect.DeepEqual(v, "bar") { |
509 | 653 | t.Errorf("vunique value should be 'bar': %#v", v) |
654 | } | |
655 | } | |
656 | ||
657 | func TestDecode_EmbeddedSquashConfig(t *testing.T) { | |
658 | t.Parallel() | |
659 | ||
660 | input := map[string]interface{}{ | |
661 | "vstring": "foo", | |
662 | "vunique": "bar", | |
663 | "Named": map[string]interface{}{ | |
664 | "vstring": "baz", | |
665 | }, | |
666 | } | |
667 | ||
668 | var result EmbeddedAndNamed | |
669 | config := &DecoderConfig{ | |
670 | Squash: true, | |
671 | Result: &result, | |
672 | } | |
673 | ||
674 | decoder, err := NewDecoder(config) | |
675 | if err != nil { | |
676 | t.Fatalf("err: %s", err) | |
677 | } | |
678 | ||
679 | err = decoder.Decode(input) | |
680 | if err != nil { | |
681 | t.Fatalf("got an err: %s", err) | |
682 | } | |
683 | ||
684 | if result.Vstring != "foo" { | |
685 | t.Errorf("vstring value should be 'foo': %#v", result.Vstring) | |
686 | } | |
687 | ||
688 | if result.Vunique != "bar" { | |
689 | t.Errorf("vunique value should be 'bar': %#v", result.Vunique) | |
690 | } | |
691 | ||
692 | if result.Named.Vstring != "baz" { | |
693 | t.Errorf("Named.vstring value should be 'baz': %#v", result.Named.Vstring) | |
694 | } | |
695 | } | |
696 | ||
697 | func TestDecodeFrom_EmbeddedSquashConfig(t *testing.T) { | |
698 | t.Parallel() | |
699 | ||
700 | input := EmbeddedAndNamed{ | |
701 | Basic: Basic{Vstring: "foo"}, | |
702 | Named: Basic{Vstring: "baz"}, | |
703 | Vunique: "bar", | |
704 | } | |
705 | ||
706 | result := map[string]interface{}{} | |
707 | config := &DecoderConfig{ | |
708 | Squash: true, | |
709 | Result: &result, | |
710 | } | |
711 | decoder, err := NewDecoder(config) | |
712 | if err != nil { | |
713 | t.Fatalf("got an err: %s", err.Error()) | |
714 | } | |
715 | ||
716 | err = decoder.Decode(input) | |
717 | if err != nil { | |
718 | t.Fatalf("got an err: %s", err.Error()) | |
719 | } | |
720 | ||
721 | if _, ok := result["Basic"]; ok { | |
722 | t.Error("basic should not be present in map") | |
723 | } | |
724 | ||
725 | v, ok := result["Vstring"] | |
726 | if !ok { | |
727 | t.Error("vstring should be present in map") | |
728 | } else if !reflect.DeepEqual(v, "foo") { | |
729 | t.Errorf("vstring value should be 'foo': %#v", v) | |
730 | } | |
731 | ||
732 | v, ok = result["Vunique"] | |
733 | if !ok { | |
734 | t.Error("vunique should be present in map") | |
735 | } else if !reflect.DeepEqual(v, "bar") { | |
736 | t.Errorf("vunique value should be 'bar': %#v", v) | |
737 | } | |
738 | ||
739 | v, ok = result["Named"] | |
740 | if !ok { | |
741 | t.Error("Named should be present in map") | |
742 | } else { | |
743 | named := v.(map[string]interface{}) | |
744 | v, ok := named["Vstring"] | |
745 | if !ok { | |
746 | t.Error("Named: vstring should be present in map") | |
747 | } else if !reflect.DeepEqual(v, "baz") { | |
748 | t.Errorf("Named: vstring should be 'baz': %#v", v) | |
749 | } | |
510 | 750 | } |
511 | 751 | } |
512 | 752 | |
905 | 1145 | } |
906 | 1146 | } |
907 | 1147 | |
1148 | func TestDecoder_ErrorUnused_NotSetable(t *testing.T) { | |
1149 | t.Parallel() | |
1150 | ||
1151 | // lowercase vsilent is unexported and cannot be set | |
1152 | input := map[string]interface{}{ | |
1153 | "vsilent": "false", | |
1154 | } | |
1155 | ||
1156 | var result Basic | |
1157 | config := &DecoderConfig{ | |
1158 | ErrorUnused: true, | |
1159 | Result: &result, | |
1160 | } | |
1161 | ||
1162 | decoder, err := NewDecoder(config) | |
1163 | if err != nil { | |
1164 | t.Fatalf("err: %s", err) | |
1165 | } | |
1166 | ||
1167 | err = decoder.Decode(input) | |
1168 | if err == nil { | |
1169 | t.Fatal("expected error") | |
1170 | } | |
1171 | } | |
1172 | ||
908 | 1173 | func TestMap(t *testing.T) { |
909 | 1174 | t.Parallel() |
910 | 1175 | |
1374 | 1639 | &Basic{ |
1375 | 1640 | Vstring: "vstring", |
1376 | 1641 | Vint: 2, |
1642 | Vint8: 2, | |
1643 | Vint16: 2, | |
1644 | Vint32: 2, | |
1645 | Vint64: 2, | |
1377 | 1646 | Vuint: 3, |
1378 | 1647 | Vbool: true, |
1379 | 1648 | Vfloat: 4.56, |
1385 | 1654 | &map[string]interface{}{ |
1386 | 1655 | "Vstring": "vstring", |
1387 | 1656 | "Vint": 2, |
1657 | "Vint8": int8(2), | |
1658 | "Vint16": int16(2), | |
1659 | "Vint32": int32(2), | |
1660 | "Vint64": int64(2), | |
1388 | 1661 | "Vuint": uint(3), |
1389 | 1662 | "Vbool": true, |
1390 | 1663 | "Vfloat": 4.56, |
1391 | 1664 | "Vextra": "vextra", |
1392 | 1665 | "Vdata": []byte("data"), |
1393 | 1666 | "VjsonInt": 0, |
1667 | "VjsonUint": uint(0), | |
1394 | 1668 | "VjsonFloat": 0.0, |
1395 | 1669 | "VjsonNumber": json.Number(""), |
1396 | 1670 | }, |
1403 | 1677 | Basic: Basic{ |
1404 | 1678 | Vstring: "vstring", |
1405 | 1679 | Vint: 2, |
1680 | Vint8: 2, | |
1681 | Vint16: 2, | |
1682 | Vint32: 2, | |
1683 | Vint64: 2, | |
1406 | 1684 | Vuint: 3, |
1407 | 1685 | Vbool: true, |
1408 | 1686 | Vfloat: 4.56, |
1417 | 1695 | "Basic": map[string]interface{}{ |
1418 | 1696 | "Vstring": "vstring", |
1419 | 1697 | "Vint": 2, |
1698 | "Vint8": int8(2), | |
1699 | "Vint16": int16(2), | |
1700 | "Vint32": int32(2), | |
1701 | "Vint64": int64(2), | |
1420 | 1702 | "Vuint": uint(3), |
1421 | 1703 | "Vbool": true, |
1422 | 1704 | "Vfloat": 4.56, |
1423 | 1705 | "Vextra": "vextra", |
1424 | 1706 | "Vdata": []byte("data"), |
1425 | 1707 | "VjsonInt": 0, |
1708 | "VjsonUint": uint(0), | |
1426 | 1709 | "VjsonFloat": 0.0, |
1427 | 1710 | "VjsonNumber": json.Number(""), |
1428 | 1711 | }, |
1541 | 1824 | &map[string]interface{}{ |
1542 | 1825 | "Vfoo": "vfoo", |
1543 | 1826 | "Vbar": []string{"foo", "bar"}, |
1827 | }, | |
1828 | false, | |
1829 | }, | |
1830 | { | |
1831 | "struct with empty slice", | |
1832 | &map[string]interface{}{ | |
1833 | "Vbar": []string{}, | |
1834 | }, | |
1835 | &Slice{}, | |
1836 | &Slice{ | |
1837 | Vbar: []string{}, | |
1544 | 1838 | }, |
1545 | 1839 | false, |
1546 | 1840 | }, |
1630 | 1924 | &map[string]int{}, |
1631 | 1925 | true, |
1632 | 1926 | }, |
1927 | { | |
1928 | "remainder", | |
1929 | map[string]interface{}{ | |
1930 | "A": "hello", | |
1931 | "B": "goodbye", | |
1932 | "C": "yo", | |
1933 | }, | |
1934 | &Remainder{}, | |
1935 | &Remainder{ | |
1936 | A: "hello", | |
1937 | Extra: map[string]interface{}{ | |
1938 | "B": "goodbye", | |
1939 | "C": "yo", | |
1940 | }, | |
1941 | }, | |
1942 | false, | |
1943 | }, | |
1944 | { | |
1945 | "remainder with no extra", | |
1946 | map[string]interface{}{ | |
1947 | "A": "hello", | |
1948 | }, | |
1949 | &Remainder{}, | |
1950 | &Remainder{ | |
1951 | A: "hello", | |
1952 | Extra: nil, | |
1953 | }, | |
1954 | false, | |
1955 | }, | |
1956 | { | |
1957 | "struct with omitempty tag return non-empty values", | |
1958 | &struct { | |
1959 | VisibleField interface{} `mapstructure:"visible"` | |
1960 | OmitField interface{} `mapstructure:"omittable,omitempty"` | |
1961 | }{ | |
1962 | VisibleField: nil, | |
1963 | OmitField: "string", | |
1964 | }, | |
1965 | &map[string]interface{}{}, | |
1966 | &map[string]interface{}{"visible": nil, "omittable": "string"}, | |
1967 | false, | |
1968 | }, | |
1969 | { | |
1970 | "struct with omitempty tag ignore empty values", | |
1971 | &struct { | |
1972 | VisibleField interface{} `mapstructure:"visible"` | |
1973 | OmitField interface{} `mapstructure:"omittable,omitempty"` | |
1974 | }{ | |
1975 | VisibleField: nil, | |
1976 | OmitField: nil, | |
1977 | }, | |
1978 | &map[string]interface{}{}, | |
1979 | &map[string]interface{}{"visible": nil}, | |
1980 | false, | |
1981 | }, | |
1633 | 1982 | } |
1634 | 1983 | |
1635 | 1984 | for _, tt := range tests { |
1712 | 2061 | "vbar": map[string]interface{}{ |
1713 | 2062 | "vstring": "foo", |
1714 | 2063 | "Vuint": 42, |
2064 | "vsilent": "false", | |
1715 | 2065 | "foo": "bar", |
1716 | 2066 | }, |
1717 | 2067 | "bar": "nil", |
1731 | 2081 | t.Fatalf("bad keys: %#v", md.Keys) |
1732 | 2082 | } |
1733 | 2083 | |
1734 | expectedUnused := []string{"Vbar.foo", "bar"} | |
2084 | expectedUnused := []string{"Vbar.foo", "Vbar.vsilent", "bar"} | |
2085 | sort.Strings(md.Unused) | |
1735 | 2086 | if !reflect.DeepEqual(md.Unused, expectedUnused) { |
1736 | 2087 | t.Fatalf("bad unused: %#v", md.Unused) |
1737 | 2088 | } |
1739 | 2090 | |
1740 | 2091 | func TestMetadata(t *testing.T) { |
1741 | 2092 | t.Parallel() |
2093 | ||
2094 | type testResult struct { | |
2095 | Vfoo string | |
2096 | Vbar BasicPointer | |
2097 | } | |
1742 | 2098 | |
1743 | 2099 | input := map[string]interface{}{ |
1744 | 2100 | "vfoo": "foo", |
1745 | 2101 | "vbar": map[string]interface{}{ |
1746 | 2102 | "vstring": "foo", |
1747 | 2103 | "Vuint": 42, |
2104 | "vsilent": "false", | |
1748 | 2105 | "foo": "bar", |
1749 | 2106 | }, |
1750 | 2107 | "bar": "nil", |
1751 | 2108 | } |
1752 | 2109 | |
1753 | 2110 | var md Metadata |
1754 | var result Nested | |
2111 | var result testResult | |
1755 | 2112 | config := &DecoderConfig{ |
1756 | 2113 | Metadata: &md, |
1757 | 2114 | Result: &result, |
1773 | 2130 | t.Fatalf("bad keys: %#v", md.Keys) |
1774 | 2131 | } |
1775 | 2132 | |
1776 | expectedUnused := []string{"Vbar.foo", "bar"} | |
2133 | expectedUnused := []string{"Vbar.foo", "Vbar.vsilent", "bar"} | |
2134 | sort.Strings(md.Unused) | |
1777 | 2135 | if !reflect.DeepEqual(md.Unused, expectedUnused) { |
1778 | 2136 | t.Fatalf("bad unused: %#v", md.Unused) |
1779 | 2137 | } |
1881 | 2239 | t.Parallel() |
1882 | 2240 | |
1883 | 2241 | input := map[string]interface{}{ |
1884 | "foo": "4", | |
1885 | "bar": "value", | |
1886 | "unused": "value", | |
2242 | "foo": "4", | |
2243 | "bar": "value", | |
2244 | "unused": "value", | |
2245 | "unexported": "value", | |
1887 | 2246 | } |
1888 | 2247 | |
1889 | 2248 | var md Metadata |
1890 | 2249 | var result struct { |
1891 | Foo int | |
1892 | Bar string | |
2250 | Foo int | |
2251 | Bar string | |
2252 | unexported string | |
1893 | 2253 | } |
1894 | 2254 | |
1895 | 2255 | if err := WeakDecodeMetadata(input, &result, &md); err != nil { |
1908 | 2268 | t.Fatalf("bad keys: %#v", md.Keys) |
1909 | 2269 | } |
1910 | 2270 | |
1911 | expectedUnused := []string{"unused"} | |
2271 | expectedUnused := []string{"unexported", "unused"} | |
2272 | sort.Strings(md.Unused) | |
1912 | 2273 | if !reflect.DeepEqual(md.Unused, expectedUnused) { |
1913 | 2274 | t.Fatalf("bad unused: %#v", md.Unused) |
2275 | } | |
2276 | } | |
2277 | ||
2278 | func TestDecode_StructTaggedWithOmitempty_OmitEmptyValues(t *testing.T) { | |
2279 | t.Parallel() | |
2280 | ||
2281 | input := &StructWithOmitEmpty{} | |
2282 | ||
2283 | var emptySlice []interface{} | |
2284 | var emptyMap map[string]interface{} | |
2285 | var emptyNested *Nested | |
2286 | expected := &map[string]interface{}{ | |
2287 | "visible-string": "", | |
2288 | "visible-int": 0, | |
2289 | "visible-float": 0.0, | |
2290 | "visible-slice": emptySlice, | |
2291 | "visible-map": emptyMap, | |
2292 | "visible-nested": emptyNested, | |
2293 | } | |
2294 | ||
2295 | actual := &map[string]interface{}{} | |
2296 | Decode(input, actual) | |
2297 | ||
2298 | if !reflect.DeepEqual(actual, expected) { | |
2299 | t.Fatalf("Decode() expected: %#v, got: %#v", expected, actual) | |
2300 | } | |
2301 | } | |
2302 | ||
2303 | func TestDecode_StructTaggedWithOmitempty_KeepNonEmptyValues(t *testing.T) { | |
2304 | t.Parallel() | |
2305 | ||
2306 | input := &StructWithOmitEmpty{ | |
2307 | VisibleStringField: "", | |
2308 | OmitStringField: "string", | |
2309 | VisibleIntField: 0, | |
2310 | OmitIntField: 1, | |
2311 | VisibleFloatField: 0.0, | |
2312 | OmitFloatField: 1.0, | |
2313 | VisibleSliceField: nil, | |
2314 | OmitSliceField: []interface{}{1}, | |
2315 | VisibleMapField: nil, | |
2316 | OmitMapField: map[string]interface{}{"k": "v"}, | |
2317 | NestedField: nil, | |
2318 | OmitNestedField: &Nested{}, | |
2319 | } | |
2320 | ||
2321 | var emptySlice []interface{} | |
2322 | var emptyMap map[string]interface{} | |
2323 | var emptyNested *Nested | |
2324 | expected := &map[string]interface{}{ | |
2325 | "visible-string": "", | |
2326 | "omittable-string": "string", | |
2327 | "visible-int": 0, | |
2328 | "omittable-int": 1, | |
2329 | "visible-float": 0.0, | |
2330 | "omittable-float": 1.0, | |
2331 | "visible-slice": emptySlice, | |
2332 | "omittable-slice": []interface{}{1}, | |
2333 | "visible-map": emptyMap, | |
2334 | "omittable-map": map[string]interface{}{"k": "v"}, | |
2335 | "visible-nested": emptyNested, | |
2336 | "omittable-nested": &Nested{}, | |
2337 | } | |
2338 | ||
2339 | actual := &map[string]interface{}{} | |
2340 | Decode(input, actual) | |
2341 | ||
2342 | if !reflect.DeepEqual(actual, expected) { | |
2343 | t.Fatalf("Decode() expected: %#v, got: %#v", expected, actual) | |
1914 | 2344 | } |
1915 | 2345 | } |
1916 | 2346 |