Repository: goccy/go-yaml
Pull request #87 (2020-03-02 14:14:59)

Support complexity encoding/decoding with inline and anchor and alias
See https://github.com/goccy/go-yaml/compare/feature/complexity-reference?expand=1#diff-b9712b0e0447a2be5d319fb286dee2afR236


Patch:
diff --git a/ast/ast.go b/ast/ast.go
index 3d0ab90..1a88f2e 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -505,6 +505,11 @@ func (n *MergeKeyNode) String() string {
 	return n.Token.Value
 }
 
+// AddColumn add column number to child nodes recursively
+func (n *MergeKeyNode) AddColumn(col int) {
+	n.Token.AddColumn(col)
+}
+
 // BoolNode type of boolean node
 type BoolNode struct {
 	ScalarNode
@@ -873,7 +878,9 @@ func (n *AnchorNode) String() string {
 	value := n.Value.String()
 	if len(strings.Split(value, "\n")) > 1 {
 		return fmt.Sprintf("&%s\n%s", n.Name.String(), value)
-	} else if _, ok := n.Value.(*SequenceNode); ok {
+	} else if s, ok := n.Value.(*SequenceNode); ok && !s.IsFlowStyle {
+		return fmt.Sprintf("&%s\n%s", n.Name.String(), value)
+	} else if m, ok := n.Value.(*MappingNode); ok && !m.IsFlowStyle {
 		return fmt.Sprintf("&%s\n%s", n.Name.String(), value)
 	}
 	return fmt.Sprintf("&%s %s", n.Name.String(), value)
diff --git a/decode.go b/decode.go
index d489cac..1fc1300 100644
--- a/decode.go
+++ b/decode.go
@@ -339,7 +339,9 @@ func (d *Decoder) deleteStructKeys(structValue reflect.Value, unknownFields map[
 func (d *Decoder) decodeValue(dst reflect.Value, src ast.Node) error {
 	if src.Type() == ast.AnchorType {
 		anchorName := src.(*ast.AnchorNode).Name.GetToken().Value
-		d.anchorValueMap[anchorName] = dst
+		if _, exists := d.anchorValueMap[anchorName]; !exists {
+			d.anchorValueMap[anchorName] = dst
+		}
 	}
 	valueType := dst.Type()
 	if unmarshaler, ok := dst.Addr().Interface().(BytesUnmarshaler); ok {
@@ -513,7 +515,7 @@ func (d *Decoder) createDecodedNewValue(typ reflect.Type, node ast.Node) (reflec
 	return newValue, nil
 }
 
-func (d *Decoder) keyToNodeMap(node ast.Node, getKeyOrValueNode func(*ast.MapNodeIter) ast.Node) (map[string]ast.Node, error) {
+func (d *Decoder) keyToNodeMap(node ast.Node, ignoreMergeKey bool, getKeyOrValueNode func(*ast.MapNodeIter) ast.Node) (map[string]ast.Node, error) {
 	mapNode, err := d.getMapNode(node)
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to get map node")
@@ -526,7 +528,10 @@ func (d *Decoder) keyToNodeMap(node ast.Node, getKeyOrValueNode func(*ast.MapNod
 	for mapIter.Next() {
 		keyNode := mapIter.Key()
 		if keyNode.Type() == ast.MergeKeyType {
-			mergeMap, err := d.keyToNodeMap(mapIter.Value(), getKeyOrValueNode)
+			if ignoreMergeKey {
+				continue
+			}
+			mergeMap, err := d.keyToNodeMap(mapIter.Value(), ignoreMergeKey, getKeyOrValueNode)
 			if err != nil {
 				return nil, errors.Wrapf(err, "failed to get keyToNodeMap by MergeKey node")
 			}
@@ -544,16 +549,16 @@ func (d *Decoder) keyToNodeMap(node ast.Node, getKeyOrValueNode func(*ast.MapNod
 	return keyToNodeMap, nil
 }
 
-func (d *Decoder) keyToKeyNodeMap(node ast.Node) (map[string]ast.Node, error) {
-	m, err := d.keyToNodeMap(node, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Key() })
+func (d *Decoder) keyToKeyNodeMap(node ast.Node, ignoreMergeKey bool) (map[string]ast.Node, error) {
+	m, err := d.keyToNodeMap(node, ignoreMergeKey, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Key() })
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to get keyToNodeMap")
 	}
 	return m, nil
 }
 
-func (d *Decoder) keyToValueNodeMap(node ast.Node) (map[string]ast.Node, error) {
-	m, err := d.keyToNodeMap(node, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Value() })
+func (d *Decoder) keyToValueNodeMap(node ast.Node, ignoreMergeKey bool) (map[string]ast.Node, error) {
+	m, err := d.keyToNodeMap(node, ignoreMergeKey, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Value() })
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to get keyToNodeMap")
 	}
@@ -628,6 +633,26 @@ func (d *Decoder) decodeTime(dst reflect.Value, src ast.Node) error {
 	return nil
 }
 
+// getMergeAliasName support single alias only
+func (d *Decoder) getMergeAliasName(src ast.Node) string {
+	mapNode, err := d.getMapNode(src)
+	if err != nil {
+		return ""
+	}
+	if mapNode == nil {
+		return ""
+	}
+	mapIter := mapNode.MapRange()
+	for mapIter.Next() {
+		key := mapIter.Key()
+		value := mapIter.Value()
+		if key.Type() == ast.MergeKeyType && value.Type() == ast.AliasType {
+			return value.(*ast.AliasNode).Value.GetToken().Value
+		}
+	}
+	return ""
+}
+
 func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
 	if src == nil {
 		return nil
@@ -637,18 +662,20 @@ func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
 	if err != nil {
 		return errors.Wrapf(err, "failed to create struct field map")
 	}
-	keyToNodeMap, err := d.keyToValueNodeMap(src)
+	ignoreMergeKey := structFieldMap.hasMergeProperty()
+	keyToNodeMap, err := d.keyToValueNodeMap(src, ignoreMergeKey)
 	if err != nil {
 		return errors.Wrapf(err, "failed to get keyToValueNodeMap")
 	}
 	var unknownFields map[string]ast.Node
 	if d.disallowUnknownField {
-		unknownFields, err = d.keyToKeyNodeMap(src)
+		unknownFields, err = d.keyToKeyNodeMap(src, ignoreMergeKey)
 		if err != nil {
 			return errors.Wrapf(err, "failed to get keyToKeyNodeMap")
 		}
 	}
 
+	aliasName := d.getMergeAliasName(src)
 	var foundErr error
 
 	for i := 0; i < structType.NumField(); i++ {
@@ -659,6 +686,15 @@ func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
 		structField := structFieldMap[field.Name]
 		if structField.IsInline {
 			fieldValue := dst.FieldByName(field.Name)
+			if structField.IsAutoAlias {
+				if aliasName != "" {
+					newFieldValue := d.anchorValueMap[aliasName]
+					if newFieldValue.IsValid() {
+						fieldValue.Set(d.castToAssignableValue(newFieldValue, fieldValue.Type()))
+					}
+				}
+				continue
+			}
 			if !fieldValue.CanSet() {
 				return xerrors.Errorf("cannot set embedded type as unexported field %s.%s", field.PkgPath, field.Name)
 			}
@@ -667,7 +703,14 @@ func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
 				fieldValue.Set(reflect.Zero(fieldValue.Type()))
 				continue
 			}
-			newFieldValue, err := d.createDecodedNewValue(fieldValue.Type(), src)
+			mapNode := ast.Mapping(nil, false)
+			for k, v := range keyToNodeMap {
+				mapNode.Values = append(mapNode.Values, &ast.MappingValueNode{
+					Key:   &ast.StringNode{Value: k},
+					Value: v,
+				})
+			}
+			newFieldValue, err := d.createDecodedNewValue(fieldValue.Type(), mapNode)
 			if d.disallowUnknownField {
 				var ufe *unknownFieldError
 				if xerrors.As(err, &ufe) {
diff --git a/encode.go b/encode.go
index a8bf4d2..21b33eb 100644
--- a/encode.go
+++ b/encode.go
@@ -150,7 +150,17 @@ func (e *Encoder) encodeValue(v reflect.Value, column int) (ast.Node, error) {
 		return e.encodeUint(v.Uint()), nil
 	case reflect.Float32, reflect.Float64:
 		return e.encodeFloat(v.Float()), nil
-	case reflect.Ptr, reflect.Interface:
+	case reflect.Ptr:
+		anchorName := e.anchorPtrToNameMap[v.Pointer()]
+		if anchorName != "" {
+			aliasName := anchorName
+			return &ast.AliasNode{
+				Start: token.New("*", "*", e.pos(column)),
+				Value: ast.String(token.New(aliasName, aliasName, e.pos(column))),
+			}, nil
+		}
+		return e.encodeValue(v.Elem(), column)
+	case reflect.Interface:
 		return e.encodeValue(v.Elem(), column)
 	case reflect.String:
 		return e.encodeString(v.String(), column), nil
@@ -392,6 +402,8 @@ func (e *Encoder) encodeStruct(value reflect.Value, column int) (ast.Node, error
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to get struct field map")
 	}
+	hasInlineAnchorField := false
+	var inlineAnchorValue reflect.Value
 	for i := 0; i < value.NumField(); i++ {
 		field := structType.Field(i)
 		if isIgnoredStructField(field) {
@@ -425,12 +437,6 @@ func (e *Encoder) encodeStruct(value reflect.Value, column int) (ast.Node, error
 				return nil, errors.Wrapf(err, "failed to encode anchor")
 			}
 			value = anchorNode
-		case structField.IsAutoAnchor:
-			anchorNode, err := e.encodeAnchor(structField.RenderName, value, fieldValue, column)
-			if err != nil {
-				return nil, errors.Wrapf(err, "failed to encode anchor")
-			}
-			value = anchorNode
 		case structField.IsAutoAlias:
 			if fieldValue.Kind() != reflect.Ptr {
 				return nil, xerrors.Errorf(
@@ -464,6 +470,13 @@ func (e *Encoder) encodeStruct(value reflect.Value, column int) (ast.Node, error
 				key = ast.MergeKey(token.New("<<", "<<", e.pos(column)))
 			}
 		case structField.IsInline:
+			isAutoAnchor := structField.IsAutoAnchor
+			if !hasInlineAnchorField {
+				hasInlineAnchorField = isAutoAnchor
+			}
+			if isAutoAnchor {
+				inlineAnchorValue = fieldValue
+			}
 			mapNode, ok := value.(ast.MapNode)
 			if !ok {
 				return nil, xerrors.Errorf("inline value is must be map or struct type")
@@ -485,11 +498,38 @@ func (e *Encoder) encodeStruct(value reflect.Value, column int) (ast.Node, error
 				})
 			}
 			continue
+		case structField.IsAutoAnchor:
+			anchorNode, err := e.encodeAnchor(structField.RenderName, value, fieldValue, column)
+			if err != nil {
+				return nil, errors.Wrapf(err, "failed to encode anchor")
+			}
+			value = anchorNode
 		}
 		node.Values = append(node.Values, &ast.MappingValueNode{
 			Key:   key,
 			Value: value,
 		})
 	}
+	if hasInlineAnchorField {
+		node.AddColumn(e.indent)
+		anchorName := "anchor"
+		anchorNode := &ast.AnchorNode{
+			Start: token.New("&", "&", e.pos(column)),
+			Name:  ast.String(token.New(anchorName, anchorName, e.pos(column))),
+			Value: node,
+		}
+		if e.anchorCallback != nil {
+			if err := e.anchorCallback(anchorNode, value.Addr().Interface()); err != nil {
+				return nil, errors.Wrapf(err, "failed to marshal anchor")
+			}
+			if snode, ok := anchorNode.Name.(*ast.StringNode); ok {
+				anchorName = snode.Value
+			}
+		}
+		if inlineAnchorValue.Kind() == reflect.Ptr {
+			e.anchorPtrToNameMap[inlineAnchorValue.Pointer()] = anchorName
+		}
+		return anchorNode, nil
+	}
 	return node, nil
 }
diff --git a/struct.go b/struct.go
index 2c6015d..ac78f31 100644
--- a/struct.go
+++ b/struct.go
@@ -102,6 +102,15 @@ func (m StructFieldMap) isIncludedRenderName(name string) bool {
 	return false
 }
 
+func (m StructFieldMap) hasMergeProperty() bool {
+	for _, v := range m {
+		if v.IsOmitEmpty && v.IsInline && v.IsAutoAlias {
+			return true
+		}
+	}
+	return false
+}
+
 func structFieldMap(structType reflect.Type) (StructFieldMap, error) {
 	structFieldMap := StructFieldMap{}
 	renderNameMap := map[string]struct{}{}


Test patch:
diff --git a/yaml_test.go b/yaml_test.go
index 5444e86..e399374 100644
--- a/yaml_test.go
+++ b/yaml_test.go
@@ -1,9 +1,11 @@
 package yaml_test
 
 import (
+	"bytes"
 	"testing"
 
 	"github.com/goccy/go-yaml"
+	"github.com/goccy/go-yaml/ast"
 	"golang.org/x/xerrors"
 )
 
@@ -206,3 +208,75 @@ b:
 		t.Fatal("failed to UnmarshalYAML")
 	}
 }
+
+type ObjectMap map[string]*Object
+type ObjectDecl struct {
+	Name    string `yaml:"-"`
+	*Object `yaml:",inline,anchor"`
+}
+
+func (m ObjectMap) MarshalYAML() (interface{}, error) {
+	newMap := map[string]*ObjectDecl{}
+	for k, v := range m {
+		newMap[k] = &ObjectDecl{Name: k, Object: v}
+	}
+	return newMap, nil
+}
+
+type rootObject struct {
+	Single     ObjectMap            `yaml:"single"`
+	Collection map[string][]*Object `yaml:"collection"`
+}
+
+type Object struct {
+	*Object  `yaml:",omitempty,inline,alias"`
+	MapValue map[string]interface{} `yaml:",omitempty,inline"`
+}
+
+func TestInlineAnchorAndAlias(t *testing.T) {
+	yml := `---
+single:
+  default: &default
+    id: 1
+    name: john
+  user_1: &user_1
+    id: 1
+    name: ken
+  user_2: &user_2
+    <<: *default
+    id: 2
+collection:
+  defaults:
+  - *default
+  - <<: *default
+  - <<: *default
+    id: 2
+  users:
+  - <<: *user_1
+  - <<: *user_2
+  - <<: *user_1
+    id: 3
+  - <<: *user_1
+    id: 4
+  - <<: *user_1
+    id: 5
+`
+	var v rootObject
+	if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
+		panic(err)
+	}
+	opt := yaml.MarshalAnchor(func(anchor *ast.AnchorNode, value interface{}) error {
+		if o, ok := value.(*ObjectDecl); ok {
+			anchor.Name.(*ast.StringNode).Value = o.Name
+		}
+		return nil
+	})
+	var buf bytes.Buffer
+	if err := yaml.NewEncoder(&buf, opt).Encode(v); err != nil {
+		t.Fatalf("%+v", err)
+	}
+	actual := "---\n" + buf.String()
+	if yml != actual {
+		t.Fatalf("failed to marshal: expected:[%s] actual:[%s]", yml, actual)
+	}
+}