使用动态键解组 JSON

我有一个场景,其中 JSON 具有需要解组到结构中的动态字段集。


const jsonStream = `{

    "name": "john",

    "age": 23,

    "bvu62fu6dq": {

        "status": true

    }

}`


type Status struct {

    Status bool

}


type Person struct {

    Name   string            `json:"name"`

    Age    int               `json:"age"`

    Status map[string]Status `json:"status"`

}


func main() {

    dec := json.NewDecoder(strings.NewReader(jsonStream))

    for {

        var person Person

        if err := dec.Decode(&person); err == io.EOF {

            break

        } else if err != nil {

            log.Fatal(err)

        }

        fmt.Println(person)

        fmt.Println(person.Status["bvu62fu6dq"])

    }

}

输出:


{john 23 map[]}

{false}

当它被解组时,嵌套状态结构没有被正确解析为 JSON 中的值(即使在 JSON 中显示为真值也显示为 false),代码中是否有任何问题?


月关宝盒
浏览 106回答 2
2回答

慕妹3146593

您的类型与您拥有的 JSON 不匹配:type Status struct {    Status bool}type Person struct {    Name   string            `json:"name"`    Age    int               `json:"age"`    Status map[string]Status `json:"status"`}映射到 JSON,看起来像这样:{    "name": "foo",    "age": 12,    "status": {        "some-string": {            "Status": true        }    }}在 go 类型中使用已知/未知字段的组合来解组数据的最简单方法是使用如下内容:type Person struct {    Name   string                 `json:"name"`    Age    int                    `json:"age"`    Random map[string]interface{} `json:"-"` // skip this key}然后,首先解组已知数据:var p Personif err := json.Unmarshal([]byte(jsonStream), &p); err != nil {    panic(err)}// then unmarshal the rest of the dataif err := json.Unmarshal([]byte(jsonStream), &p.Random); err != nil {    panic(err)}现在Random地图将包含所有数据,包括name和age字段。看到你已经在结构上标记了那些,这些键是已知的,所以你可以轻松地从地图中删除它们:delete(p.Random, "name")delete(p.Random, "age")现在p.Random将包含所有未知键及其各自的值。这些值显然将是一个带有 field 的对象status,它应该是一个布尔值。您可以着手使用类型断言并将它们全部转换为更合理的类型,或者您可以采取快捷方式并编组/取消编组值。像这样更新你的Person类型:type Person struct {    Name     string                 `json:"name"`    Age      int                    `json:"age"`    Random   map[string]interface{} `json:"-"`    Statuses map[string]Status      `json:"-"`}现在获取干净的Random值,将其编组并将其解组回Statuses字段中:b, err := json.Marshal(p.Random)if err != nil {    panic(err)}if err := json.Unmarshal(b, &p.Statuses); err != nil {    panic(err)}// remove Random mapp.Random = nil结果Person.Statuses["bvu62fu6dq"].Status设置为true清理这一切,并将数据编组回来现在,因为我们的Random和Statuses字段被标记为在 JSON 编组 ( json:"-") 中被忽略,当您想从这些类型输出原始 JSON 时,编组这种Person类型不会很好。最好将此逻辑包装在自定义 JSON (un)-Marshaller 接口中。您可以在类型的MarshalJSON和UnmarshalJSON方法中使用一些中间类型Person,或者只创建一个映射并设置您需要的键:func (p Person) MarshalJSON() ([]byte, error) {    data := make(map[string]interface{}, len(p.Statuses) + 2) // 2 being the extra fields    // copy status fields    for k, v := range p.Statuses {        data[k] = v    }    // add known keys    data["name"] = p.Name    data["age"] = p.Age    return json.Marshal(data) // return the marshalled map}同样,您可以为 做同样的事情UnmarshalJSON,但您需要创建一个Person没有自定义处理的类型版本:type intermediaryPerson struct {    Name string  `json:"name"`    Age  int `json:"age"`    Random map[string]interface{} `json:"-"`}// no need for the tags and helper fields anymoretype Person struct {    Name    string    Age     int    Statuses map[string]Status // Status type doesn't change}func (p *Person) UnmarshalJSON(data []byte) error {    i := intermediaryPerson{}    if err := json.Unmarshal(data, &i); err != nil {        return err    }    if err := json.Unmarshal(data, &i.Random); err != nil {        return err    }    delete(i.Random, "name")    delete(i.Random, "age")    stat, err := json.Marshal(i.Random)    if err != nil {        return err    }    // copy known fields    p.Name = i.Name    p.Age = i.Age    return json.Unmarshal(stat, &p.Statuses) // set status fields}在这种情况下,创建一个处理已知字段的类型并将其嵌入是很常见的,不过:type BasePerson struct {    Name string `json:"name"`    Age  int    `json:"age"`}并将其嵌入中介和“主要”/导出类型中:type interPerson struct {   BasePerson   Random map[string]interface{} `json:"-"`}type Person struct {    BasePerson    Statuses map[string]Status}这样,您可以直接将已知字段解组为BasePerson类型,分配它,然后处理映射:func (p *Person) UnmarshalJSON(data []byte) error {    base := BasePerson{}    if err := json.Unmarshal(data, &base); err != nil {        return err    }    p.BasePerson = base // takes care of all known fields    unknown := map[string]interface{}{}    if err := json.Unmarshal(data, unknown); err != nil {        return err    }    // handle status stuff same as before    delete(unknown, "name") // remove known fields    // marshal unknown key map, then unmarshal into p.Statuses}演示 2这就是我的处理方式。它允许调用json.Marshal和json.Unmarshal看起来就像任何其他类型,它将未知字段的处理集中在一个地方(编组器/解组器接口的实现),并为您留下一个Person类型,其中每个字段都包含所需的数据, 以可用的格式。它的效率有点低,因为它依赖于对未知密钥的解组/编组/解组。你可以取消它,就像我说的那样,使用类型断言并在地图上迭代,unknown而是用这样的东西来解决:for k, v := range unknown {    m, ok := v.(map[string]interface{})    if !ok {        continue // not {"status": bool}    }    s, ok := m["status"]    if !ok {        continue // status key did not exist, ignore    }    if sb, ok := s.(bool); ok {        // ok, we have a status bool value        p.Statuses[k] = Status{            Status: sb,        }    }}但说实话,性能差异不会那么大(这是微优化 IMO),而且代码对我来说有点太冗长了。懒惰,在需要时优化,而不是随时优化

翻过高山走不出你

类型不符合您的 json 值。    const jsonStream = `{    "name": "john",    "age": 23,    "bvu62fu6dq": {        "status": true     }   }`对于上面的 json,您的代码应该像下面的 snnipet 一样工作(对现有代码进行一些修改)。package mainimport (    "encoding/json"    "fmt"    "io"    "log"    "strings")const jsonStream = `{    "name": "john",    "age": 23,    "bvu62fu6dq": {        "status": true    }}`type bvu62fu6dq struct {    Status bool}type Person struct {    Name   string     `json:"name"`    Age    int        `json:"age"`    Status bvu62fu6dq `json:"bvu62fu6dq"`}func main() {    dec := json.NewDecoder(strings.NewReader(jsonStream))    for {        var person Person        if err := dec.Decode(&person); err == io.EOF {            break        } else if err != nil {            log.Fatal(err)        }        fmt.Println(person)        fmt.Println(person.Status)    }}根据您的 json 数据,您必须使用类型字段进行映射。 运行代码片段
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go