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

我正在处理一个遗留系统,该系统返回带有嵌套结构和一些可选字段(以随机顺序)的 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

    */         

}

如果 Item[12] 确实出现,我想把它们藏在地图或类似的地方。


有没有优雅的方法来解组 X?有什么方法可以为 X 编写自定义 UnmarshalJSON func(以处理选项字符串字段),然后将 A 和 B 交给默认的 JSON 解组器?


喵喵时光机
浏览 168回答 2
2回答

慕标5832272

如果我从您的附加评论中正确理解了问题,那么输入可能包含任何具有未知名称(和类型?)的任意额外字段,并且您想要/需要访问这些字段。如果只是为了以后重新编组,那么该json.RawMessage类型会很有趣。理想情况下encoding/json会有一个特殊的标签(如",any" encoding/xml标签),它会自动将任何额外/未引用的 JSON 项目收集到 a map[string]interface{}或map[string]json.RawMessage字段中。但是我找不到任何这样的功能,也找不到一种明显的方法来用匿名结构来模拟它(但我并没有很努力地尝试)。编辑:Go 项目中有一个针对此功能的未解决问题。显然,围绕 Go 1.2 提交了一个更改并进行了部分审查,但最终没有被接受。如果做不到这一点,有几种方法可以完全按照您的建议执行,为 X 制作自定义 (un) marshaller 并回调到 json 包以处理[]A和[]B.这是一个快速组合在一起的示例,可能有更好/更清晰/更安全的方法来做到这一点。(在整个示例中,A 和 B 可以是任意复杂的,可能包含本身具有自定义(取消)编组方法的类型。)package mainimport (    "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 inputtype 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

holdtom

作为 Dace C 答案的附加答案。我想实现与您相同的目标,但是我想重用该函数而不是对值进行硬编码。这是我做的:type DynamicFieldsUnmarshaller interface {&nbsp; &nbsp; WithExtraFields(map[string]interface{})&nbsp; &nbsp; Unmarshal([]byte) error}type TestObject struct {&nbsp; &nbsp; Name string `json:"name"`&nbsp; &nbsp; CustomFields map[string]interface{} `json:"-"`}func (o *TestObject) Unmarshal(data []byte) error {&nbsp; &nbsp; return UnmarshalCustomJSON(data,o)}func (o *TestObject) WithExtraFields(f map[string]interface{}) {&nbsp; &nbsp; o.CustomFields = f}func UnmarshalCustomJSON(b []byte, o DynamicFieldsUnmarshaller) error {&nbsp; &nbsp; if err := json.Unmarshal(b, &o); err != nil {&nbsp; &nbsp; &nbsp; &nbsp; return err&nbsp; &nbsp; }&nbsp; &nbsp; // unmarshal everything to a map&nbsp; &nbsp; var vals map[string]interface{}&nbsp; &nbsp; if err := json.Unmarshal(b, &vals); err != nil {&nbsp; &nbsp; &nbsp; &nbsp; return err&nbsp; &nbsp; }&nbsp; &nbsp; if len(vals)== 0 {&nbsp; &nbsp; &nbsp; &nbsp; return nil&nbsp; &nbsp; }&nbsp; &nbsp; fields := reflect.TypeOf(o).Elem()&nbsp; &nbsp; num := fields.NumField()&nbsp; &nbsp; for i := 0; i < num; i++ {&nbsp; &nbsp; &nbsp; &nbsp; field := fields.Field(i)&nbsp; &nbsp; &nbsp; &nbsp; jsonTag := field.Tag.Get("json")&nbsp; &nbsp; &nbsp; &nbsp; if jsonTag != "" && jsonTag != "-" {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; delete(vals, jsonTag)&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; o.WithExtraFields(vals)&nbsp; &nbsp; return nil}这应该只将不在结构中的值添加到map[string]interface{}字段中。例如:&nbsp; &nbsp;body := []byte(`&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "name":"kilise",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "age": 40&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; `)&nbsp; &nbsp; var dto TestObject&nbsp; &nbsp; err := dto.Unmarshal(body)&nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; panic(err)&nbsp; &nbsp; }只会将“年龄”添加到 dto.CustomFields 地图。请注意,此解决方案可能并不总是最好的,因为它没有实现 json.Unmarshaler
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go