Skip to content

Commit

Permalink
Zero yaml.Node values behave as nil (go-yaml#545)
Browse files Browse the repository at this point in the history
  • Loading branch information
niemeyer committed Jun 3, 2020
1 parent 3e3e88c commit e307989
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 10 deletions.
29 changes: 19 additions & 10 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ func (d *decoder) callObsoleteUnmarshaler(n *Node, u obsoleteUnmarshaler) (good
//
// If n holds a null value, prepare returns before doing anything.
func (d *decoder) prepare(n *Node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) {
if n.ShortTag() == nullTag {
if n.ShortTag() == nullTag || n.Kind == 0 && n.IsZero() {
return out, false, false
}
again := true
Expand Down Expand Up @@ -505,8 +505,13 @@ func (d *decoder) unmarshal(n *Node, out reflect.Value) (good bool) {
good = d.mapping(n, out)
case SequenceNode:
good = d.sequence(n, out)
case 0:
if n.IsZero() {
return d.null(out)
}
fallthrough
default:
panic("internal error: unknown node kind: " + strconv.Itoa(int(n.Kind)))
failf("cannot decode node with unknown kind %d", n.Kind)
}
return good
}
Expand Down Expand Up @@ -541,6 +546,17 @@ func resetMap(out reflect.Value) {
}
}

func (d *decoder) null(out reflect.Value) bool {
if out.CanAddr() {
switch out.Kind() {
case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
out.Set(reflect.Zero(out.Type()))
return true
}
}
return false
}

func (d *decoder) scalar(n *Node, out reflect.Value) bool {
var tag string
var resolved interface{}
Expand All @@ -558,14 +574,7 @@ func (d *decoder) scalar(n *Node, out reflect.Value) bool {
}
}
if resolved == nil {
if out.CanAddr() {
switch out.Kind() {
case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
out.Set(reflect.Zero(out.Type()))
return true
}
}
return false
return d.null(out)
}
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
// We've resolved to exactly the type we want, so use that.
Expand Down
8 changes: 8 additions & 0 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,12 @@ func (e *encoder) nodev(in reflect.Value) {
}

func (e *encoder) node(node *Node, tail string) {
// Zero nodes behave as nil.
if node.Kind == 0 && node.IsZero() {
e.nilv()
return
}

// If the tag was not explicitly requested, and dropping it won't change the
// implicit tag of the value, don't include it in the presentation.
var tag = node.Tag
Expand Down Expand Up @@ -560,5 +566,7 @@ func (e *encoder) node(node *Node, tail string) {
}

e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail))
default:
failf("cannot encode node with unknown kind %d", node.Kind)
}
}
37 changes: 37 additions & 0 deletions node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ var nodeTests = []struct {
Column: 1,
}},
},
}, {
"[encode]null\n",
yaml.Node{},
}, {
"foo\n",
yaml.Node{
Expand Down Expand Up @@ -2497,6 +2500,40 @@ func (s *S) TestNodeEncodeDecode(c *C) {
}
}

func (s *S) TestNodeZeroEncodeDecode(c *C) {
// Zero node value behaves as nil when encoding...
var n yaml.Node
data, err := yaml.Marshal(&n)
c.Assert(err, IsNil)
c.Assert(string(data), Equals, "null\n")

// ... and decoding.
var v *struct{} = &struct{}{}
c.Assert(n.Decode(&v), IsNil)
c.Assert(v, IsNil)

// Kind zero is still unknown, though.
n.Line = 1
_, err = yaml.Marshal(&n)
c.Assert(err, ErrorMatches, "yaml: cannot encode node with unknown kind 0")
c.Assert(n.Decode(&v), ErrorMatches, "yaml: cannot decode node with unknown kind 0")
}

func (s *S) TestNodeOmitEmpty(c *C) {
var v struct {
A int
B yaml.Node ",omitempty"
}
v.A = 1
data, err := yaml.Marshal(&v)
c.Assert(err, IsNil)
c.Assert(string(data), Equals, "a: 1\n")

v.B.Line = 1
_, err = yaml.Marshal(&v)
c.Assert(err, ErrorMatches, "yaml: cannot encode node with unknown kind 0")
}

func fprintComments(out io.Writer, node *yaml.Node, indent string) {
switch node.Kind {
case yaml.ScalarNode:
Expand Down
7 changes: 7 additions & 0 deletions yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,13 @@ type Node struct {
Column int
}

// IsZero returns whether the node has all of its fields unset.
func (n *Node) IsZero() bool {
return n.Kind == 0 && n.Style == 0 && n.Tag == "" && n.Value == "" && n.Anchor == "" && n.Alias == nil && n.Content == nil &&
n.HeadComment == "" && n.LineComment == "" && n.FootComment == "" && n.Line == 0 && n.Column == 0
}


// LongTag returns the long form of the tag that indicates the data type for
// the node. If the Tag field isn't explicitly defined, one will be computed
// based on the node properties.
Expand Down

0 comments on commit e307989

Please sign in to comment.