基于类型键解组动态 JSON

Iva*_*ras 2 json go

我在寻找一种不涉及引入一个额外的“通用”字段的溶液等Value,Data等这将是该变种领域的占位符。

我有一个 JSON 规范,它描述了几个大型结构,其中大部分包含简单的值,但偶尔也会包含一个结构本身的值,其动态类型取决于某个字段的值。

例如,这两个 JSON 文档都应该解组为同一个 Go 结构:

{ 
  "some_data": "foo",
  "dynamic_field": { "type": "A", "name": "Johnny" },
  "other_data": "bar"
}
Run Code Online (Sandbox Code Playgroud)

和

{
  "some_data": "foo",
  "dynamic_field": { "type": "B", "address": "Somewhere" },
  "other_data": "bar"
}
Run Code Online (Sandbox Code Playgroud)

JSON 结构已设置,我无法更改它。

Go 结构体必须如下所示:

type BigStruct struct {
  SomeData     string    `json:"some_data"`
  DynamicField Something `json:"dynamic_field"`
  OtherData    string    `json:"other_data"`
}
Run Code Online (Sandbox Code Playgroud)

问题是如何真正做到这一点以及那种Something类型应该是什么。

我已经开始使它成为一个界面:

type Something interface {
  GetType() string
}
Run Code Online (Sandbox Code Playgroud)

并有几个结构体和函数与之配合:

type BaseDynamicType struct {
  Type string `json:"type"`
}

type DynamicTypeA struct {
  BaseDynamicType
  Name string `json:"name"`
}

type DynamicTypeB struct {
  BaseDynamicType
  Address string `json:"address"`
}

func (d *BaseDynamicType) GetType() string {
  return d.Type
}
Run Code Online (Sandbox Code Playgroud)

原因是,当我获得 的实例时BigStruct,我可以这样做:

switch big.DynamicField.GetType() {
  case "A": // do something with big.DynamicField cast to DynamicTypeA
  case "B": // do something with big.DynamicField cast to DynamicTypeB
}
Run Code Online (Sandbox Code Playgroud)

但是,然后我被卡住了 - 这种安排如何适用UnmarshalJSON?我认为BigStruct应该实现UnmarshalJSON它会以某种方式检查 的Type领域,dynamic_field然后基于它,制作DynamicFieldaDynamicTypeA或DynamicTypeB.

但是如何?由于递归而可能不起作用的一种方法是:

  • 标记DynamicField为json:"-"
  • UnmarshalJSON为 BigStruct实施
  • 解组JSON成map[string]interface{}在BigStruct的UnmarshalJSON,
  • 检查dynamic_field地图中的值,手动构建DynamicTypeA或DynamicTypeB
  • 再次将相同的数据解组为 BigStruct
  • DynamicField使用手动创建的值修复

...但是当我尝试将数据解组为BigStruct将调用UnmarshalJSON当前正在执行的相同函数时,这将导致第 5 步中的无限递归。

mko*_*iva 8

type BigStruct struct {
    SomeData     string      `json:"some_data"`
    DynamicField DynamicType `json:"dynamic_field"`
    OtherData    string      `json:"other_data"`
}

type DynamicType struct {
    Value interface{}
}

func (d *DynamicType) UnmarshalJSON(data []byte) error {
    var typ struct {
        Type string `json:"type"`
    }
    if err := json.Unmarshal(data, &typ); err != nil {
        return err
    }
    switch typ.Type {
    case "A":
        d.Value = new(TypeA)
    case "B":
        d.Value = new(TypeB)
    }
    return json.Unmarshal(data, d.Value)

}

type TypeA struct {
    Name string `json:"name"`
}

type TypeB struct {
    Address string `json:"address"`
}
Run Code Online (Sandbox Code Playgroud)

https://play.golang.com/p/oKMKQTdzp7s


如果您不想或不能更改 DynamicField 的类型,您可以将 UnmarshalJSON 方法放在 BigStruct 上并声明一个临时类型以避免递归。

func (b *BigStruct) UnmarshalJSON(data []byte) error {
    var typ struct {
        DF struct {
            Type string `json:"type"`
        } `json:"dynamic_field"`
    }
    if err := json.Unmarshal(data, &typ); err != nil {
        return err
    }

    switch typ.DF.Type {
    case "A":
        b.DynamicField = new(DynamicTypeA)
    case "B":
        b.DynamicField = new(DynamicTypeB)
    }

    type tmp BigStruct // avoids infinite recursion
    return json.Unmarshal(data, (*tmp)(b))
}
Run Code Online (Sandbox Code Playgroud)

https://play.golang.com/p/at5Okp3VU2u

  • @IvanVoras 我通过添加更符合您要求的解决方案来扩展答案。 (2认同)