Получить данные из json golang: декодировать массивы с разнородными данными
Сам ответ json имеет такую структуру:
{
"payload":[
0,
[
[
[
{
"title":"Привет!"
},
{
"title":"123"
}
]
],
1000
]
]
}
Для получения данных я использовал следующий код:
var d map[string]interface{}
rawData := []byte(`{"payload": [0, [[[{"title": "Привет!"}, {"title": "123"}]], 1000]]}`)
err := json.Unmarshal(rawData, &d)
if err != nil {
panic(err)
}
fmt.Println(d["payload"].([]interface{})[1].([]interface{})[0].([]interface{}))
Да, этот код работает. Проблема заключается в том, что хотелось бы работать с помощью структур или сделать код более читабельным. На сайте генерации из json в struct ответ следующий. Только не знаю как работать с индексами.
type AutoGenerated struct {
Payload []interface{} `json:"payload"`
}
Ответы (1 шт):
В go по-умолчанию структуры транслируются в объекты json и обратно. Например, для типа
type Title struct {
Title string `json:"title"` // Use default json codec
}
вызов json.Marshal(Title{"Привет!"}) сгенерирует строку {"title":"Привет!"}, а обратное преобразование будет работать через анализ типов:
var jsonData := []byte(`{"title":"Привет!"}`)
var dst Title
err := json.Unmarshal(jsonData, &dst)
...
Но для того, чтобы отобразить структуру в массив и обратно, нужно делать кастомный кодек.
Для приведённого вами примера json объекта я придумал вот такую иерархию типов:
type Message struct {
Payload OuterArray `json:"payload"` // Use default json codec
}
type OuterArray struct {
Number int
Data InnerArray
}
type InnerArray struct {
Titles []ArrayOfTitles
Number int
}
type ArrayOfTitles []Title // Use default json codec
type Title struct {
Title string `json:"title"` // Use default json codec
}
Для структур OuterArray и InnerArray нужно делать кастомные кодеки. Маршалер устроен просто:
// Implements json.Marshaler
func (ia InnerArray) MarshalJSON() ([]byte, error) {
asArray := []interface{}{
ia.Titles,
ia.Number,
}
return json.Marshal(asArray)
}
// Implements json.Marshaler
func (oa OuterArray) MarshalJSON() ([]byte, error) {
asArray := []interface{}{
oa.Number,
oa.Data,
}
return json.Marshal(asArray)
}
Анмаршалер устроен хитрее. В encoding/json есть потоковый парсер, который позволяет парсить json токен за токеном. Соответственно, нужно проверить, что первый токен - это открывающая скобка [, затем парсить элементы массива в соответствии со структурой.
// Implements json.Unmarshaler
func (dst *OuterArray) UnmarshalJSON(bytes []byte) error {
stream := string(bytes)
dec := json.NewDecoder(strings.NewReader(stream))
// read open bracket
token, err := dec.Token()
if err != nil {
return err
}
delim, ok := token.(json.Delim)
if !ok {
return fmt.Errorf("Not an array: %s", stream)
}
if delim != '[' {
return fmt.Errorf("'[' expected, got: %c", delim)
}
if !dec.More() {
return fmt.Errorf("Missing number: %s", string(bytes))
}
err = dec.Decode(&dst.Number)
if err != nil {
return err
}
if !dec.More() {
return fmt.Errorf("Missing data: %s", string(bytes))
}
err = dec.Decode(&dst.Data)
if err != nil {
return err
}
return nil
}
В результате кодирование/декодирование работает без проблем:
var msg = `{
"payload":[
0,
[
[
[
{
"title":"Привет!"
},
{
"title":"123"
}
]
],
1000
]
]
}`
func main() {
ia := InnerArray{
Titles: []ArrayOfTitles{
{
Title{Title: "Привет!"},
Title{Title: "123"},
},
},
Number: 1000,
}
oa := OuterArray{
Number: 0,
Data: ia,
}
payload := Message{
Payload: oa,
}
bytes, err := json.Marshal(payload)
if err != nil {
panic(err)
}
fmt.Printf("Object: %#v\nEncoded: %s\n", payload, string(bytes))
var msgDecoded Message
err = json.Unmarshal([]byte(msg), &msgDecoded)
if err != nil {
panic(err)
}
fmt.Printf("Decoded object: %#v\n", msgDecoded)
}
Результат работы программы:
Object: main.Message{Payload:main.OuterArray{Number:0, Data:main.InnerArray{Titles:[]main.ArrayOfTitles{main.ArrayOfTitles{main.Title{Title:"Привет!"}, main.Title{Title:"123"}}}, Number:1000}}}
Encoded: {"payload":[0,[[[{"title":"Привет!"},{"title":"123"}]],1000]]}
Decoded object: main.Message{Payload:main.OuterArray{Number:0, Data:main.InnerArray{Titles:[]main.ArrayOfTitles{main.ArrayOfTitles{main.Title{Title:"Привет!"}, main.Title{Title:"123"}}}, Number:1000}}}
Как видите, кодирование работает в обе стороны. Вместо d["payload"].([]interface{})[1].([]interface{})[0].([]interface{}) доступ к списку названий будет msgDecoded.Payload.Data.Titles
Полный вариант кода со всем необходимыми в Github: https://github.com/pakuula/StackOverflow/blob/main/go/1436064/main.go