golang json递归数据模型的序列化/反序列化

Lets say I have:

type IObject interface {
}

type Item struct {
    Description string
    Data        []byte
}

type FunctionX1 struct {
    Object IInclusionObject
}

type FunctionX2 struct {
    Object1 IInclusionObject
    Object2 IInclusionObject
}

I would like to be able to serialization/deserialization a model, where Item, FunctionX1 FunctionX2 they all implement IObject and they could be pointing to each other arbitrarily deep.

Note that I do not want: FunctionX1{Item{"foo", []byte("bar")}} to be serialized as:

"object": {
    "Description": "foo"
    "Data": ...
}

but rather:

"FunctionX1": {
    "item": { 
        "Description": "foo"
        "Data": ...
    }
}

Do I need to do my own JSON marshaller - it does not seem that I can use the existing one.

Related questions in case I need my own. Is there JSON prettifier where I can stream valid but randomly formatted JSON and it to output pretty version (note the JSON could be significantly large - I do not want to generate, parse generate formatted).

With Wrappers (struct or map)

You may use struct tags to map fields of structs to different names in/from JSON. So with this you can change "Object" to "item":

type FunctionX1 struct {
    Object IInclusionObject `json:"item"`
}

But you still need a wrapper struct or map if you want "FunctionX1" to appear in the JSON text. For example:

f := FunctionX1{Item{"foo", []byte("bar")}}

if data, err := json.Marshal(map[string]interface{}{"FunctionX1": f}); err != nil {
    panic(err)
} else {
    fmt.Println(string(data))
}

Output:

{"FunctionX1":{"item":{"Description":"foo","Data":"YmFy"}}}

Or with a wrapper struct:

type Wrapper struct {
    FunctionX1 FunctionX1
}

f := FunctionX1{Item{"foo", []byte("bar")}}

if data, err := json.Marshal(Wrapper{f}); err != nil {
    panic(err)
} else {
    fmt.Println(string(data))
}

Output is the same:

{"FunctionX1":{"item":{"Description":"foo","Data":"YmFy"}}}

If you want pretty formatted JSON, you may use json.MarshalIndent() to do the marshaling:

if data, err := json.MarshalIndent(Wrapper{f}, "", "  "); err != nil {
    panic(err)
} else {
    fmt.Println(string(data))
}

Output:

{
  "FunctionX1": {
    "item": {
      "Description": "foo",
      "Data": "YmFy"
    }
  }
}

Try all the examples on the Go Playground.

With Custom Marshaling

If you don't want to utilize a wrapper struct or map, you need to use custom marshalling, but it's quite simple:

type FunctionX1 struct {
    Object IInclusionObject `json:"item"`
}

func (f FunctionX1) MarshalJSON() ([]byte, error) {
    type FunctionX1_ FunctionX1
    return json.Marshal(map[string]interface{}{"FunctionX1": FunctionX1_(f)})
}

Effectively we moved the wrapping into the MarshalJSON() method, so others marshaling values of FunctionX1 don't have to.

Testing it:

f := FunctionX1{Item{"foo", []byte("bar")}}

if data, err := json.Marshal(f); err != nil {
    panic(err)
} else {
    fmt.Println(string(data))
}

if data, err := json.MarshalIndent(f, "", "  "); err != nil {
    panic(err)
} else {
    fmt.Println(string(data))
}

Note that the new FunctionX1_ type inside MarshalJSON() is to avoid infinite "recursion".

Output:

{"FunctionX1":{"item":{"Description":"foo","Data":"YmFy"}}}
{
  "FunctionX1": {
    "item": {
      "Description": "foo",
      "Data": "YmFy"
    }
  }
}

Try this one on the Go Playground.