使用自定义unmarshaller处理嵌套的JSON结构

时间:2015-04-06 02:10:42

标签: 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"`
}

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函数(处理选项字符串字段),然后切换到A和B的默认JSON unmarshaller?

3 个答案:

答案 0 :(得分:0)

根据json.Unmarshal(...)中的JSON对象类型将Items1 / 2声明为map[string]interface{}。如果它们丢失,则只需将其设置为nil

type X struct {
  // ...
  Item1 string map[string]interface{}
  Item2 string map[string]interface{}

另请注意,如果字段名称与JSON密钥名称匹配(不区分大小写),则无需为其包含json:"..."名称标记。

type A struct {
  AF1 string // Will look for keys named "AF1", "af1", etc.
}

答案 1 :(得分:0)

如果我从您的其他评论中正确理解了问题, 然后输入可能包含任何任意名称未知的额外字段(和类型?) 你想要/需要访问这些。 如果它只是为了以后的重新编组,那么json.RawMessage类型会引起人们的兴趣。

理想情况下,encoding/json会有一个特殊标记 (例如",any" encoding/xml标记 这将自动收集任何额外/未引用的JSON项目到a map[string]interface{}map[string]json.RawMessage字段。 但是,我找不到任何这样的功能,也没有找到一种用匿名结构模拟它的明显方法(但我并没有非常努力)。

编辑:有an open issue in the Go project for this feature。显然,在Go 1.2中提交了一项变更并进行了部分审核,但最终没有被接受。

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

这是一个快速拼凑的例子, 可能有更好/更清晰/更安全的方法来做到这一点。 (在这个例子中,A和B可以是任意复杂的,可能包含自己有自定义(非)编组方法的类型。)

package main

import (
    "encoding/json"
    "fmt"
)

type A struct {
    AF1 string
}

type B struct {
    BF1 string
}

type X struct {
    Things []A
    Thangs []B

    // Or perhaps json.RawMessage if you just
    // want to pass them through.
    // Or map of string/int/etc if the value type is fixed.
    Extra map[string]interface{}
}

// Marshal Way 1: call unmarshal twice on whole input

type xsub struct {
    Things []A `json:"things"`
    Thangs []B `json:"thangs"`
}

func (x *X) _UnmarshalJSON(b []byte) error {
    // First unmarshall the known keys part:
    var tmp xsub
    if err := json.Unmarshal(b, &tmp); err != nil {
        return err
    }

    // Then unmarshall the whole thing again:
    var vals map[string]interface{}
    if err := json.Unmarshal(b, &vals); err != nil {
        return err
    }

    // Everything worked, chuck the map entries for
    // "known" fields and store results.
    delete(vals, "things")
    delete(vals, "thangs")
    x.Things = tmp.Things
    x.Thangs = tmp.Thangs
    x.Extra = vals
    return nil
}

// Way 2:

func (x *X) UnmarshalJSON(b []byte) error {
    // Only partially decode:
    var tmp map[string]json.RawMessage
    if err := json.Unmarshal(b, &tmp); err != nil {
        return err
    }

    // Now handle the known fields:
    var things []A
    if err := json.Unmarshal(tmp["things"], &things); err != nil {
        return err
    }
    var thangs []B
    if err := json.Unmarshal(tmp["thangs"], &thangs); err != nil {
        return err
    }

    // And the unknown fields.
    var extra map[string]interface{}

    // Either:
    if true {
        // this has more calls to Unmarshal, but may be more desirable
        // as it completely skips over the already handled things/thangs.
        delete(tmp, "things")
        delete(tmp, "thangs")
        // If you only needed to store the json.RawMessage for use
        // in MarshalJSON then you'd just store "tmp" and stop here.

        extra = make(map[string]interface{}, len(tmp))
        for k, raw := range tmp {
            var v interface{}
            if err := json.Unmarshal(raw, &v); err != nil {
                return err
            }
            extra[k] = v
        }
    } else { // Or:
        // just one more call to Unmarshal, but it will waste
        // time with things/thangs again.
        if err := json.Unmarshal(b, &extra); err != nil {
            return err
        }
        delete(extra, "things")
        delete(extra, "thangs")
    }

    // no error, we can store the results
    x.Things = things
    x.Thangs = thangs
    x.Extra = extra
    return nil
}

func (x X) MarshalJSON() ([]byte, error) {
    // abusing/reusing x.Extra, could copy map instead
    x.Extra["things"] = x.Things
    x.Extra["thangs"] = x.Thangs
    result, err := json.Marshal(x.Extra)
    delete(x.Extra, "things")
    delete(x.Extra, "thangs")
    return result, err
}

func main() {
    inputs := []string{
        `{"things": [], "thangs": []}`,

        `
{
    "things": [
    {
        "AF1": "foo"
    },
    {
        "AF1": "bar"
    }
    ],
    "thangs": [
        {
            "BF1": "string value"
        }
    ],
    "xRandomKey":       "not known ahead of time",
    "xAreValueTypesKnown": 172
}`,
    }

    for _, in := range inputs {
        fmt.Printf("\nUnmarshal(%q):\n", in)
        var x X
        err := json.Unmarshal([]byte(in), &x)
        if err != nil {
            fmt.Println("unmarshal:", err)
        } else {
            fmt.Printf("\tas X: %+v\n", x)
            fmt.Printf("\twith map: %v\n", x.Extra)
            out, err := json.Marshal(x)
            if err != nil {
                fmt.Println("marshal:", err)
                continue
            }
            fmt.Printf("\tRemarshals to: %s\n", out)
        }
    }
}

Run on Playground

答案 2 :(得分:0)

作为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
}

这只能将不在结构中的值添加到map[string]interface{}字段中。

例如:

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

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

只会在dto.CustomFields映射中添加“年龄”。

请注意,此解决方案可能并不总是最好的,因为它没有实现json.Unmarshaler