encoding/json.NewDecoder în Go

Creează un Decoder care citește JSON dintr-un io.Reader (corpul unei cereri, un fișier); Decode citește o valoare.

Sintaxă
func NewDecoder(r io.Reader) *Decoder
func (dec *Decoder) Decode(v any) error

Ce face encoding/json.NewDecoder

json.NewDecoder(r) creează un *json.Decoder care citește JSON direct dintr-un flux: r.Body la server, resp.Body la client, un fișier deschis. dec.Decode(&v) citește o valoare JSON și o pune în v, cu aceleași reguli ca Unmarshal (pointer, etichete, chei ignorate).

Pentru că citește din flux, poate primi mai multe valori una după alta: fiecare Decode o ia pe următoare, iar la sfârșit returnează io.EOF. Așa citești fișiere JSON Lines (câte un obiect pe rând) sau răspunsuri foarte mari fără să le ții întregi în memorie.

Diferența față de Unmarshal: Decode se oprește după prima valoare și nu verifică ce urmează. Dacă vrei să refuzi un corp cu date în plus, verifică după decodare că dec.More() este false sau că un nou Decode dă io.EOF.

Parametri

Parametru Descriere
r Sursa: orice io.Reader.

Ce returnează

NewDecoder: un *json.Decoder. Decode: nil, io.EOF la sfârșitul fluxului, sau eroarea de citire.

Exemple

Exemplul 1: Corpul JSON al unei cereri

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
)

type Inscriere struct {
	Nume  string `json:"nume"`
	Email string `json:"email"`
	Curs  string `json:"curs"`
}

func inscrie(w http.ResponseWriter, r *http.Request) {
	var in Inscriere
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
		http.Error(w, "JSON invalid: "+err.Error(), http.StatusBadRequest)
		return
	}
	fmt.Fprintf(w, "%s (%s) s-a înscris la %s", in.Nume, in.Email, in.Curs)
}

func main() {
	for _, corp := range []string{
		`{"nume":"Ana","email":"ana@example.ro","curs":"Go pentru începători"}`,
		`{"nume":"Ion","email":}`,
	} {
		rec := httptest.NewRecorder()
		inscrie(rec, httptest.NewRequest("POST", "/inscriere", strings.NewReader(corp)))
		fmt.Println(rec.Code, strings.TrimSpace(rec.Body.String()))
	}
}

Rezultat

200 Ana (ana@example.ro) s-a înscris la Go pentru începători
400 JSON invalid: invalid character '}' looking for beginning of value

Exemplul 2: Mai multe valori dintr-un flux (JSON Lines)

Fiecare Decode citește următorul obiect; bucla se oprește la io.EOF.

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"strings"
)

type Vanzare struct {
	Produs string  `json:"produs"`
	Suma   float64 `json:"suma"`
}

func main() {
	jurnal := `{"produs":"cafea","suma":65}
{"produs":"ceai","suma":14}
{"produs":"cafea","suma":32.5}
`
	dec := json.NewDecoder(strings.NewReader(jurnal))
	total := map[string]float64{}
	for {
		var v Vanzare
		err := dec.Decode(&v)
		if errors.Is(err, io.EOF) {
			break
		}
		if err != nil {
			fmt.Println("eroare:", err)
			return
		}
		total[v.Produs] += v.Suma
	}
	fmt.Println(total)
}

Rezultat

map[cafea:97.5 ceai:14]

Greșeli frecvente

  • Citești tot corpul cu io.ReadAll și apoi faci Unmarshal, fără limită de mărime. Pe server pune întâi http.MaxBytesReader, apoi folosește Decoder.
  • Te aștepți ca Decode să dea eroare pentru {"a":1} gunoi: citește prima valoare și se oprește. Verifică restul cu dec.More().

Comenzi înrudite

  • encoding/json.Decoder.More: Spune dacă mai urmează un element în lista sau obiectul citit; cu Token permite citirea element cu element a unei liste mari.
  • encoding/json.Decoder.DisallowUnknownFields: Face ca Decode să returneze eroare când JSON-ul are chei care nu există în structura țintă.
  • encoding/json.Unmarshal: Citește JSON și completează o valoare Go (structură, map, slice) dată prin pointer; potrivește cheile după etichetele json.
  • encoding/json.NewEncoder: Creează un Encoder care scrie JSON direct într-un io.Writer (răspuns HTTP, fișier, ecran); Encode scrie o valoare.
  • net/http.MaxBytesReader: Limitează mărimea corpului unei cereri: citirea peste n octeți returnează eroare *http.MaxBytesError.