Avatar ·

json.Unmarshal против json.NewDecoder.Decode: в чём разница в Go?

Всем привет! Я новичок в Go и пытаюсь разобраться с парсингом JSON. Вижу, что в стандартной библиотеке есть два способа: `json.Unmarshal` и `json.NewDecoder().Decode()`. Оба вроде делают одно и то же — превращают JSON в структуру данных Go. Но я не понимаю, когда какой использовать. Например, у меня есть такой код: ```go type User struct { Name string `json:"name"` Age int `json:"age"` } // Способ 1 data := `{"name":"Alice","age":30}` var user1 User err := json.Unmarshal([]byte(data), &user1) // Способ 2 reader := strings.NewReader(data) var user2 User err = json.NewDecoder(reader).Decode(&user2) ``` Оба варианта работают одинаково. Но я слышал, что `Decoder` лучше для потоков, а `Unmarshal` — для маленьких данных. В чём реальная разница? Есть ли разница в производительности? Когда стоит выбирать один метод вместо другого? И что будет, если JSON очень большой (например, 100 МБ)? Помогите разобраться, а то в документации написано непонятно. <<>>

Основное различие: потоковая обработка vs загрузка в память

Главное отличие между json.Unmarshal и json.NewDecoder.Decode заключается в том, как они работают с источником данных и памятью.

json.Unmarshal

Функция json.Unmarshal принимает на вход уже готовый []byte (срез байтов). Она загружает весь JSON в память, затем парсит его и записывает результат в переданную структуру. Это удобно, когда:

  • У вас уже есть данные в виде строки или среза байтов (например, из HTTP-запроса, файла или переменной).
  • Размер JSON небольшой (до нескольких мегабайт).
  • Нужно просто и быстро распарсить один объект.

Пример:

data := `{"name":"Bob","age":25}`
var person Person
err := json.Unmarshal([]byte(data), &person)
if err != nil {
    log.Fatal(err)
}
fmt.Println(person.Name) // Bob

json.NewDecoder.Decode

json.NewDecoder создаёт декодер, который читает данные из io.Reader (поток). Он не загружает весь JSON в память сразу, а читает его частями (буферизованно). Это идеально для:

  • Больших JSON-файлов (сотни мегабайт или гигабайты).
  • Потоковых данных (например, ответ от сервера по HTTP, чтение из сети).
  • Ситуаций, когда нужно обрабатывать несколько JSON-объектов подряд (например, в формате JSON Lines).

Пример с большим файлом:

file, err := os.Open("huge_data.json")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

decoder := json.NewDecoder(file)
var data LargeStruct
err = decoder.Decode(&data)
if err != nil {
    log.Fatal(err)
}

Производительность и память

  • Скорость: json.Unmarshal может быть немного быстрее для маленьких данных, так как не требует дополнительной буферизации. Но разница обычно незначительна.
  • Память: json.Unmarshal загружает весь JSON в память. Для файла в 1 ГБ это будет 1 ГБ + память под итоговую структуру. Decoder же потребляет лишь небольшой буфер (по умолчанию 4096 байт).
  • Ошибки: При потоковом декодировании ошибка может возникнуть на середине данных, и вы не сможете "откатить" прочитанное. Unmarshal либо полностью успешен, либо возвращает ошибку для всего блока.

Когда что использовать?

  • Используйте json.Unmarshal, если:
    • JSON уже загружен в память (например, из строки, из небольшого файла).
    • Размер JSON < 10-50 МБ (зависит от ваших ограничений по памяти).
    • Вам нужно просто и быстро распарсить один объект.
  • Используйте json.NewDecoder.Decode, если:
    • JSON очень большой (сотни МБ или ГБ).
    • Данные приходят потоком (сеть, stdin, pipe).
    • Вы обрабатываете JSON Lines (много объектов, разделённых переносом строки).
    • Хотите минимизировать использование памяти.

Дополнительные возможности Decoder

У Decoder есть полезные методы, которых нет у Unmarshal:

  • decoder.Decode(&v) — можно вызывать многократно для чтения последовательности JSON-объектов.
  • decoder.Buffered() — возвращает оставшиеся непрочитанные данные.
  • decoder.Token() — позволяет читать JSON токен за токеном (низкоуровневый парсинг).
  • decoder.More() — проверяет, есть ли ещё объекты в потоке (полезно для JSON Lines).

Пример для JSON Lines (каждая строка — отдельный JSON-объект):

decoder := json.NewDecoder(reader)
for {
    var item Item
    if err := decoder.Decode(&item); err == io.EOF {
        break // конец потока
    } else if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Получен элемент: %+v\n", item)
}

Итог

Выбор между Unmarshal и Decoder — это компромисс между простотой и контролем над памятью. Для 90% случаев в учебных или небольших проектах json.Unmarshal более чем достаточно. Но если вы работаете с большими данными или потоковой передачей — используйте json.NewDecoder.

<<>>

Дополнительный нюанс: валидация и строгость

Стоит отметить ещё одно важное различие: поведение при несоответствии типов и лишних полях.

json.Unmarshal

По умолчанию Unmarshal игнорирует поля JSON, которых нет в структуре Go. Например, если в JSON есть поле "email", а в структуре его нет — ошибки не будет, оно просто отбросится. Это удобно, но может скрыть опечатки.

Чтобы включить строгую проверку, нужно использовать json.Decoder с опцией DisallowUnknownFields():

decoder := json.NewDecoder(reader)
decoder.DisallowUnknownFields() // теперь лишние поля вызовут ошибку
err := decoder.Decode(&target)

json.Decoder и целые числа

Ещё один нюанс: json.Decoder по умолчанию использует тип float64 для чисел, если не указана конкретная структура. Unmarshal делает то же самое. Но если вы используете decoder.UseNumber(), то числа будут представлены типом json.Number (строка), что позволяет избежать потери точности для больших чисел:

decoder := json.NewDecoder(reader)
decoder.UseNumber()
var data map[string]interface{}
decoder.Decode(&data)
// data["id"] будет json.Number, а не float64

Таким образом, если вам нужен строгий контроль над форматом JSON или точность чисел — Decoder даёт больше гибкости.