Repository: goccy/go-yaml
Pull request #236 (2021-07-19 12:10:46)

Add yaml.NodeToValue(ast.Node, interface{}, ...DecodeOption) error
`go-yaml` can convert a value to an AST node by `ValueToNode`. This PR adds the opposite function `NodeToValue`.


Patch:
diff --git a/decode.go b/decode.go
index f3e583a..bf17737 100644
--- a/decode.go
+++ b/decode.go
@@ -1489,3 +1489,25 @@ func (d *Decoder) DecodeContext(ctx context.Context, v interface{}) error {
 	}
 	return nil
 }
+
+// DecodeFromNode decodes node into the value pointed to by v.
+func (d *Decoder) DecodeFromNode(node ast.Node, v interface{}) error {
+	return d.DecodeFromNodeContext(context.Background(), node, v)
+}
+
+// DecodeFromNodeContext decodes node into the value pointed to by v with context.Context.
+func (d *Decoder) DecodeFromNodeContext(ctx context.Context, node ast.Node, v interface{}) error {
+	rv := reflect.ValueOf(v)
+	if rv.Type().Kind() != reflect.Ptr {
+		return errors.ErrDecodeRequiredPointerType
+	}
+	if !d.isInitialized() {
+		if err := d.decodeInit(); err != nil {
+			return errors.Wrapf(err, "failed to decodInit")
+		}
+	}
+	if err := d.decodeValue(ctx, rv.Elem(), node); err != nil {
+		return errors.Wrapf(err, "failed to decode value")
+	}
+	return nil
+}
diff --git a/yaml.go b/yaml.go
index ad404cf..2f626db 100644
--- a/yaml.go
+++ b/yaml.go
@@ -197,6 +197,15 @@ func UnmarshalWithContext(ctx context.Context, data []byte, v interface{}, opts
 	return nil
 }
 
+// NodeToValue converts node to the value pointed to by v.
+func NodeToValue(node ast.Node, v interface{}, opts ...DecodeOption) error {
+	var buf bytes.Buffer
+	if err := NewDecoder(&buf, opts...).DecodeFromNode(node, v); err != nil {
+		return errors.Wrapf(err, "failed to convert node to value")
+	}
+	return nil
+}
+
 // FormatError is a utility function that takes advantage of the metadata
 // stored in the errors returned by this package's parser.
 //


Test patch:
diff --git a/decode_test.go b/decode_test.go
index 7d5960f..79d7d49 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -16,6 +16,7 @@ import (
 
 	"github.com/goccy/go-yaml"
 	"github.com/goccy/go-yaml/ast"
+	"github.com/goccy/go-yaml/internal/errors"
 	"github.com/goccy/go-yaml/parser"
 	"golang.org/x/xerrors"
 )
@@ -1716,6 +1717,40 @@ func Test_UnmarshalerWithContext(t *testing.T) {
 	}
 }
 
+func TestDecoder_DecodeFromNode(t *testing.T) {
+	t.Run("with reference", func(t *testing.T) {
+		anchor := strings.NewReader(`
+map: &map
+  text: hello`)
+		var buf bytes.Buffer
+		dec := yaml.NewDecoder(&buf, yaml.ReferenceReaders(anchor))
+		f, err := parser.ParseBytes([]byte("map: *map"), 0)
+		if err != nil {
+			t.Fatalf("failed to parse: %s", err)
+		}
+		type T struct {
+			Map map[string]string
+		}
+		var v T
+		if err := dec.DecodeFromNode(f.Docs[0].Body, &v); err != nil {
+			t.Fatalf("failed to decode: %s", err)
+		}
+		actual := fmt.Sprintf("%+v", v)
+		expect := fmt.Sprintf("%+v", T{map[string]string{"text": "hello"}})
+		if actual != expect {
+			t.Fatalf("actual=[%s], expect=[%s]", actual, expect)
+		}
+	})
+	t.Run("value is not pointer", func(t *testing.T) {
+		var buf bytes.Buffer
+		var v bool
+		err := yaml.NewDecoder(&buf).DecodeFromNode(nil, v)
+		if !xerrors.Is(err, errors.ErrDecodeRequiredPointerType) {
+			t.Fatalf("unexpected error: %s", err)
+		}
+	})
+}
+
 func Example_JSONTags() {
 	yml := `---
 foo: 1
@@ -1756,6 +1791,22 @@ complecated: string
 	//            ^
 }
 
+func Example_Unmarshal_Node() {
+	f, err := parser.ParseBytes([]byte("text: node example"), 0)
+	if err != nil {
+		panic(err)
+	}
+	var v struct {
+		Text string `yaml:"text"`
+	}
+	if err := yaml.NodeToValue(f.Docs[0].Body, &v); err != nil {
+		panic(err)
+	}
+	fmt.Println(v.Text)
+	// OUTPUT:
+	// node example
+}
+
 type unmarshalableYAMLStringValue string
 
 func (v *unmarshalableYAMLStringValue) UnmarshalYAML(b []byte) error {