Dekodowanie JSON przy użyciu json.Unmarshal vs json.NewDecoder.Decode

202

Tworzę klienta API, w którym muszę zakodować ładunek JSON na żądanie i zdekodować treść JSON na podstawie odpowiedzi.

Przeczytałem kod źródłowy z kilku bibliotek i z tego, co widziałem, mam zasadniczo dwie możliwości kodowania i dekodowania łańcucha JSON.

Użyj json.Unmarshalprzekazywania całego ciągu odpowiedzi

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

lub za pomocą json.NewDecoder.Decode

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

W moim przypadku, gdy mamy do czynienia z odpowiedziami HTTP, które się implementują io.Reader, wydaje się, że druga wersja wymaga mniej kodu, ale skoro widziałam obie, zastanawiam się, czy jest jakaś preferencja, czy powinienem użyć rozwiązania, czy innej.

Co więcej, przyjęta odpowiedź na to pytanie mówi

Użyj json.Decoderzamiast json.Unmarshal.

ale nie podał przyczyny. Czy naprawdę powinienem unikać używania json.Unmarshal?

Simone Carletti
źródło
To żądanie ściągnięcia w GitHub zastąpiło wywołanie Unmarshal json.NewDecoder w celu „usunięcia bufora w dekodowaniu JSON”.
Matt
To zależy tylko od tego, który wkład jest wygodniejszy w użyciu. blog.golang.org/json-and-go podaje przykłady użycia obu technik.
rexposadas
15
IMO, ioutil.ReadAlljest prawie zawsze zły pomysł. Nie jest to związane z twoim celem, ale wymaga posiadania wystarczającej ilości ciągłej pamięci, aby przechowywać wszystko, co może zejść z potoku, nawet jeśli ostatnie 20 TB odpowiedzi przypada po ostatnim }w twoim JSON.
Dustin
@Dustin Możesz użyć, io.LimitReaderaby temu zapobiec.
Inanc Gumus

Odpowiedzi:

240

To zależy od twojego wkładu. Jeśli spojrzysz na implementację Decodemetody json.Decoder, buforuje ona całą wartość JSON w pamięci przed jej rozłączeniem na wartość Go. Więc w większości przypadków nie będzie już wydajniejszej pamięci (chociaż może to łatwo zmienić w przyszłej wersji języka).

Lepsza ogólna zasada jest następująca:

  • Użyj, json.Decoderjeśli dane pochodzą ze io.Readerstrumienia lub musisz zdekodować wiele wartości ze strumienia danych.
  • Użyj, json.Unmarshaljeśli masz już dane JSON w pamięci.

W przypadku czytania z żądania HTTP wybrałbym, json.Decoderponieważ oczywiście czytasz ze strumienia.

James Henstridge
źródło
25
Ponadto: sprawdzając kod źródłowy Go 1.3, możemy również dowiedzieć się, że do kodowania, jeśli użyjesz json.Encoder, użyje ponownie globalnej puli buforów (wspieranej przez nową pulę synchronizacji), co powinno znacznie zmniejszyć zapełnianie bufora jeśli kodujesz dużo JSON. Jest tylko jedna globalna pula, więc różni json. Enkoder dzieli ją. Powodem, dla którego nie można tego zrobić dla interfejsu json.Marshal, jest to, że bajty są zwracane do użytkownika, a użytkownik nie ma możliwości „zwrócenia” bajtów do puli. Więc jeśli robisz dużo kodowania, json.Marshal zawsze ma dość spore buforowanie.
Aktau
@Flimzy: jesteś pewien? Kod źródłowy nadal mówi, że wczytuje całą wartość do bufora przed dekodowaniem: github.com/golang/go/blob/master/src/encoding/json/… . Ta Bufferedmetoda umożliwia wyświetlanie dodatkowych danych, które zostały odczytane do bufora wewnętrznego po wartości.
James Henstridge
@JamesHenstridge: Nie, prawdopodobnie masz rację. Po prostu interpretowałem twoje oświadczenie inaczej niż zamierzałeś. Przepraszamy za zamieszanie.
Flimzy,