1package toml 2 3import ( 4 "fmt" 5 "log" 6 "reflect" 7 "testing" 8 "time" 9) 10 11func init() { 12 log.SetFlags(0) 13} 14 15func TestDecodeSimple(t *testing.T) { 16 var testSimple = ` 17age = 250 18andrew = "gallant" 19kait = "brady" 20now = 1987-07-05T05:45:00Z 21yesOrNo = true 22pi = 3.14 23colors = [ 24 ["red", "green", "blue"], 25 ["cyan", "magenta", "yellow", "black"], 26] 27 28[My.Cats] 29plato = "cat 1" 30cauchy = "cat 2" 31` 32 33 type cats struct { 34 Plato string 35 Cauchy string 36 } 37 type simple struct { 38 Age int 39 Colors [][]string 40 Pi float64 41 YesOrNo bool 42 Now time.Time 43 Andrew string 44 Kait string 45 My map[string]cats 46 } 47 48 var val simple 49 _, err := Decode(testSimple, &val) 50 if err != nil { 51 t.Fatal(err) 52 } 53 54 now, err := time.Parse("2006-01-02T15:04:05", "1987-07-05T05:45:00") 55 if err != nil { 56 panic(err) 57 } 58 var answer = simple{ 59 Age: 250, 60 Andrew: "gallant", 61 Kait: "brady", 62 Now: now, 63 YesOrNo: true, 64 Pi: 3.14, 65 Colors: [][]string{ 66 {"red", "green", "blue"}, 67 {"cyan", "magenta", "yellow", "black"}, 68 }, 69 My: map[string]cats{ 70 "Cats": {Plato: "cat 1", Cauchy: "cat 2"}, 71 }, 72 } 73 if !reflect.DeepEqual(val, answer) { 74 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 75 answer, val) 76 } 77} 78 79func TestDecodeEmbedded(t *testing.T) { 80 type Dog struct{ Name string } 81 type Age int 82 83 tests := map[string]struct { 84 input string 85 decodeInto interface{} 86 wantDecoded interface{} 87 }{ 88 "embedded struct": { 89 input: `Name = "milton"`, 90 decodeInto: &struct{ Dog }{}, 91 wantDecoded: &struct{ Dog }{Dog{"milton"}}, 92 }, 93 "embedded non-nil pointer to struct": { 94 input: `Name = "milton"`, 95 decodeInto: &struct{ *Dog }{}, 96 wantDecoded: &struct{ *Dog }{&Dog{"milton"}}, 97 }, 98 "embedded nil pointer to struct": { 99 input: ``, 100 decodeInto: &struct{ *Dog }{}, 101 wantDecoded: &struct{ *Dog }{nil}, 102 }, 103 "embedded int": { 104 input: `Age = -5`, 105 decodeInto: &struct{ Age }{}, 106 wantDecoded: &struct{ Age }{-5}, 107 }, 108 } 109 110 for label, test := range tests { 111 _, err := Decode(test.input, test.decodeInto) 112 if err != nil { 113 t.Fatal(err) 114 } 115 if !reflect.DeepEqual(test.wantDecoded, test.decodeInto) { 116 t.Errorf("%s: want decoded == %+v, got %+v", 117 label, test.wantDecoded, test.decodeInto) 118 } 119 } 120} 121 122func TestDecodeIgnoredFields(t *testing.T) { 123 type simple struct { 124 Number int `toml:"-"` 125 } 126 const input = ` 127Number = 123 128- = 234 129` 130 var s simple 131 if _, err := Decode(input, &s); err != nil { 132 t.Fatal(err) 133 } 134 if s.Number != 0 { 135 t.Errorf("got: %d; want 0", s.Number) 136 } 137} 138 139func TestTableArrays(t *testing.T) { 140 var tomlTableArrays = ` 141[[albums]] 142name = "Born to Run" 143 144 [[albums.songs]] 145 name = "Jungleland" 146 147 [[albums.songs]] 148 name = "Meeting Across the River" 149 150[[albums]] 151name = "Born in the USA" 152 153 [[albums.songs]] 154 name = "Glory Days" 155 156 [[albums.songs]] 157 name = "Dancing in the Dark" 158` 159 160 type Song struct { 161 Name string 162 } 163 164 type Album struct { 165 Name string 166 Songs []Song 167 } 168 169 type Music struct { 170 Albums []Album 171 } 172 173 expected := Music{[]Album{ 174 {"Born to Run", []Song{{"Jungleland"}, {"Meeting Across the River"}}}, 175 {"Born in the USA", []Song{{"Glory Days"}, {"Dancing in the Dark"}}}, 176 }} 177 var got Music 178 if _, err := Decode(tomlTableArrays, &got); err != nil { 179 t.Fatal(err) 180 } 181 if !reflect.DeepEqual(expected, got) { 182 t.Fatalf("\n%#v\n!=\n%#v\n", expected, got) 183 } 184} 185 186// Case insensitive matching tests. 187// A bit more comprehensive than needed given the current implementation, 188// but implementations change. 189// Probably still missing demonstrations of some ugly corner cases regarding 190// case insensitive matching and multiple fields. 191func TestCase(t *testing.T) { 192 var caseToml = ` 193tOpString = "string" 194tOpInt = 1 195tOpFloat = 1.1 196tOpBool = true 197tOpdate = 2006-01-02T15:04:05Z 198tOparray = [ "array" ] 199Match = "i should be in Match only" 200MatcH = "i should be in MatcH only" 201once = "just once" 202[nEst.eD] 203nEstedString = "another string" 204` 205 206 type InsensitiveEd struct { 207 NestedString string 208 } 209 210 type InsensitiveNest struct { 211 Ed InsensitiveEd 212 } 213 214 type Insensitive struct { 215 TopString string 216 TopInt int 217 TopFloat float64 218 TopBool bool 219 TopDate time.Time 220 TopArray []string 221 Match string 222 MatcH string 223 Once string 224 OncE string 225 Nest InsensitiveNest 226 } 227 228 tme, err := time.Parse(time.RFC3339, time.RFC3339[:len(time.RFC3339)-5]) 229 if err != nil { 230 panic(err) 231 } 232 expected := Insensitive{ 233 TopString: "string", 234 TopInt: 1, 235 TopFloat: 1.1, 236 TopBool: true, 237 TopDate: tme, 238 TopArray: []string{"array"}, 239 MatcH: "i should be in MatcH only", 240 Match: "i should be in Match only", 241 Once: "just once", 242 OncE: "", 243 Nest: InsensitiveNest{ 244 Ed: InsensitiveEd{NestedString: "another string"}, 245 }, 246 } 247 var got Insensitive 248 if _, err := Decode(caseToml, &got); err != nil { 249 t.Fatal(err) 250 } 251 if !reflect.DeepEqual(expected, got) { 252 t.Fatalf("\n%#v\n!=\n%#v\n", expected, got) 253 } 254} 255 256func TestPointers(t *testing.T) { 257 type Object struct { 258 Type string 259 Description string 260 } 261 262 type Dict struct { 263 NamedObject map[string]*Object 264 BaseObject *Object 265 Strptr *string 266 Strptrs []*string 267 } 268 s1, s2, s3 := "blah", "abc", "def" 269 expected := &Dict{ 270 Strptr: &s1, 271 Strptrs: []*string{&s2, &s3}, 272 NamedObject: map[string]*Object{ 273 "foo": {"FOO", "fooooo!!!"}, 274 "bar": {"BAR", "ba-ba-ba-ba-barrrr!!!"}, 275 }, 276 BaseObject: &Object{"BASE", "da base"}, 277 } 278 279 ex1 := ` 280Strptr = "blah" 281Strptrs = ["abc", "def"] 282 283[NamedObject.foo] 284Type = "FOO" 285Description = "fooooo!!!" 286 287[NamedObject.bar] 288Type = "BAR" 289Description = "ba-ba-ba-ba-barrrr!!!" 290 291[BaseObject] 292Type = "BASE" 293Description = "da base" 294` 295 dict := new(Dict) 296 _, err := Decode(ex1, dict) 297 if err != nil { 298 t.Errorf("Decode error: %v", err) 299 } 300 if !reflect.DeepEqual(expected, dict) { 301 t.Fatalf("\n%#v\n!=\n%#v\n", expected, dict) 302 } 303} 304 305func TestDecodeBadTimestamp(t *testing.T) { 306 var x struct { 307 T time.Time 308 } 309 for _, s := range []string{ 310 "T = 123", "T = 2006-01-50T00:00:00Z", "T = 2006-01-30T00:00:00", 311 } { 312 if _, err := Decode(s, &x); err == nil { 313 t.Errorf("Expected invalid DateTime error for %q", s) 314 } 315 } 316} 317 318func TestDecodeMultilineStrings(t *testing.T) { 319 var x struct { 320 S string 321 } 322 const s0 = `s = """ 323a b \n c 324d e f 325"""` 326 if _, err := Decode(s0, &x); err != nil { 327 t.Fatal(err) 328 } 329 if want := "a b \n c\nd e f\n"; x.S != want { 330 t.Errorf("got: %q; want: %q", x.S, want) 331 } 332 const s1 = `s = """a b c\ 333"""` 334 if _, err := Decode(s1, &x); err != nil { 335 t.Fatal(err) 336 } 337 if want := "a b c"; x.S != want { 338 t.Errorf("got: %q; want: %q", x.S, want) 339 } 340} 341 342type sphere struct { 343 Center [3]float64 344 Radius float64 345} 346 347func TestDecodeSimpleArray(t *testing.T) { 348 var s1 sphere 349 if _, err := Decode(`center = [0.0, 1.5, 0.0]`, &s1); err != nil { 350 t.Fatal(err) 351 } 352} 353 354func TestDecodeArrayWrongSize(t *testing.T) { 355 var s1 sphere 356 if _, err := Decode(`center = [0.1, 2.3]`, &s1); err == nil { 357 t.Fatal("Expected array type mismatch error") 358 } 359} 360 361func TestDecodeLargeIntoSmallInt(t *testing.T) { 362 type table struct { 363 Value int8 364 } 365 var tab table 366 if _, err := Decode(`value = 500`, &tab); err == nil { 367 t.Fatal("Expected integer out-of-bounds error.") 368 } 369} 370 371func TestDecodeSizedInts(t *testing.T) { 372 type table struct { 373 U8 uint8 374 U16 uint16 375 U32 uint32 376 U64 uint64 377 U uint 378 I8 int8 379 I16 int16 380 I32 int32 381 I64 int64 382 I int 383 } 384 answer := table{1, 1, 1, 1, 1, -1, -1, -1, -1, -1} 385 toml := ` 386 u8 = 1 387 u16 = 1 388 u32 = 1 389 u64 = 1 390 u = 1 391 i8 = -1 392 i16 = -1 393 i32 = -1 394 i64 = -1 395 i = -1 396 ` 397 var tab table 398 if _, err := Decode(toml, &tab); err != nil { 399 t.Fatal(err.Error()) 400 } 401 if answer != tab { 402 t.Fatalf("Expected %#v but got %#v", answer, tab) 403 } 404} 405 406func TestUnmarshaler(t *testing.T) { 407 408 var tomlBlob = ` 409[dishes.hamboogie] 410name = "Hamboogie with fries" 411price = 10.99 412 413[[dishes.hamboogie.ingredients]] 414name = "Bread Bun" 415 416[[dishes.hamboogie.ingredients]] 417name = "Lettuce" 418 419[[dishes.hamboogie.ingredients]] 420name = "Real Beef Patty" 421 422[[dishes.hamboogie.ingredients]] 423name = "Tomato" 424 425[dishes.eggsalad] 426name = "Egg Salad with rice" 427price = 3.99 428 429[[dishes.eggsalad.ingredients]] 430name = "Egg" 431 432[[dishes.eggsalad.ingredients]] 433name = "Mayo" 434 435[[dishes.eggsalad.ingredients]] 436name = "Rice" 437` 438 m := &menu{} 439 if _, err := Decode(tomlBlob, m); err != nil { 440 log.Fatal(err) 441 } 442 443 if len(m.Dishes) != 2 { 444 t.Log("two dishes should be loaded with UnmarshalTOML()") 445 t.Errorf("expected %d but got %d", 2, len(m.Dishes)) 446 } 447 448 eggSalad := m.Dishes["eggsalad"] 449 if _, ok := interface{}(eggSalad).(dish); !ok { 450 t.Errorf("expected a dish") 451 } 452 453 if eggSalad.Name != "Egg Salad with rice" { 454 t.Errorf("expected the dish to be named 'Egg Salad with rice'") 455 } 456 457 if len(eggSalad.Ingredients) != 3 { 458 t.Log("dish should be loaded with UnmarshalTOML()") 459 t.Errorf("expected %d but got %d", 3, len(eggSalad.Ingredients)) 460 } 461 462 found := false 463 for _, i := range eggSalad.Ingredients { 464 if i.Name == "Rice" { 465 found = true 466 break 467 } 468 } 469 if !found { 470 t.Error("Rice was not loaded in UnmarshalTOML()") 471 } 472 473 // test on a value - must be passed as * 474 o := menu{} 475 if _, err := Decode(tomlBlob, &o); err != nil { 476 log.Fatal(err) 477 } 478 479} 480 481type menu struct { 482 Dishes map[string]dish 483} 484 485func (m *menu) UnmarshalTOML(p interface{}) error { 486 m.Dishes = make(map[string]dish) 487 data, _ := p.(map[string]interface{}) 488 dishes := data["dishes"].(map[string]interface{}) 489 for n, v := range dishes { 490 if d, ok := v.(map[string]interface{}); ok { 491 nd := dish{} 492 nd.UnmarshalTOML(d) 493 m.Dishes[n] = nd 494 } else { 495 return fmt.Errorf("not a dish") 496 } 497 } 498 return nil 499} 500 501type dish struct { 502 Name string 503 Price float32 504 Ingredients []ingredient 505} 506 507func (d *dish) UnmarshalTOML(p interface{}) error { 508 data, _ := p.(map[string]interface{}) 509 d.Name, _ = data["name"].(string) 510 d.Price, _ = data["price"].(float32) 511 ingredients, _ := data["ingredients"].([]map[string]interface{}) 512 for _, e := range ingredients { 513 n, _ := interface{}(e).(map[string]interface{}) 514 name, _ := n["name"].(string) 515 i := ingredient{name} 516 d.Ingredients = append(d.Ingredients, i) 517 } 518 return nil 519} 520 521type ingredient struct { 522 Name string 523} 524 525func TestDecodeSlices(t *testing.T) { 526 s := struct{ Test []string }{Test: []string{}} 527 if _, err := Decode(`Test = ["test"]`, &s); err != nil { 528 t.Errorf("Error decoding into empty slice: %s", err) 529 } 530 s.Test = []string{"a", "b", "c"} 531 if _, err := Decode(`Test = ["test"]`, &s); err != nil { 532 t.Errorf("Error decoding into oversized slice: %s", err) 533 } 534 if want := []string{"test"}; !reflect.DeepEqual(s.Test, want) { 535 t.Errorf("Got %v; want %v", s.Test, want) 536 } 537} 538 539func ExampleMetaData_PrimitiveDecode() { 540 var md MetaData 541 var err error 542 543 var tomlBlob = ` 544ranking = ["Springsteen", "J Geils"] 545 546[bands.Springsteen] 547started = 1973 548albums = ["Greetings", "WIESS", "Born to Run", "Darkness"] 549 550[bands."J Geils"] 551started = 1970 552albums = ["The J. Geils Band", "Full House", "Blow Your Face Out"] 553` 554 555 type band struct { 556 Started int 557 Albums []string 558 } 559 type classics struct { 560 Ranking []string 561 Bands map[string]Primitive 562 } 563 564 // Do the initial decode. Reflection is delayed on Primitive values. 565 var music classics 566 if md, err = Decode(tomlBlob, &music); err != nil { 567 log.Fatal(err) 568 } 569 570 // MetaData still includes information on Primitive values. 571 fmt.Printf("Is `bands.Springsteen` defined? %v\n", 572 md.IsDefined("bands", "Springsteen")) 573 574 // Decode primitive data into Go values. 575 for _, artist := range music.Ranking { 576 // A band is a primitive value, so we need to decode it to get a 577 // real `band` value. 578 primValue := music.Bands[artist] 579 580 var aBand band 581 if err = md.PrimitiveDecode(primValue, &aBand); err != nil { 582 log.Fatal(err) 583 } 584 fmt.Printf("%s started in %d.\n", artist, aBand.Started) 585 } 586 // Check to see if there were any fields left undecoded. 587 // Note that this won't be empty before decoding the Primitive value! 588 fmt.Printf("Undecoded: %q\n", md.Undecoded()) 589 590 // Output: 591 // Is `bands.Springsteen` defined? true 592 // Springsteen started in 1973. 593 // J Geils started in 1970. 594 // Undecoded: [] 595} 596 597func ExampleDecode() { 598 var tomlBlob = ` 599# Some comments. 600[alpha] 601ip = "10.0.0.1" 602 603 [alpha.config] 604 Ports = [ 8001, 8002 ] 605 Location = "Toronto" 606 Created = 1987-07-05T05:45:00Z 607 608[beta] 609ip = "10.0.0.2" 610 611 [beta.config] 612 Ports = [ 9001, 9002 ] 613 Location = "New Jersey" 614 Created = 1887-01-05T05:55:00Z 615` 616 617 type serverConfig struct { 618 Ports []int 619 Location string 620 Created time.Time 621 } 622 623 type server struct { 624 IP string `toml:"ip,omitempty"` 625 Config serverConfig `toml:"config"` 626 } 627 628 type servers map[string]server 629 630 var config servers 631 if _, err := Decode(tomlBlob, &config); err != nil { 632 log.Fatal(err) 633 } 634 635 for _, name := range []string{"alpha", "beta"} { 636 s := config[name] 637 fmt.Printf("Server: %s (ip: %s) in %s created on %s\n", 638 name, s.IP, s.Config.Location, 639 s.Config.Created.Format("2006-01-02")) 640 fmt.Printf("Ports: %v\n", s.Config.Ports) 641 } 642 643 // Output: 644 // Server: alpha (ip: 10.0.0.1) in Toronto created on 1987-07-05 645 // Ports: [8001 8002] 646 // Server: beta (ip: 10.0.0.2) in New Jersey created on 1887-01-05 647 // Ports: [9001 9002] 648} 649 650type duration struct { 651 time.Duration 652} 653 654func (d *duration) UnmarshalText(text []byte) error { 655 var err error 656 d.Duration, err = time.ParseDuration(string(text)) 657 return err 658} 659 660// Example Unmarshaler shows how to decode TOML strings into your own 661// custom data type. 662func Example_unmarshaler() { 663 blob := ` 664[[song]] 665name = "Thunder Road" 666duration = "4m49s" 667 668[[song]] 669name = "Stairway to Heaven" 670duration = "8m03s" 671` 672 type song struct { 673 Name string 674 Duration duration 675 } 676 type songs struct { 677 Song []song 678 } 679 var favorites songs 680 if _, err := Decode(blob, &favorites); err != nil { 681 log.Fatal(err) 682 } 683 684 // Code to implement the TextUnmarshaler interface for `duration`: 685 // 686 // type duration struct { 687 // time.Duration 688 // } 689 // 690 // func (d *duration) UnmarshalText(text []byte) error { 691 // var err error 692 // d.Duration, err = time.ParseDuration(string(text)) 693 // return err 694 // } 695 696 for _, s := range favorites.Song { 697 fmt.Printf("%s (%s)\n", s.Name, s.Duration) 698 } 699 // Output: 700 // Thunder Road (4m49s) 701 // Stairway to Heaven (8m3s) 702} 703 704// Example StrictDecoding shows how to detect whether there are keys in the 705// TOML document that weren't decoded into the value given. This is useful 706// for returning an error to the user if they've included extraneous fields 707// in their configuration. 708func Example_strictDecoding() { 709 var blob = ` 710key1 = "value1" 711key2 = "value2" 712key3 = "value3" 713` 714 type config struct { 715 Key1 string 716 Key3 string 717 } 718 719 var conf config 720 md, err := Decode(blob, &conf) 721 if err != nil { 722 log.Fatal(err) 723 } 724 fmt.Printf("Undecoded keys: %q\n", md.Undecoded()) 725 // Output: 726 // Undecoded keys: ["key2"] 727} 728 729// Example UnmarshalTOML shows how to implement a struct type that knows how to 730// unmarshal itself. The struct must take full responsibility for mapping the 731// values passed into the struct. The method may be used with interfaces in a 732// struct in cases where the actual type is not known until the data is 733// examined. 734func Example_unmarshalTOML() { 735 736 var blob = ` 737[[parts]] 738type = "valve" 739id = "valve-1" 740size = 1.2 741rating = 4 742 743[[parts]] 744type = "valve" 745id = "valve-2" 746size = 2.1 747rating = 5 748 749[[parts]] 750type = "pipe" 751id = "pipe-1" 752length = 2.1 753diameter = 12 754 755[[parts]] 756type = "cable" 757id = "cable-1" 758length = 12 759rating = 3.1 760` 761 o := &order{} 762 err := Unmarshal([]byte(blob), o) 763 if err != nil { 764 log.Fatal(err) 765 } 766 767 fmt.Println(len(o.parts)) 768 769 for _, part := range o.parts { 770 fmt.Println(part.Name()) 771 } 772 773 // Code to implement UmarshalJSON. 774 775 // type order struct { 776 // // NOTE `order.parts` is a private slice of type `part` which is an 777 // // interface and may only be loaded from toml using the 778 // // UnmarshalTOML() method of the Umarshaler interface. 779 // parts parts 780 // } 781 782 // func (o *order) UnmarshalTOML(data interface{}) error { 783 784 // // NOTE the example below contains detailed type casting to show how 785 // // the 'data' is retrieved. In operational use, a type cast wrapper 786 // // may be prefered e.g. 787 // // 788 // // func AsMap(v interface{}) (map[string]interface{}, error) { 789 // // return v.(map[string]interface{}) 790 // // } 791 // // 792 // // resulting in: 793 // // d, _ := AsMap(data) 794 // // 795 796 // d, _ := data.(map[string]interface{}) 797 // parts, _ := d["parts"].([]map[string]interface{}) 798 799 // for _, p := range parts { 800 801 // typ, _ := p["type"].(string) 802 // id, _ := p["id"].(string) 803 804 // // detect the type of part and handle each case 805 // switch p["type"] { 806 // case "valve": 807 808 // size := float32(p["size"].(float64)) 809 // rating := int(p["rating"].(int64)) 810 811 // valve := &valve{ 812 // Type: typ, 813 // ID: id, 814 // Size: size, 815 // Rating: rating, 816 // } 817 818 // o.parts = append(o.parts, valve) 819 820 // case "pipe": 821 822 // length := float32(p["length"].(float64)) 823 // diameter := int(p["diameter"].(int64)) 824 825 // pipe := &pipe{ 826 // Type: typ, 827 // ID: id, 828 // Length: length, 829 // Diameter: diameter, 830 // } 831 832 // o.parts = append(o.parts, pipe) 833 834 // case "cable": 835 836 // length := int(p["length"].(int64)) 837 // rating := float32(p["rating"].(float64)) 838 839 // cable := &cable{ 840 // Type: typ, 841 // ID: id, 842 // Length: length, 843 // Rating: rating, 844 // } 845 846 // o.parts = append(o.parts, cable) 847 848 // } 849 // } 850 851 // return nil 852 // } 853 854 // type parts []part 855 856 // type part interface { 857 // Name() string 858 // } 859 860 // type valve struct { 861 // Type string 862 // ID string 863 // Size float32 864 // Rating int 865 // } 866 867 // func (v *valve) Name() string { 868 // return fmt.Sprintf("VALVE: %s", v.ID) 869 // } 870 871 // type pipe struct { 872 // Type string 873 // ID string 874 // Length float32 875 // Diameter int 876 // } 877 878 // func (p *pipe) Name() string { 879 // return fmt.Sprintf("PIPE: %s", p.ID) 880 // } 881 882 // type cable struct { 883 // Type string 884 // ID string 885 // Length int 886 // Rating float32 887 // } 888 889 // func (c *cable) Name() string { 890 // return fmt.Sprintf("CABLE: %s", c.ID) 891 // } 892 893 // Output: 894 // 4 895 // VALVE: valve-1 896 // VALVE: valve-2 897 // PIPE: pipe-1 898 // CABLE: cable-1 899 900} 901 902type order struct { 903 // NOTE `order.parts` is a private slice of type `part` which is an 904 // interface and may only be loaded from toml using the UnmarshalTOML() 905 // method of the Umarshaler interface. 906 parts parts 907} 908 909func (o *order) UnmarshalTOML(data interface{}) error { 910 911 // NOTE the example below contains detailed type casting to show how 912 // the 'data' is retrieved. In operational use, a type cast wrapper 913 // may be prefered e.g. 914 // 915 // func AsMap(v interface{}) (map[string]interface{}, error) { 916 // return v.(map[string]interface{}) 917 // } 918 // 919 // resulting in: 920 // d, _ := AsMap(data) 921 // 922 923 d, _ := data.(map[string]interface{}) 924 parts, _ := d["parts"].([]map[string]interface{}) 925 926 for _, p := range parts { 927 928 typ, _ := p["type"].(string) 929 id, _ := p["id"].(string) 930 931 // detect the type of part and handle each case 932 switch p["type"] { 933 case "valve": 934 935 size := float32(p["size"].(float64)) 936 rating := int(p["rating"].(int64)) 937 938 valve := &valve{ 939 Type: typ, 940 ID: id, 941 Size: size, 942 Rating: rating, 943 } 944 945 o.parts = append(o.parts, valve) 946 947 case "pipe": 948 949 length := float32(p["length"].(float64)) 950 diameter := int(p["diameter"].(int64)) 951 952 pipe := &pipe{ 953 Type: typ, 954 ID: id, 955 Length: length, 956 Diameter: diameter, 957 } 958 959 o.parts = append(o.parts, pipe) 960 961 case "cable": 962 963 length := int(p["length"].(int64)) 964 rating := float32(p["rating"].(float64)) 965 966 cable := &cable{ 967 Type: typ, 968 ID: id, 969 Length: length, 970 Rating: rating, 971 } 972 973 o.parts = append(o.parts, cable) 974 975 } 976 } 977 978 return nil 979} 980 981type parts []part 982 983type part interface { 984 Name() string 985} 986 987type valve struct { 988 Type string 989 ID string 990 Size float32 991 Rating int 992} 993 994func (v *valve) Name() string { 995 return fmt.Sprintf("VALVE: %s", v.ID) 996} 997 998type pipe struct { 999 Type string 1000 ID string 1001 Length float32 1002 Diameter int 1003} 1004 1005func (p *pipe) Name() string { 1006 return fmt.Sprintf("PIPE: %s", p.ID) 1007} 1008 1009type cable struct { 1010 Type string 1011 ID string 1012 Length int 1013 Rating float32 1014} 1015 1016func (c *cable) Name() string { 1017 return fmt.Sprintf("CABLE: %s", c.ID) 1018} 1019