json.Unmarshal против json.NewDecoder.Decode: в чём разница в Go?
Основное различие: потоковая обработка 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 даёт больше гибкости.