Golang中递归数据模型的JSON序列化/反序列化

3
假设我有以下内容:
type IObject interface {
}

type Item struct {
    Description string
    Data        []byte
}

type FunctionX1 struct {
    Object IInclusionObject
}

type FunctionX2 struct {
    Object1 IInclusionObject
    Object2 IInclusionObject
}

我希望能够对一个模型进行序列化/反序列化,其中 ItemFunctionX1FunctionX2 都实现了 IObject 接口,并且它们可以任意深度地相互引用。
请注意,我不希望 FunctionX1{Item{"foo", []byte("bar")}} 被序列化为:
"object": {
    "Description": "foo"
    "Data": ...
}

而不是:

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

我需要自己编写JSON marshaller吗?似乎无法使用现有的marshaller。

如果需要,以下是相关问题。是否有JSON prettifier可以流式处理有效但随机格式的JSON,并将其输出为漂亮的版本(请注意,JSON可能非常大 - 我不想生成、解析和生成格式化的JSON)。

1个回答

2

使用包装器(结构体或映射)

您可以使用结构体标记将结构体字段映射到JSON中的不同名称。因此,通过这种方式,您可以将"Object"更改为"item"

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

但是如果你想让"FunctionX1"出现在JSON文本中,仍然需要一个包装结构体或映射。例如:

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))
}

输出:

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

或者使用包装结构体:
(注:该句为代码注释,无需翻译)
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))
}

输出结果相同:

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

如果您想要格式化漂亮的JSON,可以使用json.MarshalIndent()进行编组:
if data, err := json.MarshalIndent(Wrapper{f}, "", "  "); err != nil {
    panic(err)
} else {
    fmt.Println(string(data))
}

输出:

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

请在Go Playground上尝试所有示例。

使用自定义编组

如果您不想使用包装结构体或映射,您需要使用自定义编组,但它非常简单:

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)})
}

我们有效地将包装移到了MarshalJSON()方法中,这样其他对FunctionX1值进行编组的人就不必再进行包装了。

测试:

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))
}

请注意,在MarshalJSON()函数内部的新FunctionX1_类型是为了避免无限“递归”。
输出:
{"FunctionX1":{"item":{"Description":"foo","Data":"YmFy"}}}
{
  "FunctionX1": {
    "item": {
      "Description": "foo",
      "Data": "YmFy"
    }
  }
}

Go Playground中尝试这个。


谢谢,@icza。自定义编组似乎非常接近我想要的,只有一个例外。作为FinctionX1参数的对象可能不是一个项目,而是另一个FunctionX1或FunctionX2,在这种情况下,子项不应被称为项目。我尝试将相同的自定义编组器应用于项目,但在那里我得到了一个额外的级别:https://play.golang.org/p/Op2tg1LOtZ。有什么想法可以摆脱它吗? - gsf
其实,我想我已经弄清楚了 https://play.golang.org/p/6lVxnjnyit - gsf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接