00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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.