go-yaml / yaml

YAML support for the Go language.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

MarshalText interface is ignored on nested structs

TheFriendlyCoder opened this issue · comments

Given the following example structs:

type MyChild struct {
	nested   string
}

func (c *MyChild) MarshalText() ([]byte, error) {
	return []byte(c.nested), nil
}

type MyParent struct {
	Child MyChild
}

If you attempt to marshal an instance of MyParent to YAML by doing something like this:

c := MyChild{
	nested: "Hello",
}
p := MyParent{
	Child: c,
}

data, _ := yaml.Marshal(&p)
fmt.Printf("%+v\n", string(data))

You get the following output:

child: {}

Instead of the expected output of:

child:
    nested: Hello

If, however, you attempt to marshal and instance of the MyChild struct directly, as in:

data, _ := yaml.Marshal(&c)
fmt.Printf("%+v\n", string(data))

You get the expected output:

Hello

NOTE: It appears as though the MarshalYAML() interface is also ignored on child / nested structs as well, as in the following example:

func (c *MyChild) MarshalYAML() (interface{}, error) {
	return []byte(c.nested), nil
}

I did a bit more ad-hoc testing on this issue, and the problem seems to be related to whether the marshaller method takes a pointer or reference receiver. Using the sample code from above, if I have a struct that contains a reference to a value object, then the marshaller method needs to have a reference receiver:

type MyChild struct {
	nested   string
}

func (c MyChild) MarshalText() ([]byte, error) {       // <----- notice the reference receiver "c MyChild"
	return []byte(c.nested), nil
}

type MyParent struct {
	Child MyChild                   // <----- notice the reference value here
}

Similarly, if I have a struct that contains a pointer to a value instead of a reference, the marshaller interface must use a pointer-receiver as in:

type MyChild struct {
	nested   string
}

func (c *MyChild) MarshalText() ([]byte, error) {           // <----- notice the pointer receiver here "c *MyChild"
	return []byte(c.nested), nil
}

type MyParent struct {
	Child *MyChild          // <------- notice the pointer to the value here
}

The issue then becomes, how can one implement a marshal implementation for a structure so as to allow both reference and pointer values to be serialized properly?

I did a bit more ad-hoc testing on this issue, and the problem seems to be related to whether the marshaller method takes a pointer or reference receiver. Using the sample code from above, if I have a struct that contains a reference to a value object, then the marshaller method needs to have a reference receiver:

type MyChild struct {
	nested   string
}

func (c MyChild) MarshalText() ([]byte, error) {       // <----- notice the reference receiver "c MyChild"
	return []byte(c.nested), nil
}

type MyParent struct {
	Child MyChild                   // <----- notice the reference value here
}

Similarly, if I have a struct that contains a pointer to a value instead of a reference, the marshaller interface must use a pointer-receiver as in:

type MyChild struct {
	nested   string
}

func (c *MyChild) MarshalText() ([]byte, error) {           // <----- notice the pointer receiver here "c *MyChild"
	return []byte(c.nested), nil
}

type MyParent struct {
	Child *MyChild          // <------- notice the pointer to the value here
}

The issue then becomes, how can one implement a marshal implementation for a structure so as to allow both reference and pointer values to be serialized properly?

I can confirm this is the case