簡體   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