json.Unmarshal против json.NewDecoder.Decode: что выбрать для парсинга JSON в Go?
Основное различие: работа с памятью и потоками
Ключевое отличие между json.Unmarshal и json.NewDecoder.Decode — способ обработки данных:
- json.Unmarshal — принимает уже загруженный в память срез байт (
[]byte). Весь JSON должен находиться в памяти целиком. - json.NewDecoder.Decode — читает данные из потока (
io.Reader), обрабатывая их по мере поступления. Не требует полной загрузки в память.
Когда использовать json.Unmarshal
Идеален для небольших JSON-объектов, которые уже есть в памяти, например:
// JSON из строки
jsonStr := `{"name": "Bob", "age": 25}`
var user User
json.Unmarshal([]byte(jsonStr), &user)
// JSON из файла (маленький файл)
data, _ := os.ReadFile("user.json")
json.Unmarshal(data, &user)
// JSON из HTTP-ответа (маленький ответ)
resp, _ := http.Get("https://api.example.com/user")
body, _ := io.ReadAll(resp.Body)
json.Unmarshal(body, &user)
Плюсы: простота, контроль над буфером, возможность повторного использования данных.
Когда использовать json.NewDecoder.Decode
Незаменим для потоковой обработки больших объёмов данных:
// Большой файл (несколько гигабайт)
file, _ := os.Open("huge_data.json")
defer file.Close()
var result LargeStruct
json.NewDecoder(file).Decode(&result)
// HTTP-ответ без буферизации (экономия памяти)
resp, _ := http.Get("https://api.example.com/bigdata")
defer resp.Body.Close()
var data BigResponse
json.NewDecoder(resp.Body).Decode(&data)
// Потоковая обработка массива объектов (JSON Stream)
decoder := json.NewDecoder(reader)
// Пропускаем открывающую скобку массива
decoder.Token()
for decoder.More() {
var item Item
decoder.Decode(&item)
// Обрабатываем элемент
}
Производительность и особенности
Тесты показывают, что json.Unmarshal может быть немного быстрее для маленьких JSON, так как не требует создания дополнительных объектов (декодер, буферы). Однако для больших данных json.NewDecoder выигрывает за счёт отсутствия необходимости хранить всё в памяти.
Важные нюансы:
json.Decoderпо умолчанию не допускает неизвестных полей в JSON. Чтобы разрешить, используйтеdecoder.DisallowUnknownFields().json.Decoderможет работать с несколькими JSON-объектами из одного потока (например, логи), в то время какUnmarshalожидает ровно один объект.- При работе с HTTP-ответами
json.NewDecoder(resp.Body)автоматически закрывает тело после декодирования, если используетсяdefer resp.Body.Close().
Рекомендация
Для 90% случаев (API-ответы до 1 МБ, конфиги, небольшие файлы) используйте json.Unmarshal — он проще и понятнее. Для потоков, больших данных или когда память критична — выбирайте json.NewDecoder.Decode.
Дополнительный совет: обработка ошибок и валидация
Ещё одно важное различие — как методы обрабатывают некорректный JSON:
- json.Unmarshal — при ошибке возвращает её сразу, но часть данных может быть записана в структуру. Например, если в JSON поле
age— строка, а структура ожидает число, тоageостанется нулевым. - json.Decoder.Decode — в некоторых случаях может прочитать часть потока и вернуть ошибку, оставив структуру частично заполненной. Это важно при потоковой обработке.
Рекомендую всегда проверять ошибки и, если нужно, использовать decoder.DisallowUnknownFields() для строгой валидации:
decoder := json.NewDecoder(reader)
decoder.DisallowUnknownFields()
err := decoder.Decode(&data)
if err != nil {
// Обработка ошибки — структура может быть в невалидном состоянии
}
Также помните: если вы используете json.Decoder с HTTP-ответом, всегда закрывайте тело ответа через defer resp.Body.Close(), чтобы избежать утечки ресурсов.