golang unmarshal complex json

ε祈祈猫儿з 提交于 2019-12-01 08:41:00

The JSON input you specified is an array of different types, so as it is, you can't unmarshal it into a struct, but only into a slice of different types: []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)

Output:

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

Filling the struct

Good, you now have the values, just not in the struct you want them to be. You can use type assertion to obtain the types you want:

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)

Output (try it on the Go Playground):

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

Some notes:

The "internal" arrays of the values of "a" and "b" are unmarshaled into values of type []interface{} which you cannot simply convert to []int or []float64 hence the for loops to iterate over them and use type assertion on each of their elements. Also note that the json package unmarshals the numbers into values of type float64 and not int (because not just integers can be in the JSON text so float64 is used which can accommodate both).

Also note that the success of type assertions are not checked in the above example. If the unmarshaled array has less than 3 elements, or any of the type assertion fails, a runtime panic occurs.

Using recover()

You can add a defer function which calls recover() to catch this panic (try it on the 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...

Code with checks

Or you can add checks for the type assertions. The type assertion has a special form v, ok = x.(T) which when used never panics, but rather if the type assertion doesn't hold, ok will be false (and will be true if type assertion holds).

Try it on the 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)

Your JSON contains an array literal and you're trying to deserialize it as a struct. You need to change the JSON to an object literal where the keys are the property names of your struct.

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

If the JSON isn't something you have the ability to change, then you will need to deserialize it into an untyped array and perform your own transformation into the struct type.

Because you have an array literal rather than an object, the best way to parse is by unmarshaling first to a slice of json.RawMessages, and then go through the fields in the resulting slice:

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)
}

This gives the correct result:

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

Playground: https://play.golang.org/p/jcYvINkTTn

If you have control over the source of the JSON though, changing it to an object literal would be the most straightforward way to go about it.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!