dtnat7146 2018-11-23 23:14
浏览 30
已采纳

基于类型密钥解组动态JSON

I'm looking for a solution which DOESN'T involve introducing an additional "generic" field like Value, Data, etc. which would be a placeholder for the variant field.

I have a JSON spec which describes several large structs, which hold mostly simple values, but occasionally a value which is a struct itself, with a dynamic type depending on the value of a certain field.

For example, both these JSON documents should unmarshal to the same Go struct:

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

and

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

The JSON structure is set, I can't change it.

The Go struct must look like this:

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

The question is how to actually do it and what that Something type should be.

I've started by making it an interface:

type Something interface {
  GetType() string
}

And have several structs and funcs to go with it:

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
}

The reason is, that when I get an instance of BigStruct, I can do this:

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

However, then I got stuck - how could this arrangement work with UnmarshalJSON? I think that BigStruct should implement UnmarshalJSON which would somehow inspect the Type field of the dynamic_field and then based on it, make DynamicField either a DynamicTypeA or DynamicTypeB.

But how? One way which probably doesn't work because of recursion would be:

  • Mark DynamicField as json:"-"
  • Implement UnmarshalJSON for BigStruct
  • unmarshal the JSON into a map[string]interface{} in the BigStruct's UnmarshalJSON,
  • inspect the dynamic_field value in the map, manually construct either DynamicTypeA or DynamicTypeB
  • unmarshal the same data again into BigStruct
  • fixup the DynamicField with the manually created values

... but that will lead to infinite recursion in the 5th step when I try to unmarshal the data into a BigStruct which would call the same UnmarshalJSON function which is currently executing.

  • 写回答

1条回答 默认 最新

  • douzhantanju1849 2018-11-24 00:37
    关注
    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


    If you don't want to, or can't, change the type of the DynamicField you can put the UnmarshalJSON method on the BigStruct and declare a temporary type to avoid recursion.

    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

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 SQL Server下载
  • ¥15 python如何将动态的多个子列表,拼接后进行集合的交集
  • ¥20 vitis-ai量化基于pytorch框架下的yolov5模型
  • ¥15 如何实现H5在QQ平台上的二次分享卡片效果?
  • ¥15 python爬取bilibili校园招聘网站
  • ¥30 求解达问题(有红包)
  • ¥15 请解包一个pak文件
  • ¥15 不同系统编译兼容问题
  • ¥100 三相直流充电模块对数字电源芯片在物理上它必须具备哪些功能和性能?
  • ¥30 数字电源对DSP芯片的具体要求