package mapstructure import ( "reflect" "testing" "time" ) // GH-1, GH-10, GH-96 func TestDecode_NilValue(t *testing.T) { t.Parallel() tests := []struct { name string in interface{} target interface{} out interface{} metaKeys []string metaUnused []string }{ { "all nil", &map[string]interface{}{ "vfoo": nil, "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "", Vother: nil}, []string{"Vfoo", "Vother"}, []string{}, }, { "partial nil", &map[string]interface{}{ "vfoo": "baz", "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "baz", Vother: nil}, []string{"Vfoo", "Vother"}, []string{}, }, { "partial decode", &map[string]interface{}{ "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "foo", Vother: nil}, []string{"Vother"}, []string{}, }, { "unused values", &map[string]interface{}{ "vbar": "bar", "vfoo": nil, "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "", Vother: nil}, []string{"Vfoo", "Vother"}, []string{"vbar"}, }, { "map interface all nil", &map[interface{}]interface{}{ "vfoo": nil, "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "", Vother: nil}, []string{"Vfoo", "Vother"}, []string{}, }, { "map interface partial nil", &map[interface{}]interface{}{ "vfoo": "baz", "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "baz", Vother: nil}, []string{"Vfoo", "Vother"}, []string{}, }, { "map interface partial decode", &map[interface{}]interface{}{ "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "foo", Vother: nil}, []string{"Vother"}, []string{}, }, { "map interface unused values", &map[interface{}]interface{}{ "vbar": "bar", "vfoo": nil, "vother": nil, }, &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, &Map{Vfoo: "", Vother: nil}, []string{"Vfoo", "Vother"}, []string{"vbar"}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { config := &DecoderConfig{ Metadata: new(Metadata), Result: tc.target, ZeroFields: true, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("should not error: %s", err) } err = decoder.Decode(tc.in) if err != nil { t.Fatalf("should not error: %s", err) } if !reflect.DeepEqual(tc.out, tc.target) { t.Fatalf("%q: TestDecode_NilValue() expected: %#v, got: %#v", tc.name, tc.out, tc.target) } if !reflect.DeepEqual(tc.metaKeys, config.Metadata.Keys) { t.Fatalf("%q: Metadata.Keys mismatch expected: %#v, got: %#v", tc.name, tc.metaKeys, config.Metadata.Keys) } if !reflect.DeepEqual(tc.metaUnused, config.Metadata.Unused) { t.Fatalf("%q: Metadata.Unused mismatch expected: %#v, got: %#v", tc.name, tc.metaUnused, config.Metadata.Unused) } }) } } // #48 func TestNestedTypePointerWithDefaults(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbar": map[string]interface{}{ "vstring": "foo", "vint": 42, "vbool": true, }, } result := NestedPointer{ Vbar: &Basic{ Vuint: 42, }, } err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vfoo != "foo" { t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) } if result.Vbar.Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring) } if result.Vbar.Vint != 42 { t.Errorf("vint value should be 42: %#v", result.Vbar.Vint) } if result.Vbar.Vbool != true { t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool) } if result.Vbar.Vextra != "" { t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra) } // this is the error if result.Vbar.Vuint != 42 { t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint) } } type NestedSlice struct { Vfoo string Vbars []Basic Vempty []Basic } // #48 func TestNestedTypeSliceWithDefaults(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbars": []map[string]interface{}{ {"vstring": "foo", "vint": 42, "vbool": true}, {"vint": 42, "vbool": true}, }, "vempty": []map[string]interface{}{ {"vstring": "foo", "vint": 42, "vbool": true}, {"vint": 42, "vbool": true}, }, } result := NestedSlice{ Vbars: []Basic{ {Vuint: 42}, {Vstring: "foo"}, }, } err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vfoo != "foo" { t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) } if result.Vbars[0].Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vbars[0].Vstring) } // this is the error if result.Vbars[0].Vuint != 42 { t.Errorf("vuint value should be 42: %#v", result.Vbars[0].Vuint) } } // #48 workaround func TestNestedTypeWithDefaults(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbar": map[string]interface{}{ "vstring": "foo", "vint": 42, "vbool": true, }, } result := Nested{ Vbar: Basic{ Vuint: 42, }, } err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vfoo != "foo" { t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) } if result.Vbar.Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring) } if result.Vbar.Vint != 42 { t.Errorf("vint value should be 42: %#v", result.Vbar.Vint) } if result.Vbar.Vbool != true { t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool) } if result.Vbar.Vextra != "" { t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra) } // this is the error if result.Vbar.Vuint != 42 { t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint) } } // #67 panic() on extending slices (decodeSlice with disabled ZeroValues) func TestDecodeSliceToEmptySliceWOZeroing(t *testing.T) { t.Parallel() type TestStruct struct { Vfoo []string } decode := func(m interface{}, rawVal interface{}) error { config := &DecoderConfig{ Metadata: nil, Result: rawVal, ZeroFields: false, } decoder, err := NewDecoder(config) if err != nil { return err } return decoder.Decode(m) } { input := map[string]interface{}{ "vfoo": []string{"1"}, } result := &TestStruct{} err := decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } } { input := map[string]interface{}{ "vfoo": []string{"1"}, } result := &TestStruct{ Vfoo: []string{}, } err := decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } } { input := map[string]interface{}{ "vfoo": []string{"2", "3"}, } result := &TestStruct{ Vfoo: []string{"1"}, } err := decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } } } // #70 func TestNextSquashMapstructure(t *testing.T) { data := &struct { Level1 struct { Level2 struct { Foo string } `mapstructure:",squash"` } `mapstructure:",squash"` }{} err := Decode(map[interface{}]interface{}{"foo": "baz"}, &data) if err != nil { t.Fatalf("should not error: %s", err) } if data.Level1.Level2.Foo != "baz" { t.Fatal("value should be baz") } } type ImplementsInterfacePointerReceiver struct { Name string } func (i *ImplementsInterfacePointerReceiver) DoStuff() {} type ImplementsInterfaceValueReceiver string func (i ImplementsInterfaceValueReceiver) DoStuff() {} // GH-140 Type error when using DecodeHook to decode into interface func TestDecode_DecodeHookInterface(t *testing.T) { t.Parallel() type Interface interface { DoStuff() } type DecodeIntoInterface struct { Test Interface } testData := map[string]string{"test": "test"} stringToPointerInterfaceDecodeHook := func(from, to reflect.Type, data interface{}) (interface{}, error) { if from.Kind() != reflect.String { return data, nil } if to != reflect.TypeOf((*Interface)(nil)).Elem() { return data, nil } // Ensure interface is satisfied var impl Interface = &ImplementsInterfacePointerReceiver{data.(string)} return impl, nil } stringToValueInterfaceDecodeHook := func(from, to reflect.Type, data interface{}) (interface{}, error) { if from.Kind() != reflect.String { return data, nil } if to != reflect.TypeOf((*Interface)(nil)).Elem() { return data, nil } // Ensure interface is satisfied var impl Interface = ImplementsInterfaceValueReceiver(data.(string)) return impl, nil } { decodeInto := new(DecodeIntoInterface) decoder, _ := NewDecoder(&DecoderConfig{ DecodeHook: stringToPointerInterfaceDecodeHook, Result: decodeInto, }) err := decoder.Decode(testData) if err != nil { t.Fatalf("Decode returned error: %s", err) } expected := &ImplementsInterfacePointerReceiver{"test"} if !reflect.DeepEqual(decodeInto.Test, expected) { t.Fatalf("expected: %#v (%T), got: %#v (%T)", decodeInto.Test, decodeInto.Test, expected, expected) } } { decodeInto := new(DecodeIntoInterface) decoder, _ := NewDecoder(&DecoderConfig{ DecodeHook: stringToValueInterfaceDecodeHook, Result: decodeInto, }) err := decoder.Decode(testData) if err != nil { t.Fatalf("Decode returned error: %s", err) } expected := ImplementsInterfaceValueReceiver("test") if !reflect.DeepEqual(decodeInto.Test, expected) { t.Fatalf("expected: %#v (%T), got: %#v (%T)", decodeInto.Test, decodeInto.Test, expected, expected) } } } // #103 Check for data type before trying to access its composants prevent a panic error // in decodeSlice func TestDecodeBadDataTypeInSlice(t *testing.T) { t.Parallel() input := map[string]interface{}{ "Toto": "titi", } result := []struct { Toto string }{} if err := Decode(input, &result); err == nil { t.Error("An error was expected, got nil") } } // #202 Ensure that intermediate maps in the struct -> struct decode process are settable // and not just the elements within them. func TestDecodeIntermediateMapsSettable(t *testing.T) { type Timestamp struct { Seconds int64 Nanos int32 } type TsWrapper struct { Timestamp *Timestamp } type TimeWrapper struct { Timestamp time.Time } input := TimeWrapper{ Timestamp: time.Unix(123456789, 987654), } expected := TsWrapper{ Timestamp: &Timestamp{ Seconds: 123456789, Nanos: 987654, }, } timePtrType := reflect.TypeOf((*time.Time)(nil)) mapStrInfType := reflect.TypeOf((map[string]interface{})(nil)) var actual TsWrapper decoder, err := NewDecoder(&DecoderConfig{ Result: &actual, DecodeHook: func(from, to reflect.Type, data interface{}) (interface{}, error) { if from == timePtrType && to == mapStrInfType { ts := data.(*time.Time) nanos := ts.UnixNano() seconds := nanos / 1000000000 nanos = nanos % 1000000000 return &map[string]interface{}{ "Seconds": seconds, "Nanos": int32(nanos), }, nil } return data, nil }, }) if err != nil { t.Fatalf("failed to create decoder: %v", err) } if err := decoder.Decode(&input); err != nil { t.Fatalf("failed to decode input: %v", err) } if !reflect.DeepEqual(expected, actual) { t.Fatalf("expected: %#[1]v (%[1]T), got: %#[2]v (%[2]T)", expected, actual) } } // GH-206: decodeInt throws an error for an empty string func TestDecode_weakEmptyStringToInt(t *testing.T) { input := map[string]interface{}{ "StringToInt": "", "StringToUint": "", "StringToBool": "", "StringToFloat": "", } expectedResultWeak := TypeConversionResult{ StringToInt: 0, StringToUint: 0, StringToBool: false, StringToFloat: 0, } // Test weak type conversion var resultWeak TypeConversionResult err := WeakDecode(input, &resultWeak) if err != nil { t.Fatalf("got an err: %s", err) } if !reflect.DeepEqual(resultWeak, expectedResultWeak) { t.Errorf("expected \n%#v, got: \n%#v", expectedResultWeak, resultWeak) } } // GH-228: Squash cause *time.Time set to zero func TestMapSquash(t *testing.T) { type AA struct { T *time.Time } type A struct { AA } v := time.Now() in := &AA{ T: &v, } out := &A{} d, err := NewDecoder(&DecoderConfig{ Squash: true, Result: out, }) if err != nil { t.Fatalf("err: %s", err) } if err := d.Decode(in); err != nil { t.Fatalf("err: %s", err) } // these failed if !v.Equal(*out.T) { t.Fatal("expected equal") } if out.T.IsZero() { t.Fatal("expected false") } } // GH-238: Empty key name when decoding map from struct with only omitempty flag func TestMapOmitEmptyWithEmptyFieldnameInTag(t *testing.T) { type Struct struct { Username string `mapstructure:",omitempty"` Age int `mapstructure:",omitempty"` } s := Struct{ Username: "Joe", } var m map[string]interface{} if err := Decode(s, &m); err != nil { t.Fatal(err) } if len(m) != 1 { t.Fatalf("fail: %#v", m) } if m["Username"] != "Joe" { t.Fatalf("fail: %#v", m) } } // GH-340: Decoding array of slices causes panic type HasNonComparableType struct { NonComparableType [2][]byte } func TestDecode_nonComparableType(t *testing.T) { decodeTo := &HasNonComparableType{} expected := [2][]byte{{1, 2}, {3, 4, 5}} if err := Decode(map[string]interface{}{"NonComparableType": expected}, &decodeTo); err != nil { t.Fatal(err) } if !reflect.DeepEqual(expected, decodeTo.NonComparableType) { t.Fatalf("fail: %#v", decodeTo.NonComparableType) } } // GH-347: Decoding maps with multiple indirection results in an error func TestDecodeToMapWithMultipleIndirection(t *testing.T) { t.Run("Struct", func(t *testing.T) { type Struct struct { Foo string `mapstructure:"foo"` } v := Struct{ Foo: "bar", } i := &v ii := &i iii := &ii var actual map[string]interface{} if err := Decode(iii, &actual); err != nil { t.Fatal(err) } expected := map[string]interface{}{ "foo": "bar", } if !reflect.DeepEqual(actual, expected) { t.Fatalf("expected: %#v, got: %#v", expected, actual) } }) t.Run("Map", func(t *testing.T) { v := map[string]interface{}{ "foo": "bar", } i := &v ii := &i iii := &ii var actual map[string]interface{} if err := Decode(iii, &actual); err != nil { t.Fatal(err) } expected := map[string]interface{}{ "foo": "bar", } if !reflect.DeepEqual(actual, expected) { t.Fatalf("expected: %#v, got: %#v", expected, actual) } }) t.Run("Array", func(t *testing.T) { v := [1]map[string]interface{}{ { "foo": "bar", }, } i := &v ii := &i iii := &ii var actual map[string]interface{} if err := WeakDecode(iii, &actual); err != nil { t.Fatal(err) } expected := map[string]interface{}{ "foo": "bar", } if !reflect.DeepEqual(actual, expected) { t.Fatalf("expected: %#v, got: %#v", expected, actual) } }) t.Run("Slice", func(t *testing.T) { v := []map[string]interface{}{ { "foo": "bar", }, } i := &v ii := &i iii := &ii var actual map[string]interface{} if err := WeakDecode(iii, &actual); err != nil { t.Fatal(err) } expected := map[string]interface{}{ "foo": "bar", } if !reflect.DeepEqual(actual, expected) { t.Fatalf("expected: %#v, got: %#v", expected, actual) } }) }