Декодирование JSON с использованием json.Unmarshal vs json.NewDecoder.Decode


203

Я разрабатываю клиент API, в котором мне нужно кодировать полезную нагрузку JSON по запросу и декодировать тело JSON из ответа.

Я прочитал исходный код из нескольких библиотек и из того, что я видел, у меня есть две возможности кодирования и декодирования строки JSON.

Использовать json.Unmarshalпередачу всей строки ответа

data, err := ioutil.ReadAll(resp.Body)
if err == nil && data != nil {
    err = json.Unmarshal(data, value)
}

или используя json.NewDecoder.Decode

err = json.NewDecoder(resp.Body).Decode(value)

В моем случае, когда речь идет о HTTP-ответах, которые реализуются io.Reader, вторая версия, похоже, требует меньше кода, но, поскольку я видел оба, я задаюсь вопросом, есть ли какое-либо предпочтение, следует ли мне использовать решение, а не другое.

Более того, принятый ответ на этот вопрос говорит

Пожалуйста, используйте json.Decoderвместо json.Unmarshal.

но это не упомянуло причину. Должен ли я действительно избегать использования json.Unmarshal?


Этот запрос на GitHub заменил вызов Unmarshal на json.NewDecoder для «удаления буфера в JSON-декодировании».
Мэтт

Это зависит только от того, какой ввод вам удобнее использовать. blog.golang.org/json-and-go приводит примеры использования обоих методов.
rexposadas

15
ИМО, ioutil.ReadAllэто почти всегда неправильно , что нужно делать. Это не связано с вашей целью, но требует, чтобы у вас было достаточно непрерывной памяти для хранения всего, что может идти по трубе, даже если последние 20 ТБ ответа идут после последних }в вашем JSON.
Дастин

@ Dustin Вы можете использовать, io.LimitReaderчтобы предотвратить это.
Inanc Gumus

Ответы:


240

Это действительно зависит от вашего вклада. Если вы посмотрите на реализацию Decodeметода json.Decoder, он буферизует все значение JSON в памяти перед тем, как демонтировать его в значение Go. Так что в большинстве случаев он не будет более эффективным в использовании памяти (хотя это может легко измениться в будущей версии языка).

Итак, лучшее эмпирическое правило таково:

  • Используйте, json.Decoderесли ваши данные поступают из io.Readerпотока, или вам нужно декодировать несколько значений из потока данных.
  • Используйте, json.Unmarshalесли у вас уже есть данные JSON в памяти.

В случае чтения из HTTP-запроса я бы выбрал, json.Decoderпоскольку вы, очевидно, читаете из потока.


25
Также: изучив исходный код Go 1.3, мы также можем узнать, что для кодирования, если вы используете json.Encoder, он будет повторно использовать глобальный буферный пул (поддерживаемый новым sync.Pool), что должно значительно уменьшить отток буфера если вы кодируете много JSON. Есть только один глобальный пул, так что разные json.Encoder делятся им. Причина, по которой это не может быть сделано для интерфейса json.Marshal, заключается в том, что байты возвращаются пользователю, и у пользователя нет способа «вернуть» байты в пул. Так что, если вы делаете много кодирования, json.Marshal всегда имеет небольшой буферный отток.
Актау

@Flimzy: ты уверен? Исходный код все еще говорит, что он читает все значение в буфер перед декодированием: github.com/golang/go/blob/master/src/encoding/json/… . Этот Bufferedметод позволяет вам увидеть любые дополнительные данные, которые были прочитаны во внутренний буфер после значения.
Джеймс Хенстридж

@JamesHenstridge: Нет, вы, вероятно, правы. Я просто интерпретировал ваше заявление иначе, чем вы хотели. Извиняюсь за путаницу.
Хлипкий
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.