gostructmarshallinggo-structtag

How to marshal struct as if it were an anonymous struct?


The documentation states:

Anonymous struct fields are usually marshaled as if their inner exported fields were fields in the outer struct.

For examble:

type foo struct {
    Foo1 string `json:"foo1"`
    Foo2 string `json:"foo2"`
}

type boo struct {
    Boo1 string `json:"boo1"`
    foo
}

and I do this:

s := boo{
    Boo: "boo1",
    foo: foo{
        Foo1: "foo1",
        Foo2: "foo2",
    },
}

b, err := json.MarshalIndent(s, "", "   ")
fmt.Println(string(b))

I get this:

{
    "boo1": "boo1",
    "foo1": "foo1",
    "foo2": "foo2"
}

But how can I achieve the same result when the foo is Not an anonymous struct? Meaning:

type boo struct {
    Boo string `json:"boo"`
    Foo foo
}

And also unmarshaling the json.


Solution

  • You have to implement a custom json.Marshaler for that.

    type boo struct {
        Boo1 string `json:"boo1"`
        // "-" will tell encoding/json to ignore this field during (un)marshaling
        Foo foo `json:"-"`
    }
    
    func (b boo) MarshalJSON() ([]byte, error) {
        // Declare a new type using boo's definition, this
        // "copies" boo's structure but not its behaviour,
        // i.e. B has same fields as boo, but zero methods,
        // not even MarshalJSON -- this is necessary to avoid
        // infinite recursive calls to MarshalJSON.
        type B boo
    
        // Declare a new type that *embeds* those structs whose
        // fields you want to be at the same level.
        type T struct {
            B
            foo
        }
    
        // Create an instance of the new type with its fields
        // set from the source boo instance and marshal it.
        return json.Marshal(T{B: B(b), foo: b.Foo})
    }
    

    https://play.golang.org/p/Go1w9quPkMa


    A note on "anonymous"

    The label anonymous as you're using it, and as it is used in the documentation you quoted, is outdated and imprecise. The proper label for a field without an explicit name is embedded.

    https://golang.org/ref/spec#Struct_types

    A field declared with a type but no explicit field name is called an embedded field. An embedded field must be specified as a type name T or as a pointer to a non-interface type name *T, and T itself may not be a pointer type. The unqualified type name acts as the field name.

    The distinction is important because there is such a thing as "anonymous struct fields" in Go, it's used quite often but it is different from embedded fields. For example:

    type T struct {
        // F is an anonymous struct field, or
        // a field of an anonymous struct type.
        // F is not embedded however.
        F struct {
            foo string
            bar int
        }
    }