diff --git a/README.md b/README.md index 820357d5..b8c2c533 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,9 @@ fmt.Printf("%+v\n", v) // {A:{B:1 C:hello}} ### 3.1. Explicitly declared `Anchor` name and `Alias` name -If you want to use `anchor` or `alias`, you can define it as a struct tag. +If you want to use `anchor`, you can define it as a struct tag. +If the value specified for an anchor is a pointer type and the same address as the pointer is found, the value is automatically set to alias. +If an explicit alias name is specified, an error is raised if its value is different from the value specified in the anchor. ```go type T struct { @@ -216,10 +218,7 @@ d: *x If you do not explicitly declare the anchor name, the default behavior is to use the equivalent of `strings.ToLower($FieldName)` as the name of the anchor. - -If you do not explicitly declare the alias name AND the value is a pointer -to another element, we look up the anchor name by finding out which anchor -field the value is assigned to by looking up its pointer address. +If the value specified for an anchor is a pointer type and the same address as the pointer is found, the value is automatically set to alias. ```go type T struct { @@ -229,8 +228,8 @@ type T struct { var v struct { A *T `yaml:"a,anchor"` B *T `yaml:"b,anchor"` - C *T `yaml:"c,alias"` - D *T `yaml:"d,alias"` + C *T `yaml:"c"` + D *T `yaml:"d"` } v.A = &T{I: 1, S: "hello"} v.B = &T{I: 2, S: "world"} diff --git a/encode.go b/encode.go index 265d0442..027b1be2 100644 --- a/encode.go +++ b/encode.go @@ -787,42 +787,27 @@ func (e *Encoder) encodeStruct(ctx context.Context, value reflect.Value, column } var key ast.MapKeyNode = e.encodeString(structField.RenderName, column) switch { - case structField.AnchorName != "": - anchorNode, err := e.encodeAnchor(structField.AnchorName, value, fieldValue, column) - if err != nil { - return nil, err - } - value = anchorNode - case structField.IsAutoAlias: - if fieldValue.Kind() != reflect.Ptr { - return nil, fmt.Errorf( - "%s in struct is not pointer type. but required automatically alias detection", - structField.FieldName, - ) - } - anchorName := e.anchorPtrToNameMap[fieldValue.Pointer()] - if anchorName == "" { - return nil, errors.New( - "cannot find anchor name from pointer address for automatically alias detection", - ) + case value.Type() == ast.AliasType: + if aliasName := structField.AliasName; aliasName != "" { + alias, ok := value.(*ast.AliasNode) + if !ok { + return nil, errors.ErrUnexpectedNodeType(value.Type(), ast.AliasType, value.GetToken()) + } + got := alias.Value.String() + if aliasName != got { + return nil, fmt.Errorf("expected alias name is %q but got %q", aliasName, got) + } } - aliasName := anchorName - alias := ast.Alias(token.New("*", "*", e.pos(column))) - alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column))) - value = alias if structField.IsInline { // if both used alias and inline, output `<<: *alias` key = ast.MergeKey(token.New("<<", "<<", e.pos(column))) } - case structField.AliasName != "": - aliasName := structField.AliasName - alias := ast.Alias(token.New("*", "*", e.pos(column))) - alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column))) - value = alias - if structField.IsInline { - // if both used alias and inline, output `<<: *alias` - key = ast.MergeKey(token.New("<<", "<<", e.pos(column))) + case structField.AnchorName != "": + anchorNode, err := e.encodeAnchor(structField.AnchorName, value, fieldValue, column) + if err != nil { + return nil, err } + value = anchorNode case structField.IsInline: isAutoAnchor := structField.IsAutoAnchor if !hasInlineAnchorField { diff --git a/encode_test.go b/encode_test.go index bcf2f3f2..1d9b9bd4 100644 --- a/encode_test.go +++ b/encode_test.go @@ -804,8 +804,8 @@ func TestEncodeWithAutoAlias(t *testing.T) { 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"` + C *T `yaml:"c"` + D *T `yaml:"d"` } v.A = &T{I: 1, S: "hello"} v.B = &T{I: 2, S: "world"} @@ -838,8 +838,8 @@ func TestEncodeWithImplicitAnchorAndAlias(t *testing.T) { var v struct { A *T `yaml:"a,anchor"` B *T `yaml:"b,anchor"` - C *T `yaml:"c,alias"` - D *T `yaml:"d,alias"` + C *T `yaml:"c"` + D *T `yaml:"d"` } v.A = &T{I: 1, S: "hello"} v.B = &T{I: 2, S: "world"} @@ -864,7 +864,7 @@ d: *b func TestEncodeWithMerge(t *testing.T) { type Person struct { - *Person `yaml:",omitempty,inline,alias"` + *Person `yaml:",omitempty,inline"` Name string `yaml:",omitempty"` Age int `yaml:",omitempty"` } @@ -1151,8 +1151,8 @@ func TestEncoder_MarshalAnchor(t *testing.T) { Host *Host `yaml:",anchor"` } type Queue struct { - Name string `yaml:","` - *Host `yaml:",alias"` + Name string `yaml:","` + *Host } var doc struct { Hosts []*HostDecl `yaml:"hosts"` @@ -1338,8 +1338,8 @@ func ExampleMarshal_implicitAnchorAlias() { var v struct { A *T `yaml:"a,anchor"` B *T `yaml:"b,anchor"` - C *T `yaml:"c,alias"` - D *T `yaml:"d,alias"` + C *T `yaml:"c"` + D *T `yaml:"d"` } v.A = &T{I: 1, S: "hello"} v.B = &T{I: 2, S: "world"} @@ -1675,3 +1675,45 @@ func TestIssue174(t *testing.T) { t.Fatalf("failed to encode: %q", got) } } + +func TestIssue259(t *testing.T) { + type AnchorValue struct { + Foo uint64 + Bar string + } + + type Value struct { + Baz string `yaml:"baz"` + Value *AnchorValue `yaml:"value,anchor"` + } + + type Schema struct { + Values []*Value + } + + schema := Schema{} + anchorValue := AnchorValue{Foo: 3, Bar: "bar"} + schema.Values = []*Value{ + {Baz: "xxx", Value: &anchorValue}, + {Baz: "yyy", Value: &anchorValue}, + {Baz: "zzz", Value: &anchorValue}, + } + b, err := yaml.Marshal(schema) + if err != nil { + t.Fatal(err) + } + expected := ` +values: +- baz: xxx + value: &value + foo: 3 + bar: bar +- baz: yyy + value: *value +- baz: zzz + value: *value +` + if strings.TrimPrefix(expected, "\n") != string(b) { + t.Fatalf("failed to encode: got = %s", string(b)) + } +} diff --git a/testdata/yaml_test.go b/testdata/yaml_test.go index d3ca8160..55a3c913 100644 --- a/testdata/yaml_test.go +++ b/testdata/yaml_test.go @@ -312,7 +312,7 @@ b: *a ` var v struct { A *int `yaml:"a,anchor"` - B *int `yaml:"b,alias"` + B *int `yaml:"b"` } if err := yaml.Unmarshal([]byte(yml), &v); err != nil { t.Fatal(err)