首页 > 解决方案 > 如何解组可以是字符串 * 或 * 字符串数组的不一致 JSON 字段?

问题描述

我在解组一些我无法控制的 Json 时遇到问题。有一个字段 99% 的时间是一个字符串,但偶尔是一个数组。

type MyListItem struct {
    Date  string `json:"date"`
    DisplayName       string `json:"display_name"`
}

type MyListings struct {
    CLItems []MyListItem `json:"myitems"`
}

var mylist MyListings
err = json.Unmarshal(jsn, &mylist)
if err != nil {
    fmt.Print("JSON:\n%s\n error:%v\n", string(jsn),err)
    return
}

json如下:

{       
    "date": "30 Apr",
    "display_name": "Mr Smith"
},
{
    "date": "30 Apr",
    "display_name": ["Mr Smith", "Mr Jones"],
}

错误:json:无法将数组解组为字符串类型的 Go struct 字段 MyListItem.display_name

标签: jsongo

解决方案


使用json.RawMessage捕获变化的字段。

使用 json "-" 名称对DisplayName解码器隐藏字段。应用程序将在顶级 JSON 解码后填充此字段。

type MyListItem struct {
    Date           string          `json:"date"`
    RawDisplayName json.RawMessage `json:"display_name"`
    DisplayName    []string        `json:"-"`
}

解组顶级 JSON:

var li MyListItem
if err := json.Unmarshal(data, &li); err != nil {
    // handle error
}

根据原始数据的类型解组显示名称:

if len(li.RawDisplayName) > 0 {
    switch li.RawDisplayName[0] {
    case '"':
        if err := json.Unmarshal(li.RawDisplayName, &li.DisplayName); err != nil {
            // handle error
        }
    case '[':
        var s []string
        if err := json.Unmarshal(li.RawDisplayName, &s); err != nil {
            // handle error
        }
        // Join arrays with "&" per OP's comment on the question.
        li.DisplayName = strings.Join(s, "&")
    }
}

游乐场示例

将上述内容合并到一个 for 循环中来处理MyListings

var listings MyListings
if err := json.Unmarshal([]byte(data), &listings); err != nil {
    // handle error
}
for i := range listings.CLItems {
    li := &listings.CLItems[i]
    if len(li.RawDisplayName) > 0 {
        switch li.RawDisplayName[0] {
        case '"':
            if err := json.Unmarshal(li.RawDisplayName, &li.DisplayName); err != nil {
                // handle error
            }
        case '[':
            var s []string
            if err := json.Unmarshal(li.RawDisplayName, &s); err != nil {
                // handle error
            }
            li.DisplayName = strings.Join(s, "&")
        }
    }
}

游乐场示例

如果数据模型中有多个地方的值可以是字符串或 [] 字符串,则将逻辑封装在一个类型中会很有帮助。在json.Unmarshaler接口的实现中解析 JSON 数据。

type multiString string

func (ms *multiString) UnmarshalJSON(data []byte) error {
    if len(data) > 0 {
        switch data[0] {
        case '"':
            var s string
            if err := json.Unmarshal(data, &s); err != nil {
                return err
            }
            *ms = multiString(s)
        case '[':
            var s []string
            if err := json.Unmarshal(data, &s); err != nil {
                return err
            }
            *ms = multiString(strings.Join(s, "&"))
        }
    }
    return nil
}

像这样使用它:

type MyListItem struct {
    Date        string      `json:"date"`
    DisplayName multiString `json:"display_name"`
}

type MyListings struct {
    CLItems []MyListItem `json:"myitems"`
}

var listings MyListings
if err := json.Unmarshal([]byte(data), &listings); err != nil {
    log.Fatal(err)
}

游乐场示例

这是将值作为字符串切片而不是作为单个字符串获取值的代码,其中值由 & 连接。

type multiString []string

func (ms *multiString) UnmarshalJSON(data []byte) error {
    if len(data) > 0 {
        switch data[0] {
        case '"':
            var s string
            if err := json.Unmarshal(data, &s); err != nil {
                return err
            }
            *ms = multiString{s}
        case '[':
            if err := json.Unmarshal(data, (*[]string)(ms)); err != nil {
                return err
            }
        }
    }
    return nil
}

游乐场示例


推荐阅读