为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
喵喵时光机 2021-09-27 17:26:39
我正在处理一个遗留系统,该系统返回带有嵌套结构和一些可选字段(以随机顺序)的 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 解组器?
查看完整描述

2 回答

?
慕标5832272

TA贡献1966条经验 获得超4个赞

如果我从您的附加评论中正确理解了问题,那么输入可能包含任何具有未知名称(和类型?)的任意额外字段,并且您想要/需要访问这些字段。如果只是为了以后重新编组,那么该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 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


查看完整回答
反对 回复 2021-09-27
?
holdtom

TA贡献1805条经验 获得超10个赞

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


查看完整回答
反对 回复 2021-09-27
  • 2 回答
  • 0 关注
  • 167 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信