Decodificando JSON usando json.Unmarshal vs json.NewDecoder.Decode

201

Estoy desarrollando un cliente API donde necesito codificar una carga JSON a pedido y decodificar un cuerpo JSON a partir de la respuesta.

He leído el código fuente de varias bibliotecas y, por lo que he visto, tengo básicamente dos posibilidades para codificar y decodificar una cadena JSON.

Use json.Unmarshalpasar toda la cadena de respuesta

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

o usando json.NewDecoder.Decode

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

En mi caso, cuando se trata de respuestas HTTP que se implementan io.Reader, la segunda versión parece requerir menos código, pero como he visto ambas, me pregunto si hay alguna preferencia si debería usar una solución en lugar de la otra.

Además, la respuesta aceptada de esta pregunta dice

Por favor, use en json.Decoderlugar de json.Unmarshal.

pero no mencionó la razón. ¿Debo realmente evitar usar json.Unmarshal?

Simone Carletti
fuente
Esta solicitud de extracción en GitHub reemplazó una llamada a Unmarshal con json.NewDecoder para "eliminar el búfer en la decodificación JSON".
Matt
Solo depende de qué entrada le resulte más conveniente. blog.golang.org/json-and-go da ejemplos del uso de ambas técnicas.
rexposadas el
15
En mi opinión, casi siempre ioutil.ReadAlles algo incorrecto. No está relacionado con su objetivo, pero requiere que tenga suficiente memoria contigua para almacenar lo que pueda venir, incluso si los últimos 20 TB de respuesta son posteriores al último en su JSON. }
Dustin
@Dustin Puedes usar io.LimitReaderpara evitar eso.
Inanc Gumus

Respuestas:

239

Realmente depende de cuál sea su aporte. Si observa la implementación del Decodemétodo de json.Decoder, almacena el valor JSON completo en la memoria antes de descomponerlo en un valor Go. Por lo tanto, en la mayoría de los casos no será más eficiente en la memoria (aunque esto podría cambiar fácilmente en una versión futura del idioma).

Así que una mejor regla general es esta:

  • Úselo json.Decodersi sus datos provienen de una io.Readersecuencia, o si necesita decodificar múltiples valores de una secuencia de datos.
  • Úselo json.Unmarshalsi ya tiene los datos JSON en la memoria.

Para el caso de leer desde una solicitud HTTP, elegiría json.Decoderya que obviamente está leyendo desde una secuencia.

James Henstridge
fuente
25
Además: al inspeccionar el código fuente de Go 1.3, también podemos aprender que para la codificación, si usa un codificador json, reutilizará un grupo de búferes global (respaldado por el nuevo grupo de sincronización), lo que debería disminuir mucho la rotación del búfer. si estás codificando mucho json. Solo hay un grupo global tan diferente que json. El codificador lo comparte. La razón por la que esto no se pudo hacer para la interfaz json.Marshal es porque los bytes se devuelven al usuario y el usuario no tiene una forma de "devolver" los bytes al grupo. Entonces, si está codificando mucho, json.Marshal siempre tiene un poco de abandono del búfer.
Aktau
@Flimzy: ¿estás seguro? El código fuente aún dice que lee el valor completo en el búfer antes de la decodificación: github.com/golang/go/blob/master/src/encoding/json/… . El Bufferedmétodo está ahí para permitirle ver los datos adicionales que se leyeron en el búfer interno después del valor.
James Henstridge
@JamesHenstridge: No, probablemente tengas razón. Solo estaba interpretando su declaración de manera diferente de lo que pretendía. Disculpas por la confusión.
Flimzy