【问题标题】:Unmarshal dynamic JSON based on a type key基于类型键解组动态 JSON
【发布时间】:2019-03-18 15:05:34
【问题描述】:

我正在寻找一种解决方案,它不涉及引入额外的“通用”字段,如 ValueData 等,这将是变量字段的占位符。

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

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

{ 
  "some_data": "foo",
  "dynamic_field": { "type": "A", "name": "Johnny" },
  "other_data": "bar"
}

{
  "some_data": "foo",
  "dynamic_field": { "type": "B", "address": "Somewhere" },
  "other_data": "bar"
}

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

Go 结构必须如下所示:

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

问题是如何实际操作以及Something 类型应该是什么。

我已经开始把它变成一个界面了:

type Something interface {
  GetType() string
}

并且有几个结构和函数与之配套:

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
}

原因是,当我得到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
}

但是,后来我陷入了困境 - 这种安排如何与 UnmarshalJSON 一起使用?我认为BigStruct 应该实现UnmarshalJSON,它会以某种方式检查dynamic_fieldType 字段,然后基于它,使DynamicField 成为DynamicTypeADynamicTypeB

但是怎么做呢?由于递归而可能不起作用的一种方法是:

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

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

【问题讨论】:

    标签: json go


    【解决方案1】:
    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"`
    }
    

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

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

    【讨论】:

    • 谢谢,我也找到了那个变种,但是它需要修改结构来创建不在原始结构中的Value 字段,所以我不能使用这种方法。
    • @IvanVoras 然后像您在问题中概述的那样将 UnmarshalJSON 放在 BigStruct 上,为了避免递归问题,您可以在 UnmarshalJSON 中声明一个新的临时类型,然后将其转换为 BigStruct。
    • @IvanVoras 我通过添加更符合您要求的解决方案扩展了答案。
    • 酷,这是一个不错的方法!谢谢!
    • @rickydj 调用该方法的是json.Unmarshal。文档说:“要将 JSON 解组为实现 Unmarshaler 接口的值,Unmarshal 调用该值的 UnmarshalJSON 方法,包括输入为 JSON null 时。”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-15
    • 2021-05-18
    • 2020-06-14
    • 2018-05-19
    • 1970-01-01
    相关资源
    最近更新 更多