Merge pull request #205 from RussellLuo/add-support-for-squashing-struct-ptr
Add support for squashing embedded struct pointers
Mitchell Hashimoto authored 3 years ago
GitHub committed 3 years ago
861 | 861 | // Next get the actual value of this field and verify it is assignable |
862 | 862 | // to the map value. |
863 | 863 | v := dataVal.Field(i) |
864 | if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct { | |
865 | // Handle embedded struct pointers as embedded structs. | |
866 | v = v.Elem() | |
867 | } | |
864 | 868 | if !v.Type().AssignableTo(valMap.Type().Elem()) { |
865 | 869 | return fmt.Errorf("cannot assign type '%s' to map value field of type '%s'", v.Type(), valMap.Type().Elem()) |
866 | 870 | } |
1231 | 1235 | |
1232 | 1236 | for i := 0; i < structType.NumField(); i++ { |
1233 | 1237 | fieldType := structType.Field(i) |
1234 | fieldKind := fieldType.Type.Kind() | |
1238 | fieldVal := structVal.Field(i) | |
1239 | if fieldVal.Kind() == reflect.Ptr && fieldVal.Elem().Kind() == reflect.Struct { | |
1240 | // Handle embedded struct pointers as embedded structs. | |
1241 | fieldVal = fieldVal.Elem() | |
1242 | } | |
1235 | 1243 | |
1236 | 1244 | // If "squash" is specified in the tag, we squash the field down. |
1237 | squash := d.config.Squash && fieldKind == reflect.Struct && fieldType.Anonymous | |
1245 | squash := d.config.Squash && fieldVal.Kind() == reflect.Struct && fieldType.Anonymous | |
1238 | 1246 | remain := false |
1239 | 1247 | |
1240 | 1248 | // We always parse the tags cause we're looking for other tags too |
1252 | 1260 | } |
1253 | 1261 | |
1254 | 1262 | if squash { |
1255 | if fieldKind != reflect.Struct { | |
1263 | if fieldVal.Kind() != reflect.Struct { | |
1256 | 1264 | errors = appendErrors(errors, |
1257 | fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldKind)) | |
1265 | fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind())) | |
1258 | 1266 | } else { |
1259 | structs = append(structs, structVal.FieldByName(fieldType.Name)) | |
1267 | structs = append(structs, fieldVal) | |
1260 | 1268 | } |
1261 | 1269 | continue |
1262 | 1270 | } |
1263 | 1271 | |
1264 | 1272 | // Build our field |
1265 | 1273 | if remain { |
1266 | remainField = &field{fieldType, structVal.Field(i)} | |
1274 | remainField = &field{fieldType, fieldVal} | |
1267 | 1275 | } else { |
1268 | 1276 | // Normal struct field, store it away |
1269 | fields = append(fields, field{fieldType, structVal.Field(i)}) | |
1277 | fields = append(fields, field{fieldType, fieldVal}) | |
1270 | 1278 | } |
1271 | 1279 | } |
1272 | 1280 | } |
57 | 57 | |
58 | 58 | type EmbeddedSquash struct { |
59 | 59 | Basic `mapstructure:",squash"` |
60 | Vunique string | |
61 | } | |
62 | ||
63 | type EmbeddedPointerSquash struct { | |
64 | *Basic `mapstructure:",squash"` | |
60 | 65 | Vunique string |
61 | 66 | } |
62 | 67 | |
654 | 659 | } |
655 | 660 | } |
656 | 661 | |
662 | func TestDecode_EmbeddedPointerSquash_FromStructToMap(t *testing.T) { | |
663 | t.Parallel() | |
664 | ||
665 | input := EmbeddedPointerSquash{ | |
666 | Basic: &Basic{ | |
667 | Vstring: "foo", | |
668 | }, | |
669 | Vunique: "bar", | |
670 | } | |
671 | ||
672 | var result map[string]interface{} | |
673 | err := Decode(input, &result) | |
674 | if err != nil { | |
675 | t.Fatalf("got an err: %s", err.Error()) | |
676 | } | |
677 | ||
678 | if result["Vstring"] != "foo" { | |
679 | t.Errorf("vstring value should be 'foo': %#v", result["Vstring"]) | |
680 | } | |
681 | ||
682 | if result["Vunique"] != "bar" { | |
683 | t.Errorf("vunique value should be 'bar': %#v", result["Vunique"]) | |
684 | } | |
685 | } | |
686 | ||
687 | func TestDecode_EmbeddedPointerSquash_FromMapToStruct(t *testing.T) { | |
688 | t.Parallel() | |
689 | ||
690 | input := map[string]interface{}{ | |
691 | "Vstring": "foo", | |
692 | "Vunique": "bar", | |
693 | } | |
694 | ||
695 | result := EmbeddedPointerSquash{ | |
696 | Basic: &Basic{}, | |
697 | } | |
698 | err := Decode(input, &result) | |
699 | if err != nil { | |
700 | t.Fatalf("got an err: %s", err.Error()) | |
701 | } | |
702 | ||
703 | if result.Vstring != "foo" { | |
704 | t.Errorf("vstring value should be 'foo': %#v", result.Vstring) | |
705 | } | |
706 | ||
707 | if result.Vunique != "bar" { | |
708 | t.Errorf("vunique value should be 'bar': %#v", result.Vunique) | |
709 | } | |
710 | } | |
711 | ||
657 | 712 | func TestDecode_EmbeddedSquashConfig(t *testing.T) { |
658 | 713 | t.Parallel() |
659 | 714 | |
2333 | 2388 | "visible-map": emptyMap, |
2334 | 2389 | "omittable-map": map[string]interface{}{"k": "v"}, |
2335 | 2390 | "visible-nested": emptyNested, |
2336 | "omittable-nested": &Nested{}, | |
2391 | "omittable-nested": map[string]interface{}{ | |
2392 | "Vbar": map[string]interface{}{ | |
2393 | "Vbool": false, | |
2394 | "Vdata": interface{}(nil), | |
2395 | "Vextra": "", | |
2396 | "Vfloat": float64(0), | |
2397 | "Vint": 0, | |
2398 | "Vint16": int16(0), | |
2399 | "Vint32": int32(0), | |
2400 | "Vint64": int64(0), | |
2401 | "Vint8": int8(0), | |
2402 | "VjsonFloat": float64(0), | |
2403 | "VjsonInt": 0, | |
2404 | "VjsonNumber": json.Number(""), | |
2405 | "VjsonUint": uint(0), | |
2406 | "Vstring": "", | |
2407 | "Vuint": uint(0), | |
2408 | }, | |
2409 | "Vfoo": "", | |
2410 | }, | |
2337 | 2411 | } |
2338 | 2412 | |
2339 | 2413 | actual := &map[string]interface{}{} |