Avatar ·

Распаковка вложенных JSON объектов в Go: проблема с пустыми полями

Всем привет! Я новичок в Go и пытаюсь распарсить JSON с вложенными объектами, но у меня ничего не получается. JSON приходит от внешнего API и выглядит примерно так: ```json { "id": 123, "name": "Test", "details": { "age": 30, "city": "Moscow" } } ``` Я создал структуры: ```go type Person struct { ID int `json:"id"` Name string `json:"name"` Details Details `json:"details"` } type Details struct { Age int `json:"age"` City string `json:"city"` } ``` И пытаюсь распарсить: ```go var person Person err := json.Unmarshal([]byte(jsonString), &person) if err != nil { log.Fatal(err) } fmt.Println(person) ``` Проблема в том, что когда я запускаю код, поля `Age` и `City` внутри `Details` всегда пустые (0 и ""). При этом `ID` и `Name` заполняются нормально. Я проверил JSON — он точно валидный. Что я делаю не так? Может быть, нужно как-то по-особенному объявлять вложенные структуры? Или проблема в том, что я забываю что-то инициализировать? Подскажите, пожалуйста, как правильно распаковывать такие вложенные объекты в Go. Заранее спасибо! Я уже перерыл кучу документации, но везде примеры только с плоскими JSON. <<>>

Почему поля Details остаются пустыми?

Ваш код в целом правильный для распаковки вложенных JSON, но проблема скорее всего в том, как вы передаёте JSON. В Go json.Unmarshal требует, чтобы JSON был в виде []byte. Если вы читаете JSON из файла или строки, убедитесь, что конвертируете его правильно. Вот рабочий пример:

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

type Person struct {
    ID      int     `json:"id"`
    Name    string  `json:"name"`
    Details Details `json:"details"`
}

type Details struct {
    Age  int    `json:"age"`
    City string `json:"city"`
}

func main() {
    jsonString := `{
        "id": 123,
        "name": "Test",
        "details": {
            "age": 30,
            "city": "Moscow"
        }
    }`

    var person Person
    err := json.Unmarshal([]byte(jsonString), &person)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("ID: %d\n", person.ID)
    fmt.Printf("Name: %s\n", person.Name)
    fmt.Printf("Age: %d\n", person.Details.Age)
    fmt.Printf("City: %s\n", person.Details.City)
}

Этот код выведет:

ID: 123
Name: Test
Age: 30
City: Moscow

Возможные причины вашей проблемы:

  • Опечатки в JSON-тегах: проверьте, что теги в структурах точно соответствуют ключам в JSON (регистр имеет значение).
  • Лишние пробелы или символы: если JSON приходит из файла, там могут быть BOM-символы или невидимые пробелы.
  • Поле details отсутствует или равно null: проверьте сам JSON — возможно, в реальных данных поле details иногда отсутствует.

Что делать, если details может отсутствовать?

Если поле details может быть null или отсутствовать, используйте указатели или omitempty:

type Person struct {
    ID      int      `json:"id"`
    Name    string   `json:"name"`
    Details *Details `json:"details"` // указатель
}

// Или используйте отдельный флаг
type Person struct {
    ID      int     `json:"id"`
    Name    string  `json:"name"`
    Details Details `json:"details,omitempty"`
}

Работа с динамической вложенностью через map

Если структура JSON может меняться, используйте map[string]interface{}:

var result map[string]interface{}
err := json.Unmarshal([]byte(jsonString), &result)
if err != nil {
    log.Fatal(err)
}

// Доступ к вложенным полям
details := result["details"].(map[string]interface{})
age := details["age"].(float64) // числа в JSON всегда float64
fmt.Println(age)

Проверка ошибок — ключ к успеху

Всегда проверяйте ошибку после json.Unmarshal. Если JSON невалидный, вы получите сообщение, которое укажет на проблему. Также можно использовать json.Decoder для потоковой обработки:

decoder := json.NewDecoder(strings.NewReader(jsonString))
var person Person
err := decoder.Decode(&person)
if err != nil {
    log.Fatal(err)
}

Этот метод лучше подходит для больших JSON или чтения из сети.

<<>>

Альтернативный подход: использование встроенных структур (embedded structs)

Если вложенный объект логически является частью родительского, можно "встроить" структуру, чтобы поля были на одном уровне:

type Person struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
    Details // встраивание
}

type Details struct {
    Age  int    `json:"age"`
    City string `json:"city"`
}

func main() {
    jsonString := `{"id": 123, "name": "Test", "age": 30, "city": "Moscow"}`
    var p Person
    json.Unmarshal([]byte(jsonString), &p)
    fmt.Println(p.Age, p.City) // 30 Moscow
}

Обратите внимание: JSON теперь плоский, но структура остаётся логически разделённой. Это удобно, если вы контролируете формат данных. Для вашего изначального вложенного JSON этот подход не подойдёт — он ожидает, что поля age и city будут на верхнем уровне.

Рекомендация

Для строго типизированной работы с вложенными JSON всегда используйте отдельные структуры (как в первом ответе). Это даёт читаемость, автодополнение в IDE и проверку типов на этапе компиляции. Если структура JSON неизвестна заранее — используйте map[string]interface{}, но будьте готовы к приведению типов.