Repository: goccy/go-yaml
Pull request #203 (2021-03-01 04:51:17)

Support to encode/decode ast.Node directly
fix https://github.com/goccy/go-yaml/issues/198

Enable encode/decode of `ast.Node` type's field of structure 


Patch:
diff --git a/ast/ast.go b/ast/ast.go
index f78e6d3..ed5366f 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -125,6 +125,8 @@ type Node interface {
 	SetComment(*token.Token) error
 	// Comment returns comment token instance
 	GetComment() *token.Token
+	// MarshalYAML
+	MarshalYAML() ([]byte, error)
 	// already read length
 	readLen() int
 	// append read length
@@ -513,6 +515,11 @@ func (d *DocumentNode) String() string {
 	return strings.Join(doc, "\n")
 }
 
+// MarshalYAML encodes to a YAML text
+func (d *DocumentNode) MarshalYAML() ([]byte, error) {
+	return []byte(d.String()), nil
+}
+
 func removeUnderScoreFromNumber(num string) string {
 	return strings.ReplaceAll(num, "_", "")
 }
@@ -561,6 +568,11 @@ func (n *NullNode) String() string {
 	return "null"
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *NullNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // IntegerNode type of integer node
 type IntegerNode struct {
 	*BaseNode
@@ -596,6 +608,11 @@ func (n *IntegerNode) String() string {
 	return n.Token.Value
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *IntegerNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // FloatNode type of float node
 type FloatNode struct {
 	*BaseNode
@@ -632,6 +649,11 @@ func (n *FloatNode) String() string {
 	return n.Token.Value
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *FloatNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // StringNode type of string node
 type StringNode struct {
 	*BaseNode
@@ -689,6 +711,11 @@ func (n *StringNode) String() string {
 	return n.Value
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *StringNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // LiteralNode type of literal node
 type LiteralNode struct {
 	*BaseNode
@@ -728,6 +755,11 @@ func (n *LiteralNode) String() string {
 	return fmt.Sprintf("%s\n%s", n.Start.Value, strings.TrimRight(strings.TrimRight(origin, " "), "\n"))
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *LiteralNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // MergeKeyNode type of merge key node
 type MergeKeyNode struct {
 	*BaseNode
@@ -762,6 +794,11 @@ func (n *MergeKeyNode) AddColumn(col int) {
 	n.Token.AddColumn(col)
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *MergeKeyNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // BoolNode type of boolean node
 type BoolNode struct {
 	*BaseNode
@@ -797,6 +834,11 @@ func (n *BoolNode) String() string {
 	return n.Token.Value
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *BoolNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // InfinityNode type of infinity node
 type InfinityNode struct {
 	*BaseNode
@@ -832,6 +874,11 @@ func (n *InfinityNode) String() string {
 	return n.Token.Value
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *InfinityNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // NanNode type of nan node
 type NanNode struct {
 	*BaseNode
@@ -866,6 +913,11 @@ func (n *NanNode) String() string {
 	return n.Token.Value
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *NanNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // MapNode interface of MappingValueNode / MappingNode
 type MapNode interface {
 	MapRange() *MapNodeIter
@@ -1002,6 +1054,11 @@ func (n *MappingNode) MapRange() *MapNodeIter {
 	}
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *MappingNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // MappingKeyNode type of tag node
 type MappingKeyNode struct {
 	*BaseNode
@@ -1035,6 +1092,11 @@ func (n *MappingKeyNode) String() string {
 	return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String())
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *MappingKeyNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // MappingValueNode type of mapping value
 type MappingValueNode struct {
 	*BaseNode
@@ -1116,6 +1178,11 @@ func (n *MappingValueNode) MapRange() *MapNodeIter {
 	}
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *MappingValueNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // ArrayNode interface of SequenceNode
 type ArrayNode interface {
 	ArrayRange() *ArrayNodeIter
@@ -1262,6 +1329,11 @@ func (n *SequenceNode) ArrayRange() *ArrayNodeIter {
 	}
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *SequenceNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // AnchorNode type of anchor node
 type AnchorNode struct {
 	*BaseNode
@@ -1319,6 +1391,11 @@ func (n *AnchorNode) String() string {
 	return fmt.Sprintf("&%s %s", n.Name.String(), value)
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *AnchorNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // AliasNode type of alias node
 type AliasNode struct {
 	*BaseNode
@@ -1364,6 +1441,11 @@ func (n *AliasNode) String() string {
 	return fmt.Sprintf("*%s", n.Value.String())
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *AliasNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // DirectiveNode type of directive node
 type DirectiveNode struct {
 	*BaseNode
@@ -1396,6 +1478,11 @@ func (n *DirectiveNode) String() string {
 	return fmt.Sprintf("%s%s", n.Start.Value, n.Value.String())
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *DirectiveNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // TagNode type of tag node
 type TagNode struct {
 	*BaseNode
@@ -1429,6 +1516,11 @@ func (n *TagNode) String() string {
 	return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String())
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *TagNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // CommentNode type of comment node
 type CommentNode struct {
 	*BaseNode
@@ -1455,6 +1547,11 @@ func (n *CommentNode) String() string {
 	return n.Comment.Value
 }
 
+// MarshalYAML encodes to a YAML text
+func (n *CommentNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // Visitor has Visit method that is invokded for each node encountered by Walk.
 // If the result visitor w is not nil, Walk visits each of the children of node with the visitor w,
 // followed by a call of w.Visit(nil).
diff --git a/decode.go b/decode.go
index 4c9fff1..5246953 100644
--- a/decode.go
+++ b/decode.go
@@ -595,6 +595,10 @@ func (d *Decoder) decodeByUnmarshaler(ctx context.Context, dst reflect.Value, sr
 	return xerrors.Errorf("does not implemented Unmarshaler")
 }
 
+var (
+	astNodeType = reflect.TypeOf((*ast.Node)(nil)).Elem()
+)
+
 func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.Node) error {
 	if src.Type() == ast.AnchorType {
 		anchorName := src.(*ast.AnchorNode).Name.GetToken().Value
@@ -625,6 +629,10 @@ func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.No
 		}
 		dst.Set(d.castToAssignableValue(v, dst.Type()))
 	case reflect.Interface:
+		if dst.Type() == astNodeType {
+			dst.Set(reflect.ValueOf(src))
+			return nil
+		}
 		v := reflect.ValueOf(d.nodeToValue(src))
 		if v.IsValid() {
 			dst.Set(v)
@@ -897,6 +905,17 @@ func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.N
 		return nil
 	}
 	structType := dst.Type()
+	srcValue := reflect.ValueOf(src)
+	srcType := srcValue.Type()
+	if srcType.Kind() == reflect.Ptr {
+		srcType = srcType.Elem()
+		srcValue = srcValue.Elem()
+	}
+	if structType == srcType {
+		// dst value implements ast.Node
+		dst.Set(srcValue)
+		return nil
+	}
 	structFieldMap, err := structFieldMap(structType)
 	if err != nil {
 		return errors.Wrapf(err, "failed to create struct field map")


Test patch:
diff --git a/decode_test.go b/decode_test.go
index e211532..9ea9ce2 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -15,6 +15,7 @@ import (
 	"time"
 
 	"github.com/goccy/go-yaml"
+	"github.com/goccy/go-yaml/ast"
 	"github.com/goccy/go-yaml/parser"
 	"golang.org/x/xerrors"
 )
@@ -2325,3 +2326,34 @@ c: d
 		t.Fatal("failed to decode from ast.File")
 	}
 }
+
+func TestDecoder_DecodeWithNode(t *testing.T) {
+	t.Run("abstract node", func(t *testing.T) {
+		type T struct {
+			Text ast.Node `yaml:"text"`
+		}
+		var v T
+		if err := yaml.Unmarshal([]byte(`text: hello`), &v); err != nil {
+			t.Fatalf("%+v", err)
+		}
+		expected := "hello"
+		got := v.Text.String()
+		if expected != got {
+			t.Fatalf("failed to decode to ast.Node: expected %s but got %s", expected, got)
+		}
+	})
+	t.Run("concrete node", func(t *testing.T) {
+		type T struct {
+			Text *ast.StringNode `yaml:"text"`
+		}
+		var v T
+		if err := yaml.Unmarshal([]byte(`text: hello`), &v); err != nil {
+			t.Fatalf("%+v", err)
+		}
+		expected := "hello"
+		got := v.Text.String()
+		if expected != got {
+			t.Fatalf("failed to decode to ast.Node: expected %s but got %s", expected, got)
+		}
+	})
+}
diff --git a/encode_test.go b/encode_test.go
index 53a306f..db795ba 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -1014,6 +1014,23 @@ a:
 	}
 }
 
+func Example_Marshal_Node() {
+	type T struct {
+		Text ast.Node `yaml:"text"`
+	}
+	stringNode, err := yaml.ValueToNode("node example")
+	if err != nil {
+		panic(err)
+	}
+	bytes, err := yaml.Marshal(T{Text: stringNode})
+	if err != nil {
+		panic(err)
+	}
+	fmt.Println(string(bytes))
+	// OUTPUT:
+	// text: node example
+}
+
 func Example_Marshal_ExplicitAnchorAlias() {
 	type T struct {
 		A int