使用自定义解组器处理嵌套JSON结构

使用自定义解组器处理嵌套JSON结构,json,go,Json,Go,我处理的是一个遗留系统,它返回带有嵌套结构和一些可选字段(并且以随机顺序)的JSON。大概是这样的: type A struct { /* simple struct, can be unmarshalled normally */ AF1 string `json:"AF1"` } type B struct { /* simple struct, can be unmarshalled normally */ BF1 string `json:"BF1"`

我处理的是一个遗留系统,它返回带有嵌套结构和一些可选字段(并且以随机顺序)的JSON。大概是这样的:

type A struct {
    /* simple struct, can be unmarshalled normally */
    AF1 string `json:"AF1"`
}

type B struct {
    /* simple struct, can be unmarshalled normally */
    BF1 string `json:"BF1"`
}

type X struct {
    Things []A `json:"things"` /* mandatory */
    Thangs []B `json:"thangs"` /* mandatory */
    /* some individual string values may or may not appear, eg:
    Item1 string
    Item2 string
    */         
}
如果项目[12]确实出现,我想把它们藏在地图或类似的地方


有没有什么优雅的方法可以解开X?有没有办法为X编写一个自定义的UnmarshalJSON func(用于处理选项字符串字段),然后为a和B编写一个默认的JSON unmarshaller

如果我从你的补充评论中正确理解了这个问题, 然后输入可能包含任何具有未知名称(和类型?)的任意额外字段 您想要/需要访问这些。 如果只是为了以后的重新编组,那么
json.RawMessage
类型将很有意义

理想情况下,
encoding/json
会有一个特殊的标记 (与
”类似,任何“
编码/xml
标记) 这将自动将任何额外/未引用的JSON项收集到
map[string]接口{}
map[string]json.RawMessage
字段。 然而,我找不到任何这样的特性,也找不出一种明显的方法来用匿名结构来模拟它(但我没有尽力)

编辑:有。显然,在Go 1.2前后提交了一个变更并进行了部分审查,但最终没有被接受

如果不能做到这一点,有几种方法可以完全按照你的建议去做, 为X创建自定义(un)封送拆收器,并回调到json包中以处理
[]A
[]B

下面是一个简单的例子, 可能有更好/更清晰/更安全的方法来做到这一点。 (在本例中,A和B可以任意复杂,可能包含本身具有自定义(un)封送方法的类型。)


作为Dace C答案的补充答案。 我希望实现与您相同的目标,但是我希望重用函数,而不是硬编码值

这是我做的:

type DynamicFieldsUnmarshaller interface {
    WithExtraFields(map[string]interface{})
    Unmarshal([]byte) error
}

type TestObject struct {
    Name string `json:"name"`
    CustomFields map[string]interface{} `json:"-"`
}

func (o *TestObject) Unmarshal(data []byte) error {
    return UnmarshalCustomJSON(data,o)
}

func (o *TestObject) WithExtraFields(f map[string]interface{}) {
    o.CustomFields = f
}

func UnmarshalCustomJSON(b []byte, o DynamicFieldsUnmarshaller) error {
    if err := json.Unmarshal(b, &o); err != nil {
        return err
    }

    // unmarshal everything to a map
    var vals map[string]interface{}
    if err := json.Unmarshal(b, &vals); err != nil {
        return err
    }

    if len(vals)== 0 {
        return nil
    }

    fields := reflect.TypeOf(o).Elem()
    num := fields.NumField()
    for i := 0; i < num; i++ {
        field := fields.Field(i)
        jsonTag := field.Tag.Get("json")
        if jsonTag != "" && jsonTag != "-" {
            delete(vals, jsonTag)
        }
    }

    o.WithExtraFields(vals)

    return nil
}
只会将“年龄”添加到dto.CustomFields映射


注意:这个解决方案可能并不总是最好的,因为它没有实现json。Unmarshaler

如果我理解下面的评论,请编辑这个问题(可能是json输入示例),以明确输入可以有任何键/值,而不仅仅是“Item1”(例如,它听起来像
{/*A和B的*/,“somekey”:“someval”,“otherkey”:42}
可能是有效的输入。嘿,我不确定你是否看到了这一点,或者它是否有用(这就是为什么我只写一条评论,也许有人无意中发现了这个问题)但是,本次演讲介绍了一些关于JSON和Go的精彩内容:似乎您想为struct X编写自己的解组程序,正如他所讨论的,如果我理解您的问题,有没有一种方法可以在不硬编码“things”和“thangs”的情况下做到这一点?
type DynamicFieldsUnmarshaller interface {
    WithExtraFields(map[string]interface{})
    Unmarshal([]byte) error
}

type TestObject struct {
    Name string `json:"name"`
    CustomFields map[string]interface{} `json:"-"`
}

func (o *TestObject) Unmarshal(data []byte) error {
    return UnmarshalCustomJSON(data,o)
}

func (o *TestObject) WithExtraFields(f map[string]interface{}) {
    o.CustomFields = f
}

func UnmarshalCustomJSON(b []byte, o DynamicFieldsUnmarshaller) error {
    if err := json.Unmarshal(b, &o); err != nil {
        return err
    }

    // unmarshal everything to a map
    var vals map[string]interface{}
    if err := json.Unmarshal(b, &vals); err != nil {
        return err
    }

    if len(vals)== 0 {
        return nil
    }

    fields := reflect.TypeOf(o).Elem()
    num := fields.NumField()
    for i := 0; i < num; i++ {
        field := fields.Field(i)
        jsonTag := field.Tag.Get("json")
        if jsonTag != "" && jsonTag != "-" {
            delete(vals, jsonTag)
        }
    }

    o.WithExtraFields(vals)

    return nil
}
   body := []byte(`
        {
            "name":"kilise",
            "age": 40
        }
    `)

    var dto TestObject
    err := dto.Unmarshal(body)
    if err != nil {
        panic(err)
    }