Skip to content

Commit

Permalink
Merge pull request #87 from goccy/feature/complexity-reference
Browse files Browse the repository at this point in the history
Support complexity encoding/decoding with inline and anchor and alias
  • Loading branch information
goccy authored Mar 3, 2020
2 parents 3b03e52 + 13494ad commit 8a9d252
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 18 deletions.
9 changes: 8 additions & 1 deletion ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
63 changes: 53 additions & 10 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
Expand All @@ -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")
}
Expand All @@ -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")
}
Expand Down Expand Up @@ -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
Expand All @@ -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++ {
Expand All @@ -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)
}
Expand All @@ -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) {
Expand Down
54 changes: 47 additions & 7 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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")
Expand All @@ -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
}
9 changes: 9 additions & 0 deletions struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}{}
Expand Down
74 changes: 74 additions & 0 deletions yaml_test.go
Original file line number Diff line number Diff line change
@@ -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"
)

Expand Down Expand Up @@ -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)
}
}

0 comments on commit 8a9d252

Please sign in to comment.