繁体   English   中英

golang unmarshal complex json

[英]golang unmarshal complex 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可以容纳两者)。

另请注意,在上面的示例中未检查类型断言是否成功。 如果unmarshaled数组少于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 )。

Go Playground尝试:

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包含一个数组文字,并且您尝试将其反序列化为结构。 您需要将JSON更改为对象文字,其中键是结构的属性名称。

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

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

因为你有一个数组文字而不是一个对象,解析的最好方法是首先解组到一片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的源代码,那么将其更改为对象文字将是最简单的方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM