github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/mapstruct/bugs_test.go (about) 1 package mapstruct 2 3 import ( 4 "reflect" 5 "testing" 6 "time" 7 ) 8 9 // GH-1, GH-10, GH-96 10 func TestDecode_NilValue(t *testing.T) { 11 t.Parallel() 12 13 tests := []struct { 14 name string 15 in interface{} 16 target interface{} 17 out interface{} 18 metaKeys []string 19 metaUnused []string 20 }{ 21 { 22 "all nil", 23 &map[string]interface{}{ 24 "vfoo": nil, 25 "vother": nil, 26 }, 27 &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, 28 &Map{Vfoo: "", Vother: nil}, 29 []string{"Vfoo", "Vother"}, 30 []string{}, 31 }, 32 { 33 "partial nil", 34 &map[string]interface{}{ 35 "vfoo": "baz", 36 "vother": nil, 37 }, 38 &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, 39 &Map{Vfoo: "baz", Vother: nil}, 40 []string{"Vfoo", "Vother"}, 41 []string{}, 42 }, 43 { 44 "partial decode", 45 &map[string]interface{}{ 46 "vother": nil, 47 }, 48 &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, 49 &Map{Vfoo: "foo", Vother: nil}, 50 []string{"Vother"}, 51 []string{}, 52 }, 53 { 54 "unused values", 55 &map[string]interface{}{ 56 "vbar": "bar", 57 "vfoo": nil, 58 "vother": nil, 59 }, 60 &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, 61 &Map{Vfoo: "", Vother: nil}, 62 []string{"Vfoo", "Vother"}, 63 []string{"vbar"}, 64 }, 65 { 66 "map interface all nil", 67 &map[interface{}]interface{}{ 68 "vfoo": nil, 69 "vother": nil, 70 }, 71 &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, 72 &Map{Vfoo: "", Vother: nil}, 73 []string{"Vfoo", "Vother"}, 74 []string{}, 75 }, 76 { 77 "map interface partial nil", 78 &map[interface{}]interface{}{ 79 "vfoo": "baz", 80 "vother": nil, 81 }, 82 &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, 83 &Map{Vfoo: "baz", Vother: nil}, 84 []string{"Vfoo", "Vother"}, 85 []string{}, 86 }, 87 { 88 "map interface partial decode", 89 &map[interface{}]interface{}{ 90 "vother": nil, 91 }, 92 &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, 93 &Map{Vfoo: "foo", Vother: nil}, 94 []string{"Vother"}, 95 []string{}, 96 }, 97 { 98 "map interface unused values", 99 &map[interface{}]interface{}{ 100 "vbar": "bar", 101 "vfoo": nil, 102 "vother": nil, 103 }, 104 &Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}}, 105 &Map{Vfoo: "", Vother: nil}, 106 []string{"Vfoo", "Vother"}, 107 []string{"vbar"}, 108 }, 109 } 110 111 for _, tc := range tests { 112 t.Run(tc.name, func(t *testing.T) { 113 config := &Config{ 114 Metadata: new(Metadata), 115 Result: tc.target, 116 ZeroFields: true, 117 } 118 119 decoder, err := NewDecoder(config) 120 if err != nil { 121 t.Fatalf("should not error: %s", err) 122 } 123 124 err = decoder.Decode(tc.in) 125 if err != nil { 126 t.Fatalf("should not error: %s", err) 127 } 128 129 if !reflect.DeepEqual(tc.out, tc.target) { 130 t.Fatalf("%q: TestDecode_NilValue() expected: %#v, got: %#v", tc.name, tc.out, tc.target) 131 } 132 133 if !reflect.DeepEqual(tc.metaKeys, config.Metadata.Keys) { 134 t.Fatalf("%q: Metadata.Keys mismatch expected: %#v, got: %#v", tc.name, tc.metaKeys, config.Metadata.Keys) 135 } 136 137 if !reflect.DeepEqual(tc.metaUnused, config.Metadata.Unused) { 138 t.Fatalf("%q: Metadata.Unused mismatch expected: %#v, got: %#v", tc.name, tc.metaUnused, config.Metadata.Unused) 139 } 140 }) 141 } 142 } 143 144 // #48 145 func TestNestedTypePointerWithDefaults(t *testing.T) { 146 t.Parallel() 147 148 input := map[string]interface{}{ 149 "vfoo": "foo", 150 "vbar": map[string]interface{}{ 151 "vstring": "foo", 152 "vint": 42, 153 "vbool": true, 154 }, 155 } 156 157 result := NestedPointer{ 158 Vbar: &Basic{ 159 Vuint: 42, 160 }, 161 } 162 err := Decode(input, &result) 163 if err != nil { 164 t.Fatalf("got an err: %s", err.Error()) 165 } 166 167 if result.Vfoo != "foo" { 168 t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) 169 } 170 171 if result.Vbar.Vstring != "foo" { 172 t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring) 173 } 174 175 if result.Vbar.Vint != 42 { 176 t.Errorf("vint value should be 42: %#v", result.Vbar.Vint) 177 } 178 179 if result.Vbar.Vbool != true { 180 t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool) 181 } 182 183 if result.Vbar.Vextra != "" { 184 t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra) 185 } 186 187 // this is the error 188 if result.Vbar.Vuint != 42 { 189 t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint) 190 } 191 } 192 193 type NestedSlice struct { 194 Vfoo string 195 Vbars []Basic 196 Vempty []Basic 197 } 198 199 // #48 200 func TestNestedTypeSliceWithDefaults(t *testing.T) { 201 t.Parallel() 202 203 input := map[string]interface{}{ 204 "vfoo": "foo", 205 "vbars": []map[string]interface{}{ 206 {"vstring": "foo", "vint": 42, "vbool": true}, 207 {"vint": 42, "vbool": true}, 208 }, 209 "vempty": []map[string]interface{}{ 210 {"vstring": "foo", "vint": 42, "vbool": true}, 211 {"vint": 42, "vbool": true}, 212 }, 213 } 214 215 result := NestedSlice{ 216 Vbars: []Basic{ 217 {Vuint: 42}, 218 {Vstring: "foo"}, 219 }, 220 } 221 err := Decode(input, &result) 222 if err != nil { 223 t.Fatalf("got an err: %s", err.Error()) 224 } 225 226 if result.Vfoo != "foo" { 227 t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) 228 } 229 230 if result.Vbars[0].Vstring != "foo" { 231 t.Errorf("vstring value should be 'foo': %#v", result.Vbars[0].Vstring) 232 } 233 // this is the error 234 if result.Vbars[0].Vuint != 42 { 235 t.Errorf("vuint value should be 42: %#v", result.Vbars[0].Vuint) 236 } 237 } 238 239 // #48 workaround 240 func TestNestedTypeWithDefaults(t *testing.T) { 241 t.Parallel() 242 243 input := map[string]interface{}{ 244 "vfoo": "foo", 245 "vbar": map[string]interface{}{ 246 "vstring": "foo", 247 "vint": 42, 248 "vbool": true, 249 }, 250 } 251 252 result := Nested{ 253 Vbar: Basic{ 254 Vuint: 42, 255 }, 256 } 257 err := Decode(input, &result) 258 if err != nil { 259 t.Fatalf("got an err: %s", err.Error()) 260 } 261 262 if result.Vfoo != "foo" { 263 t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) 264 } 265 266 if result.Vbar.Vstring != "foo" { 267 t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring) 268 } 269 270 if result.Vbar.Vint != 42 { 271 t.Errorf("vint value should be 42: %#v", result.Vbar.Vint) 272 } 273 274 if result.Vbar.Vbool != true { 275 t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool) 276 } 277 278 if result.Vbar.Vextra != "" { 279 t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra) 280 } 281 282 // this is the error 283 if result.Vbar.Vuint != 42 { 284 t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint) 285 } 286 } 287 288 // #67 panic() on extending slices (decodeSlice with disabled ZeroValues) 289 func TestDecodeSliceToEmptySliceWOZeroing(t *testing.T) { 290 t.Parallel() 291 292 type TestStruct struct { 293 Vfoo []string 294 } 295 296 decode := func(m interface{}, rawVal interface{}) error { 297 config := &Config{ 298 Metadata: nil, 299 Result: rawVal, 300 ZeroFields: false, 301 } 302 303 decoder, err := NewDecoder(config) 304 if err != nil { 305 return err 306 } 307 308 return decoder.Decode(m) 309 } 310 311 { 312 input := map[string]interface{}{ 313 "vfoo": []string{"1"}, 314 } 315 316 result := &TestStruct{} 317 318 err := decode(input, &result) 319 if err != nil { 320 t.Fatalf("got an err: %s", err.Error()) 321 } 322 } 323 324 { 325 input := map[string]interface{}{ 326 "vfoo": []string{"1"}, 327 } 328 329 result := &TestStruct{ 330 Vfoo: []string{}, 331 } 332 333 err := decode(input, &result) 334 if err != nil { 335 t.Fatalf("got an err: %s", err.Error()) 336 } 337 } 338 339 { 340 input := map[string]interface{}{ 341 "vfoo": []string{"2", "3"}, 342 } 343 344 result := &TestStruct{ 345 Vfoo: []string{"1"}, 346 } 347 348 err := decode(input, &result) 349 if err != nil { 350 t.Fatalf("got an err: %s", err.Error()) 351 } 352 } 353 } 354 355 // #70 356 func TestNextSquashmapstruct(t *testing.T) { 357 data := &struct { 358 Level1 struct { 359 Level2 struct { 360 Foo string 361 } `mapstruct:",squash"` 362 } `mapstruct:",squash"` 363 }{} 364 err := Decode(map[interface{}]interface{}{"foo": "baz"}, &data) 365 if err != nil { 366 t.Fatalf("should not error: %s", err) 367 } 368 if data.Level1.Level2.Foo != "baz" { 369 t.Fatal("value should be baz") 370 } 371 } 372 373 type ImplementsInterfacePointerReceiver struct { 374 Name string 375 } 376 377 func (i *ImplementsInterfacePointerReceiver) DoStuff() {} 378 379 type ImplementsInterfaceValueReceiver string 380 381 func (i ImplementsInterfaceValueReceiver) DoStuff() {} 382 383 // GH-140 Type error when using Hook to decode into interface 384 func TestDecode_DecodeHookInterface(t *testing.T) { 385 t.Parallel() 386 387 type Interface interface { 388 DoStuff() 389 } 390 type DecodeIntoInterface struct { 391 Test Interface 392 } 393 394 testData := map[string]string{"test": "test"} 395 396 stringToPointerInterfaceDecodeHook := func(from, to reflect.Type, data interface{}) (interface{}, error) { 397 if from.Kind() != reflect.String { 398 return data, nil 399 } 400 401 if to != reflect.TypeOf((*Interface)(nil)).Elem() { 402 return data, nil 403 } 404 // Ensure interface is satisfied 405 var impl Interface = &ImplementsInterfacePointerReceiver{data.(string)} 406 return impl, nil 407 } 408 409 stringToValueInterfaceDecodeHook := func(from, to reflect.Type, data interface{}) (interface{}, error) { 410 if from.Kind() != reflect.String { 411 return data, nil 412 } 413 414 if to != reflect.TypeOf((*Interface)(nil)).Elem() { 415 return data, nil 416 } 417 // Ensure interface is satisfied 418 var impl Interface = ImplementsInterfaceValueReceiver(data.(string)) 419 return impl, nil 420 } 421 422 { 423 decodeInto := new(DecodeIntoInterface) 424 425 decoder, _ := NewDecoder(&Config{ 426 Hook: stringToPointerInterfaceDecodeHook, 427 Result: decodeInto, 428 }) 429 430 err := decoder.Decode(testData) 431 if err != nil { 432 t.Fatalf("Decode returned error: %s", err) 433 } 434 435 expected := &ImplementsInterfacePointerReceiver{"test"} 436 if !reflect.DeepEqual(decodeInto.Test, expected) { 437 t.Fatalf("expected: %#v (%T), got: %#v (%T)", decodeInto.Test, decodeInto.Test, expected, expected) 438 } 439 } 440 441 { 442 decodeInto := new(DecodeIntoInterface) 443 444 decoder, _ := NewDecoder(&Config{ 445 Hook: stringToValueInterfaceDecodeHook, 446 Result: decodeInto, 447 }) 448 449 err := decoder.Decode(testData) 450 if err != nil { 451 t.Fatalf("Decode returned error: %s", err) 452 } 453 454 expected := ImplementsInterfaceValueReceiver("test") 455 if !reflect.DeepEqual(decodeInto.Test, expected) { 456 t.Fatalf("expected: %#v (%T), got: %#v (%T)", decodeInto.Test, decodeInto.Test, expected, expected) 457 } 458 } 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 } 477 478 // #202 Ensure that intermediate maps in the struct -> struct decode process are settable 479 // and not just the elements within them. 480 func TestDecodeIntermeidateMapsSettable(t *testing.T) { 481 type Timestamp struct { 482 Seconds int64 483 Nanos int32 484 } 485 486 type TsWrapper struct { 487 Timestamp *Timestamp 488 } 489 490 type TimeWrapper struct { 491 Timestamp time.Time 492 } 493 494 input := TimeWrapper{ 495 Timestamp: time.Unix(123456789, 987654), 496 } 497 498 expected := TsWrapper{ 499 Timestamp: &Timestamp{ 500 Seconds: 123456789, 501 Nanos: 987654, 502 }, 503 } 504 505 timePtrType := reflect.TypeOf((*time.Time)(nil)) 506 mapStrInfType := reflect.TypeOf((map[string]interface{})(nil)) 507 508 var actual TsWrapper 509 decoder, err := NewDecoder(&Config{ 510 Result: &actual, 511 Hook: func(from, to reflect.Type, data interface{}) (interface{}, error) { 512 if from == timePtrType && to == mapStrInfType { 513 ts := data.(*time.Time) 514 nanos := ts.UnixNano() 515 516 seconds := nanos / 1000000000 517 nanos = nanos % 1000000000 518 519 return &map[string]interface{}{ 520 "Seconds": seconds, 521 "Nanos": int32(nanos), 522 }, nil 523 } 524 return data, nil 525 }, 526 }) 527 if err != nil { 528 t.Fatalf("failed to create decoder: %v", err) 529 } 530 531 if err := decoder.Decode(&input); err != nil { 532 t.Fatalf("failed to decode input: %v", err) 533 } 534 535 if !reflect.DeepEqual(expected, actual) { 536 t.Fatalf("expected: %#[1]v (%[1]T), got: %#[2]v (%[2]T)", expected, actual) 537 } 538 } 539 540 // GH-206: decodeInt throws an error for an empty string 541 func TestDecode_weakEmptyStringToInt(t *testing.T) { 542 input := map[string]interface{}{ 543 "StringToInt": "", 544 "StringToUint": "", 545 "StringToBool": "", 546 "StringToFloat": "", 547 } 548 549 expectedResultWeak := TypeConversionResult{ 550 StringToInt: 0, 551 StringToUint: 0, 552 StringToBool: false, 553 StringToFloat: 0, 554 } 555 556 // Test weak type conversion 557 var resultWeak TypeConversionResult 558 err := Decode(input, &resultWeak, WithWeakType(true)) 559 if err != nil { 560 t.Fatalf("got an err: %s", err) 561 } 562 563 if !reflect.DeepEqual(resultWeak, expectedResultWeak) { 564 t.Errorf("expected \n%#v, got: \n%#v", expectedResultWeak, resultWeak) 565 } 566 } 567 568 // GH-228: Squash cause *time.Time set to zero 569 func TestMapSquash(t *testing.T) { 570 type AA struct { 571 T *time.Time 572 } 573 type A struct { 574 AA 575 } 576 577 v := time.Now() 578 in := &AA{ 579 T: &v, 580 } 581 out := &A{} 582 d, err := NewDecoder(&Config{ 583 Squash: true, 584 Result: out, 585 }) 586 if err != nil { 587 t.Fatalf("err: %s", err) 588 } 589 if err := d.Decode(in); err != nil { 590 t.Fatalf("err: %s", err) 591 } 592 593 // these failed 594 if !v.Equal(*out.T) { 595 t.Fatal("expected equal") 596 } 597 if out.T.IsZero() { 598 t.Fatal("expected false") 599 } 600 }