golang对复杂json进行解组



我有下面的JSON blob,我正在尝试将其解码为Go。

["contig", "32", {"a":[33,41,35], "b":[44,34,42]}]

我认为我必须对JSON的数据结构进行建模。我尝试使用一个名为Line:的结构

package main
import (
"encoding/json"
"fmt"
)
type Line struct {
    Contig string
    Base   string
    PopMap map[string][]int
}
func main() {
    j := []byte(`["contig", "32", {"a":[33,41,35], "b":[44,34,42]}]`)
    var dat Line
    err := json.Unmarshal(j, &dat)
    fmt.Println(dat)
    fmt.Println(err)
}

我得到以下错误:

{  map[]}
json: cannot unmarshal array into Go value of type main.Line

我做错了什么?

链接到用于尝试代码的沙箱

您指定的JSON输入是一个不同类型的数组,因此,您不能将其分解为struct,只能分解为不同类型的切片:[]interface{}

in := `["contig", "32", {"a":[33,41,35], "b":[44,34,42]}]`
var arr []interface{}
if err := json.Unmarshal([]byte(in), &arr); err != nil {
    panic(err)
}
fmt.Println(arr)

输出:

[contig 32 map[a:[33 41 35] b:[44 34 42]]]

填写struct

很好,你现在有了值,只是不在你想要的struct中。你可以使用类型断言来获得你想要的类型:

l := Line{PopMap: map[string][]int{}}
l.Contig = arr[0].(string)
l.Base = arr[1].(string)
m := arr[2].(map[string]interface{})
for k, v := range m {
    nums := v.([]interface{})
    pops := make([]int, len(nums))
    for i, val := range nums {
        pops[i] = int(val.(float64))
    }
    l.PopMap[k] = pops
}
fmt.Printf("%+v", l)

输出(在Go Playground上尝试):

{Contig:contig Base:32 PopMap:map[a:[33 41 35] b:[44 34 42]]}

一些注意事项:

"a""b"的值的"内部"数组被解组为[]interface{}类型的值,您不能简单地将其转换为[]int[]float64,因此for循环对它们进行迭代,并在它们的每个元素上使用类型断言。还要注意,json包将数字解组为float64类型的值,而不是int类型的值(因为JSON文本中不能只有整数,所以使用了float64,它可以同时容纳两者)。

还要注意,在上面的例子中没有检查类型断言的成功。如果未组合的数组的元素少于3个,或者任何类型断言失败,则会发生运行时死机。

使用recover()

你可以添加一个defer函数,它调用recover()来捕捉这种恐慌(在Go Playground上试试):

defer func() {
    if r := recover(); r != nil {
        fmt.Println("Failed to unmarshal")
    }
}()
l := Line{PopMap: map[string][]int{}}
// ...and here comes the code that uses type assertions
// and stores values into...

带检查的代码

或者,您可以添加对类型断言的检查。类型断言有一个特殊的形式v, ok = x.(T),当使用时它不会恐慌,但如果类型断言不成立,则ok将是false(如果类型断言成立,则为true)。

在围棋游戏场上试试:

if len(arr) < 3 {
    return
}
var ok bool
l := Line{PopMap: map[string][]int{}}
if l.Contig, ok = arr[0].(string); !ok {
    return
}
if l.Base, ok = arr[1].(string); !ok {
    return
}
if m, ok := arr[2].(map[string]interface{}); !ok {
    return
} else {
    for k, v := range m {
        var nums []interface{}
        if nums, ok = v.([]interface{}); !ok {
            return
        }
        pops := make([]int, len(nums))
        for i, val := range nums {
            if f, ok := val.(float64); !ok {
                return
            } else {
                pops[i] = int(f)
            }
        }
        l.PopMap[k] = pops
    }
}
fmt.Printf("%+v", l)

因为您有一个数组文字而不是一个对象,所以最好的解析方法是首先将其解组为json切片。RawMessages,然后遍历结果切片中的字段:

package main
import (
     "encoding/json"
     "fmt"
)

func main() {
    j := []byte(`["contig", "32", {"a":[33,41,35], "b":[44,34,42]}]`)
    var entries []json.RawMessage
    err := json.Unmarshal(j, &entries)
    if err != nil {
        fmt.Println(err)
    }
    var contig string
    var num string
    var obj struct {
        A []int `json:"a"`
        B []int `json:"b"`
    }
    err = json.Unmarshal(entries[0], &contig)
    err = json.Unmarshal(entries[1], &num)
    err = json.Unmarshal(entries[2], &obj)
    fmt.Println(contig)
    fmt.Println(num)
    fmt.Println(obj)
}

这给出了正确的结果:

contig 32 {[33 41 35] [44 34 42]}

游乐场:https://play.golang.org/p/jcYvINkTTn

如果您可以控制JSON的源代码,那么将其更改为对象文字将是最简单的方法

您的JSON包含一个数组文字,您正试图将其反序列化为结构。您需要将JSON更改为对象文字,其中键是结构的属性名。

j := []byte(`{
    "Contig": "contig",
    "Base": "32",
    "PopMap": {"a":[33,41,35], "b":[44,34,42]}
}`)

如果JSON不是您能够更改的内容,那么您将需要将其反序列化为非类型化数组,并执行自己的结构类型转换。

最新更新