JSON
Il pacchetto encoding/json
Go include il pacchetto encoding/json nella libreria standard per lavorare con i dati JSON. Questo pacchetto permette di convertire strutture Go in JSON (marshal) e JSON in strutture Go (unmarshal) in modo semplice e sicuro.
Marshal: da Go a JSON
La funzione json.Marshal converte un valore Go in una slice di byte contenente JSON:
package main
import (
"encoding/json"
"fmt"
)
type Prodotto struct {
Nome string `json:"nome"`
Prezzo float64 `json:"prezzo"`
Attivo bool `json:"attivo"`
}
func main() {
p := Prodotto{
Nome: "Laptop",
Prezzo: 999.99,
Attivo: true,
}
// Marshal: struct -> JSON
datiJSON, err := json.Marshal(p)
if err != nil {
fmt.Println("Errore:", err)
return
}
fmt.Println(string(datiJSON))
// {"nome":"Laptop","prezzo":999.99,"attivo":true}
// MarshalIndent per JSON formattato
datiFormattati, _ := json.MarshalIndent(p, "", " ")
fmt.Println(string(datiFormattati))
}
Unmarshal: da JSON a Go
La funzione json.Unmarshal converte dati JSON in un valore Go:
package main
import (
"encoding/json"
"fmt"
)
type Utente struct {
Nome string `json:"nome"`
Email string `json:"email"`
Eta int `json:"eta"`
}
func main() {
jsonStr := `{"nome":"Mario Rossi","email":"mario@esempio.it","eta":30}`
var utente Utente
err := json.Unmarshal([]byte(jsonStr), &utente)
if err != nil {
fmt.Println("Errore:", err)
return
}
fmt.Printf("Nome: %s, Email: %s, Eta: %d\n",
utente.Nome, utente.Email, utente.Eta)
}
Struct Tags per JSON
Le struct tag controllano come i campi vengono serializzati e deserializzati:
type Configurazione struct {
// Nome personalizzato nel JSON
Host string `json:"host"`
// omitempty: ometti se il valore e zero
Porta int `json:"porta,omitempty"`
// Ignora completamente questo campo
Segreto string `json:"-"`
// Nome con trattino nel JSON
MaxConn int `json:"max-connessioni,omitempty"`
// Campo come stringa nel JSON (anche se e un numero)
Timeout int `json:"timeout,string"`
}
func main() {
cfg := Configurazione{
Host: "localhost",
Porta: 0, // Verra omesso (omitempty + valore zero)
Segreto: "abc123", // Verra ignorato
MaxConn: 100,
Timeout: 30,
}
dati, _ := json.MarshalIndent(cfg, "", " ")
fmt.Println(string(dati))
// {
// "host": "localhost",
// "max-connessioni": 100,
// "timeout": "30"
// }
}
Il tag omitempty omette il campo quando ha il valore zero del suo tipo (stringa vuota, 0, false, nil, slice vuota, ecc.).
Custom Marshaling
Possiamo personalizzare la serializzazione implementando le interfacce json.Marshaler e json.Unmarshaler:
package main
import (
"encoding/json"
"fmt"
"time"
)
type DataItaliana struct {
time.Time
}
func (d DataItaliana) MarshalJSON() ([]byte, error) {
formattata := d.Format("02/01/2006")
return json.Marshal(formattata)
}
func (d *DataItaliana) UnmarshalJSON(dati []byte) error {
var s string
if err := json.Unmarshal(dati, &s); err != nil {
return err
}
t, err := time.Parse("02/01/2006", s)
if err != nil {
return err
}
d.Time = t
return nil
}
type Evento struct {
Nome string `json:"nome"`
Data DataItaliana `json:"data"`
}
func main() {
evento := Evento{
Nome: "Conferenza Go",
Data: DataItaliana{time.Date(2026, 3, 15, 0, 0, 0, 0, time.UTC)},
}
dati, _ := json.MarshalIndent(evento, "", " ")
fmt.Println(string(dati))
// {
// "nome": "Conferenza Go",
// "data": "15/03/2026"
// }
// Unmarshal
jsonStr := `{"nome":"Workshop","data":"20/04/2026"}`
var e2 Evento
json.Unmarshal([]byte(jsonStr), &e2)
fmt.Println(e2.Nome, e2.Data.Format("2 January 2006"))
}
json.Decoder e json.Encoder
Per lavorare con stream (come il body di una richiesta HTTP), si usano json.Decoder e json.Encoder:
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
)
type Messaggio struct {
Tipo string `json:"tipo"`
Testo string `json:"testo"`
}
func main() {
// Decoder: legge da un io.Reader
jsonStream := `{"tipo":"info","testo":"Primo messaggio"}
{"tipo":"errore","testo":"Qualcosa e andato storto"}
{"tipo":"info","testo":"Terzo messaggio"}`
decoder := json.NewDecoder(strings.NewReader(jsonStream))
for decoder.More() {
var msg Messaggio
if err := decoder.Decode(&msg); err != nil {
fmt.Println("Errore:", err)
break
}
fmt.Printf("[%s] %s\n", msg.Tipo, msg.Testo)
}
// Encoder: scrive su un io.Writer
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
encoder.Encode(Messaggio{Tipo: "risposta", Testo: "OK"})
}
Il Decoder e piu efficiente di Unmarshal quando si legge da uno stream perche non carica tutto il JSON in memoria.
Gestire JSON con struttura sconosciuta
Quando non conosciamo la struttura del JSON in anticipo, possiamo usare map[string]interface{} o any:
package main
import (
"encoding/json"
"fmt"
)
func main() {
jsonStr := `{
"nome": "Mario",
"eta": 30,
"hobby": ["calcio", "lettura"],
"indirizzo": {
"citta": "Roma",
"cap": "00100"
}
}`
var dati map[string]any
json.Unmarshal([]byte(jsonStr), &dati)
// Accesso ai campi (con type assertion)
nome := dati["nome"].(string)
eta := dati["eta"].(float64) // I numeri JSON sono float64 di default
fmt.Printf("%s ha %.0f anni\n", nome, eta)
// Accesso a slice
hobby := dati["hobby"].([]any)
for _, h := range hobby {
fmt.Println("Hobby:", h.(string))
}
// Accesso a oggetti annidati
indirizzo := dati["indirizzo"].(map[string]any)
fmt.Println("Citta:", indirizzo["citta"])
// Usare json.RawMessage per posticipare il parsing
type Risposta struct {
Tipo string `json:"tipo"`
Dati json.RawMessage `json:"dati"`
}
}
Slice e Map
Go gestisce automaticamente slice e map nella serializzazione JSON:
package main
import (
"encoding/json"
"fmt"
)
func main() {
// Slice -> Array JSON
frutti := []string{"mela", "pera", "banana"}
dati, _ := json.Marshal(frutti)
fmt.Println(string(dati)) // ["mela","pera","banana"]
// Map -> Oggetto JSON
voti := map[string]int{
"matematica": 8,
"italiano": 7,
"scienze": 9,
}
dati, _ = json.Marshal(voti)
fmt.Println(string(dati))
// {"italiano":7,"matematica":8,"scienze":9}
}
Conclusione
Il pacchetto encoding/json di Go offre tutto il necessario per lavorare con i dati JSON. Le struct tag permettono un controllo preciso sulla serializzazione, le interfacce Marshaler e Unmarshaler consentono personalizzazioni avanzate, e il Decoder/Encoder sono ideali per lavorare con stream di dati. Per strutture JSON sconosciute, map[string]any e json.RawMessage offrono la flessibilita necessaria. Padroneggiare il pacchetto JSON e essenziale per qualsiasi sviluppatore Go che lavori con API web.