package yaml_test import ( "bytes" "fmt" "math" "strconv" "testing" "github.com/goccy/go-yaml" ) func TestEncoder(t *testing.T) { tests := []struct { source string value interface{} }{ { "null\n", (*struct{})(nil), }, { "v: hi\n", map[string]string{"v": "hi"}, }, { "v: \"true\"\n", map[string]string{"v": "true"}, }, { "v: \"false\"\n", map[string]string{"v": "false"}, }, { "v: true\n", map[string]interface{}{"v": true}, }, { "v: false\n", map[string]bool{"v": false}, }, { "v: 10\n", map[string]int{"v": 10}, }, { "v: -10\n", map[string]int{"v": -10}, }, { "v: 4294967296\n", map[string]int{"v": 4294967296}, }, { "v: 0.1\n", map[string]interface{}{"v": 0.1}, }, { "v: 0.99\n", map[string]float32{"v": 0.99}, }, { "v: -0.1\n", map[string]float64{"v": -0.1}, }, { "v: 1.0\n", map[string]float64{"v": 1.0}, }, { "v: .inf\n", map[string]interface{}{"v": math.Inf(0)}, }, { "v: -.inf\n", map[string]interface{}{"v": math.Inf(-1)}, }, { "v: .nan\n", map[string]interface{}{"v": math.NaN()}, }, { "v: null\n", map[string]interface{}{"v": nil}, }, { "v: \"\"\n", map[string]string{"v": ""}, }, { "v:\n- A\n- B\n", map[string][]string{"v": {"A", "B"}}, }, { "a: -\n", map[string]string{"a": "-"}, }, { "123\n", 123, }, { "hello: world\n", map[string]string{"hello": "world"}, }, { "v:\n- A\n- 1\n- B:\n - 2\n - 3\n", map[string]interface{}{ "v": []interface{}{ "A", 1, map[string][]int{ "B": {2, 3}, }, }, }, }, { "a:\n b: c\n", map[string]interface{}{ "a": map[string]string{ "b": "c", }, }, }, { "t2: 2018-01-09T10:40:47Z\nt4: 2098-01-09T10:40:47Z\n", map[string]string{ "t2": "2018-01-09T10:40:47Z", "t4": "2098-01-09T10:40:47Z", }, }, { "a:\n b: c\n d: e\n", map[string]interface{}{ "a": map[string]string{ "b": "c", "d": "e", }, }, }, { "a: 3s\n", map[string]string{ "a": "3s", }, }, { "a: \n", map[string]string{"a": ""}, }, { "a: \"1:1\"\n", map[string]string{"a": "1:1"}, }, { "a: 1.2.3.4\n", map[string]string{"a": "1.2.3.4"}, }, { "a: \"b: c\"\n", map[string]string{"a": "b: c"}, }, { "a: \"Hello #comment\"\n", map[string]string{"a": "Hello #comment"}, }, { "a: 100.5\n", map[string]interface{}{ "a": 100.5, }, }, { "a: \"\\\\0\"\n", map[string]string{"a": "\\0"}, }, { "a: 1\nb: 2\nc: 3\nd: 4\nsub:\n e: 5\n", map[string]interface{}{ "a": 1, "b": 2, "c": 3, "d": 4, "sub": map[string]int{ "e": 5, }, }, }, { "a: b\nc: d\n", struct { A string C string `yaml:"c"` }{ "b", "d", }, }, { "a: 1\n", struct { A int B int `yaml:"-"` }{ 1, 0, }, }, // Conditional flag { "a: 1\n", struct { A int "a,omitempty" B int "b,omitempty" }{1, 0}, }, { "{}\n", struct { A int "a,omitempty" B int "b,omitempty" }{0, 0}, }, { "a: {x: 1}\n", struct { A *struct{ X, y int } "a,omitempty,flow" }{&struct{ X, y int }{1, 2}}, }, { "{}\n", struct { A *struct{ X, y int } "a,omitempty,flow" }{nil}, }, { "a: {x: 0}\n", struct { A *struct{ X, y int } "a,omitempty,flow" }{&struct{ X, y int }{}}, }, { "a: {x: 1}\n", struct { A struct{ X, y int } "a,omitempty,flow" }{struct{ X, y int }{1, 2}}, }, { "{}\n", struct { A struct{ X, y int } "a,omitempty,flow" }{struct{ X, y int }{0, 1}}, }, { "a: 1.0\n", struct { A float64 "a,omitempty" B float64 "b,omitempty" }{1, 0}, }, // Flow flag { "a: [1, 2]\n", struct { A []int "a,flow" }{[]int{1, 2}}, }, { "a: {b: c, d: e}\n", &struct { A map[string]string "a,flow" }{map[string]string{"b": "c", "d": "e"}}, }, { "a: {b: c, d: e}\n", struct { A struct { B, D string } "a,flow" }{struct{ B, D string }{"c", "e"}}, }, } for _, test := range tests { var buf bytes.Buffer enc := yaml.NewEncoder(&buf) if err := enc.Encode(test.value); err != nil { t.Fatalf("%+v", err) } if test.source != buf.String() { t.Fatalf("expect = [%s], actual = [%s]", test.source, buf.String()) } } } func TestEncodeWithAnchorAndAlias(t *testing.T) { var buf bytes.Buffer enc := yaml.NewEncoder(&buf) type T struct { A int B string } var v struct { A *T `yaml:"a,anchor=c"` B *T `yaml:"b,alias=c"` } v.A = &T{A: 1, B: "hello"} v.B = v.A if err := enc.Encode(v); err != nil { t.Fatalf("%+v", err) } expect := "a: &c\n a: 1\n b: hello\nb: *c\n" if expect != buf.String() { t.Fatalf("expect = [%s], actual = [%s]", expect, buf.String()) } } func TestEncodeWithAutoAlias(t *testing.T) { var buf bytes.Buffer enc := yaml.NewEncoder(&buf) type T struct { I int S string } var v struct { A *T `yaml:"a,anchor=a"` B *T `yaml:"b,anchor=b"` C *T `yaml:"c,alias"` D *T `yaml:"d,alias"` } v.A = &T{I: 1, S: "hello"} v.B = &T{I: 2, S: "world"} v.C = v.A v.D = v.B if err := enc.Encode(v); err != nil { t.Fatalf("%+v", err) } expect := `a: &a i: 1 s: hello b: &b i: 2 s: world c: *a d: *b ` if expect != buf.String() { t.Fatalf("expect = [%s], actual = [%s]", expect, buf.String()) } } func TestEncodeWithImplicitAnchorAndAlias(t *testing.T) { var buf bytes.Buffer enc := yaml.NewEncoder(&buf) type T struct { I int S string } var v struct { A *T `yaml:"a,anchor"` B *T `yaml:"b,anchor"` C *T `yaml:"c,alias"` D *T `yaml:"d,alias"` } v.A = &T{I: 1, S: "hello"} v.B = &T{I: 2, S: "world"} v.C = v.A v.D = v.B if err := enc.Encode(v); err != nil { t.Fatalf("%+v", err) } expect := `a: &a i: 1 s: hello b: &b i: 2 s: world c: *a d: *b ` if expect != buf.String() { t.Fatalf("expect = [%s], actual = [%s]", expect, buf.String()) } } func TestEncodeWithMerge(t *testing.T) { type Person struct { *Person `yaml:",omitempty,inline,alias"` Name string `yaml:",omitempty"` Age int `yaml:",omitempty"` } defaultPerson := &Person{ Name: "John Smith", Age: 20, } people := []*Person{ { Person: defaultPerson, Name: "Ken", Age: 10, }, { Person: defaultPerson, }, } var doc struct { Default *Person `yaml:"default,anchor"` People []*Person `yaml:"people"` } doc.Default = defaultPerson doc.People = people var buf bytes.Buffer enc := yaml.NewEncoder(&buf) if err := enc.Encode(doc); err != nil { t.Fatalf("%+v", err) } expect := `default: &default name: John Smith age: 20 people: - <<: *default name: Ken age: 10 - <<: *default ` if expect != buf.String() { t.Fatalf("expect = [%s], actual = [%s]", expect, buf.String()) } } func TestEncoder_Inline(t *testing.T) { type base struct { A int B string } var buf bytes.Buffer enc := yaml.NewEncoder(&buf) if err := enc.Encode(struct { *base `yaml:",inline"` C bool }{ base: &base{ A: 1, B: "hello", }, C: true, }); err != nil { t.Fatalf("%+v", err) } expect := ` a: 1 b: hello c: true ` actual := "\n" + buf.String() if expect != actual { t.Fatalf("inline marshal error: expect=[%s] actual=[%s]", expect, actual) } } func TestEncoder_InlineAndConflictKey(t *testing.T) { type base struct { A int B string } var buf bytes.Buffer enc := yaml.NewEncoder(&buf) if err := enc.Encode(struct { *base `yaml:",inline"` A int // conflict C bool }{ base: &base{ A: 1, B: "hello", }, A: 0, // default value C: true, }); err != nil { t.Fatalf("%+v", err) } expect := ` b: hello a: 0 c: true ` actual := "\n" + buf.String() if expect != actual { t.Fatalf("inline marshal error: expect=[%s] actual=[%s]", expect, actual) } } func TestEncoder_Flow(t *testing.T) { var buf bytes.Buffer enc := yaml.NewEncoder(&buf, yaml.Flow(true)) var v struct { A int B string C struct { D int E string } F []int } v.A = 1 v.B = "hello" v.C.D = 3 v.C.E = "world" v.F = []int{1, 2} if err := enc.Encode(v); err != nil { t.Fatalf("%+v", err) } expect := ` {a: 1, b: hello, c: {d: 3, e: world}, f: [1, 2]} ` actual := "\n" + buf.String() if expect != actual { t.Fatalf("flow style marshal error: expect=[%s] actual=[%s]", expect, actual) } } func Example_Marshal_ExplicitAnchorAlias() { type T struct { A int B string } var v struct { C *T `yaml:"c,anchor=x"` D *T `yaml:"d,alias=x"` } v.C = &T{A: 1, B: "hello"} v.D = v.C bytes, err := yaml.Marshal(v) if err != nil { panic(err) } fmt.Println(string(bytes)) // OUTPUT: //c: &x // a: 1 // b: hello //d: *x } func Example_Marshal_ImplicitAnchorAlias() { type T struct { I int S string } var v struct { A *T `yaml:"a,anchor"` B *T `yaml:"b,anchor"` C *T `yaml:"c,alias"` D *T `yaml:"d,alias"` } v.A = &T{I: 1, S: "hello"} v.B = &T{I: 2, S: "world"} v.C = v.A // C has same pointer address to A v.D = v.B // D has same pointer address to B bytes, err := yaml.Marshal(v) if err != nil { panic(err) } fmt.Println(string(bytes)) // OUTPUT: // a: &a // i: 1 // s: hello // b: &b // i: 2 // s: world // c: *a // d: *b } type tMarshal []string func (t *tMarshal) MarshalYAML() ([]byte, error) { var buf bytes.Buffer buf.WriteString("tags:\n") for i, v := range *t { if i == 0 { fmt.Fprintf(&buf, "- %s\n", v) } else { fmt.Fprintf(&buf, " %s\n", v) } } return buf.Bytes(), nil } func Test_Marshaler(t *testing.T) { const expected = `- hello-world ` // sanity check var l []string if err := yaml.Unmarshal([]byte(expected), &l); err != nil { t.Fatalf("failed to parse string: %s", err) } buf, err := yaml.Marshal(tMarshal{"hello-world"}) if err != nil { t.Fatalf("failed to marshal: %s", err) } if string(buf) != expected { t.Fatalf("expected '%s', got '%s'", expected, buf) } t.Logf("%s", buf) } type SlowMarshaler struct { A string B int } type FastMarshaler struct { A string B int } func (v SlowMarshaler) MarshalYAML() ([]byte, error) { var buf bytes.Buffer buf.WriteString("tags:\n") buf.WriteString("- slow-marshaler\n") buf.WriteString("a: " + v.A + "\n") buf.WriteString("b: " + strconv.FormatInt(int64(v.B), 10) + "\n") return buf.Bytes(), nil } func (v FastMarshaler) MarshalYAML() (interface{}, error) { return yaml.MapSlice{ {"tags", []string{"fast-marshaler"}}, {"a", v.A}, {"b", v.B}, }, nil } func Example_MarshalYAML() { var slow SlowMarshaler slow.A = "Hello slow poke" slow.B = 100 buf, err := yaml.Marshal(slow) if err != nil { panic(err.Error()) } fmt.Println(string(buf)) var fast FastMarshaler fast.A = "Hello speed demon" fast.B = 100 buf, err = yaml.Marshal(fast) if err != nil { panic(err.Error()) } fmt.Println(string(buf)) // OUTPUT: // tags: // - slow-marshaler // a: Hello slow poke // b: 100 // // tags: // - fast-marshaler // a: Hello speed demon // b: 100 }