Da JSON a Go
Genera struct Go con tag json dal JSON: numeri tipizzati in base a come sono scritti, puntatori dove un valore può mancare — tutto nel browser.
type Root struct {
Orders []Order `json:"orders"`
HasMore bool `json:"has_more"`
}
type Order struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
CreatedAt string `json:"created_at"`
Price float64 `json:"price"`
Coupon *string `json:"coupon"`
GiftNote *string `json:"gift_note,omitzero"`
Shipping Shipping `json:"shipping"`
Tags []any `json:"tags"`
}
type Shipping struct {
City string `json:"city"`
Postcode json.RawMessage `json:"postcode"`
}
In ogni posizione qui sotto, ogni numero è stato letto come intero, quindi il tipo è int64. Un valore scritto con punto decimale o esponente, anche 10.0, non verrà decodificato lì.
Posizioni: 2
Order.IDOrder.UserID
In ogni posizione qui sotto i valori sono di più di un tipo (per esempio numeri e stringhe), quindi il tipo è json.RawMessage, che conserva ogni valore così come è scritto nel tuo JSON, perché il tuo programma lo decodifichi quando sa di che tipo è. Dove uno di questi tipi è un oggetto, la sua struct resta comunque nell’output, per decodificarvi quel valore.
Posizioni: 1
Shipping.Postcode
In ogni posizione qui sotto non c’era nulla da cui dedurre un tipo: lì si è visto solo null, l’array era sempre vuoto, l’oggetto non aveva chiavi, oppure il valore è annidato troppo in profondità perché questo strumento lo segua. Quindi il tipo è any, che accetta qualsiasi valore, oppure map[string]any dove l’oggetto non aveva chiavi, che accetta qualsiasi oggetto o null e nient’altro.
Posizioni: 1
Order.Tags[]
Struct che decodificano il JSON da cui provengono
Incolla un campione di JSON e questa pagina scrive per esso le dichiarazioni Go: una struct con nome per ogni oggetto del campione, e su ogni campo un tag json che nomina la chiave che quel campo legge. Una promessa decide tutto ciò che segue. Messe in un programma compilato con Go 1.27, le dichiarazioni decodificano senza errore, tramite "encoding/json", il JSON che hai incollato, e ogni chiave viene letta in un campo, tranne una chiave che nessun tag di una struct può contenere, che resta fuori dalla struct. Dove Go non lascia che quella promessa regga, o costringe la pagina a scegliere qualcosa che i tuoi dati non hanno deciso, un avviso sotto l’output dice dove, con una rara eccezione descritta nella sezione sui tag.
La forma in sé viene ricavata prima che venga scritto qualunque codice Go, dalla stessa lettura del tuo JSON che alimenta la pagina Da JSON a TypeScript. Unire gli elementi di un array, notare le chiavi che soltanto alcuni di essi hanno, separare null da una chiave che non è mai stata inviata e dare un nome a ogni oggetto trovato dentro un altro: la guida di quella pagina copre tutto questo, quindi niente di ciò viene ripetuto più sotto. Ciò che segue è la parte di Go — un tipo per ogni numero, i puntatori, un tipo per una chiave con valori di tipi diversi, i nomi dei campi, i tag, e le domande sollevate dagli avvisi.
Tre tipi numerici, scelti in base a come è scritto ogni numero
Un browser che legge JSON fa di 10 e 10.0 lo stesso numero. Go no: un campo "int64" rifiuta un numero scritto con un punto decimale o un esponente — 10.0, 1e3, perfino -0.0 — mentre accetta 11 e -0. Così la pagina non sceglie un tipo numerico dai soli valori. Chiede al parser del browser stesso come è stato scritto ogni numero, e decide in base a quello:
- Dove ogni numero di una posizione è scritto come numero intero, senza punto decimale né esponente, il campo è "int64", e l’avviso sui numeri interi lo nomina.
- Ogni altro numero è "float64", quindi un prezzo scritto 10.0 è "float64" anche quando ogni prezzo del campione è tondo. Il modulo "json" di Python scrive un float con valore intero proprio in quel modo, e questo fa di un’API servita da Python il posto abituale in cui incontrarlo.
- "json.Number" è riservato ai numeri che nessuno dei due tipi contiene esattamente: un intero oltre l’uno o l’altro estremo dell’intervallo di "int64", un numero semplicemente troppo grande per un "float64", come 1e999, e un intero troppo lungo per un "float64" accanto a una frazione, come in [9007199254740993, 1.5]. Conserva ciascuno di essi esattamente, e il tuo programma lo converte dove usa il valore.
Un intero lungo in mezzo ad altri interi non ha bisogno di questa cautela: [9007199254740993, 1] è "[]int64". Una frazione con più cifre di quante ne conservi un "float64" resta "float64" e viene nominata in un avviso tutto suo, perché, decodificato e codificato di nuovo, 0.30000000000000001 torna come 0.3.
Leggere come è stato scritto un numero richiede un browser che lo indichi. Uno che non lo indica legge ogni numero dal solo valore, quindi lì 10.0 sembra intero e il suo campo diventa "int64", un campo che non decodificherà quel JSON; la pagina conta i numeri che non ha potuto distinguere dagli interi e mostra dove si trova il primo. Un browser così non vede neppure le cifre che un "float64" scarta, quindi lì un intero lungo accanto a una frazione è di tipo "float64", e una frazione che viene arrotondata non viene nominata.
Puntatori dove un valore può mancare
Una chiave che alcuni oggetti omettono, o impostano a null, è un puntatore: "*string", "*int64", oppure un puntatore alla struct scritta per un oggetto. Gli elementi di un array che contiene null sono anch’essi puntatori, quindi [1, null] è "[]*int64". Una chiave omessa in alcuni oggetti è inoltre marcata con "omitzero" nel suo tag. Con tutti e due, un campo può dire se è arrivato un valore, e codificare di nuovo la struct decodificata omette di nuovo una chiave che il campione non ha mai fatto altro che omettere, e riscrive come null una chiave che non ha mai fatto altro che impostare a null.
Una slice, una map, "any" e "json.RawMessage" non prendono un puntatore, perché ciascuno è già nil quando non vi è stato decodificato nulla. Una slice riceve comunque "omitzero" dove la sua chiave può mancare, e un array che era presente ma vuoto viene riscritto come [].
Una distinzione non sopravvive. Una chiave che manca in alcuni oggetti ed è null in altri è un unico nil in Go, quindi il tuo programma non può distinguere i due casi, e codificare di nuovo la struct omette la chiave anche dove il campione aveva null; la pagina nomina ogni campo a cui succede. Una chiave con valori di tipi misti mantiene la differenza, perché un "json.RawMessage" conserva un null come i byte null.
json.RawMessage per i tipi misti, e any dove non si è visto nulla
Quando i valori di una chiave non concordano nel tipo — un numero in un punto, una stringa o un oggetto in un altro — il campo è "json.RawMessage": i byte del valore stesso, lasciati al tuo programma perché li decodifichi dopo aver guardato di che tipo è ciò che è arrivato. Un null tra di essi non cambia nulla. Dove uno dei tipi è un oggetto, la struct estratta per esso viene comunque stampata accanto alle altre. Un "any" accetterebbe anch’esso ogni tipo, e non viene usato per un miscuglio, perché un numero decodificato in un "any" diventa un "float64" e perde tutto ciò che un "float64" non può contenere.
Alcune parti di un campione non offrono alcun valore da cui imparare: una chiave che non ha mai contenuto altro che null, un array che era vuoto ogni volta, un oggetto senza chiavi, e un valore sepolto più in profondità di quanto legga l’inferenza. In Go la chiave null e il valore sepolto ricevono "any", gli elementi dell’array vuoto "[]any" e l’oggetto "map[string]any". Un "any" accetta ogni tipo di valore. La map accetta un oggetto o null, e una stringa, un numero, un array o un booleano al suo posto fanno fallire la decodifica.
Oltre quella profondità, due cose non vengono dette da nessun avviso. Una chiave che lì manca in alcuni oggetti ed è null in altri viene nominata soltanto come posizione in cui non c’era nulla da cui dedurre un tipo, e Go, riscrivendola, la omette dove era null. E un intero più lungo di quanto contenga un "float64" torna arrotondato, con 9007199254740993 che torna come 9007199254740992, perché la posizione è un "any".
Nomi dei campi nello stile di Go, nomi delle struct condivisi con la pagina TypeScript
Il nome di un campo è il modo in cui Go scrive la sua chiave. La chiave viene divisa in parole a ogni carattere che non è né una lettera né una cifra, e ovunque una lettera minuscola sia seguita da una maiuscola; poi le parole vengono unite, ciascuna con l’iniziale maiuscola, quindi "user_name", "last-name" e "firstName" diventano UserName, LastName e FirstName. Una parola della lista predefinita di sigle di staticcheck si scrive in maiuscolo — "id" diventa ID, "api_key" diventa APIKey, "video_url" diventa VideoURL — ma conta soltanto una parola intera, quindi "idle" diventa Idle e il plurale "ids" diventa Ids. Una chiave scritta interamente in maiuscolo viene letta come parole: "USER_ID" diventa UserID.
Le lettere proprie della chiave restano, quindi "имя" diventa Имя. Dove un nome non comincerebbe con una maiuscola — una chiave in un sistema di scrittura senza maiuscole come "名前", una chiave che comincia con una cifra come "1st", o una prima lettera la cui maiuscola non sta in una sola lettera, come "ß" — riceve una X davanti, come X名前, X1st e Xß, e il campo legge comunque la sua chiave. I segni combinanti vengono tolti dal nome e mantenuti nel tag. Una chiave senza alcuna lettera né cifra, come "@", si chiama Field, e un nome già usato nella stessa struct riceve un numero: "user_id", "userId" e "USER_ID" insieme diventano UserID, UserID2 e UserID3.
Lo stile di Go arriva fino ai nomi dei campi e si ferma lì: ogni struct conserva il nome che l’inferenza ha scelto per il suo oggetto, quindi le dichiarazioni qui e le interfacce della pagina TypeScript si chiamano allo stesso modo, e un campo può essere scritto diversamente dalla struct in esso contenuta. Come viene scelto quel nome spetta alla guida della pagina TypeScript.
Tag per Go 1.27, e le chiavi che nessun tag può contenere
Il tag di ogni campo nomina la sua chiave esattamente, come in json:"user_id", con ,omitzero dopo la chiave dove la chiave può mancare. Un carattere di controllo in una chiave viene scritto con la sequenza di escape usata da Go stesso, e la chiave "-" viene scritta json:"-,", una forma che Go legge come quella stessa chiave.
I tag sono scritti per Go 1.27. Go 1.26 legge meno chiavi attraverso un tag: una chiave che contiene qualcosa oltre a lettere, cifre, lo spazio ASCII e un insieme di segni di punteggiatura ASCII — "Price (€)", "temp °C", un’emoji, un carattere di controllo, un accento combinante — lì non viene letta, e la pagina nomina ogni campo così. Questa metà viene controllata con Go 1.27 compilato con "GOEXPERIMENT=nojsonv2", che legge i tag come Go 1.26 ma con le tabelle Unicode più recenti di Go 1.27, quindi una chiave che contiene una lettera che le tabelle più vecchie di Go 1.26 non hanno viene letta in quel controllo e non genera alcun avviso.
Una chiave che contiene una virgola, una barra rovesciata, delle virgolette doppie, un apostrofo o un accento grave, oppure la chiave vuota, è una chiave che nessun tag di una struct può contenere, quindi non riceve alcun campo. Il tuo JSON viene comunque decodificato: "json.Unmarshal" salta quella chiave senza lamentarsi, anche se un "json.Decoder" impostato con "DisallowUnknownFields" si ferma lì. L’avviso elenca una chiave così attraverso la sua struct e la chiave citata nel modo in cui Go cita una stringa, come Order["note,internal"], e un oggetto sotto una chiave così ha comunque la sua struct stampata. Un caso resta senza avviso: due chiavi che differiscono soltanto per un surrogato isolato, una sequenza di escape che non rappresenta alcun carattere, sono una stessa chiave per Go, e nessuno dei loro campi viene riempito.
Solo dichiarazioni, nella disposizione di gofmt
L’output contiene dichiarazioni di tipo e nient’altro: nessuna clausola package e nessun import, quindi va in un file che hai già, sotto la clausola package propria di quel file. Dove usa "json.RawMessage" o "json.Number", il file importa anche "encoding/json", e quella è la riga lasciata a te o al tuo editor. La disposizione è quella di gofmt stesso — una tabulazione prima di ogni campo, e i nomi, i tipi e i tag di ogni struct in colonne allineate — quindi gofmt la lascia esattamente com’è. Il tipo della radice viene stampato per primo, ogni oggetto è un tipo con nome anziché una struct scritta dentro il campo che la contiene, e un JSON che al livello superiore è un array o un singolo valore è anch’esso un tipo con nome, come "type Root []RootItem", così c’è sempre un tipo in cui decodificarlo.
Che cosa ti chiedono di controllare gli avvisi
Ogni tipo di decisione imposta da Go riceve una voce sotto le dichiarazioni, e la voce raccoglie ogni punto a cui si applica, quindi un campione con molti campi di numeri interi riceve una voce e non una per campo. I punti si scrivono come li scrivono le dichiarazioni — Order.UserID è un campo, Order.Tags[] gli elementi di un array, e la radice è il suo nome e basta — tranne in due voci che invece rimandano al tuo JSON per riga e colonna: una chiave scritta due volte, e i numeri che questo browser non ha potuto distinguere dagli interi. Nessuna di esse modifica le dichiarazioni. Leggi ciascuna come una domanda sui tuoi dati:
- Numeri interi di tipo "int64". Ogni numero lì è stato scritto come intero. Se un valore che arriva più tardi può avere un punto decimale o un esponente, come può averlo un prezzo o una misura, non verrà decodificato, quindi rendi quel campo "float64"; un ID o un conteggio può restare com’è.
- "json.Number". Nessun altro tipo numerico contiene quei valori esattamente. Converti ciascuno dove il tuo programma lo usa, oppure, se sai che i valori reali stanno in un tipo più stretto, cambia tu il campo.
- Una frazione che "float64" arrotonda. Un numero lì ha più cifre di quante ne conservi un "float64", quindi il tuo programma vede un valore vicino anziché quello scritto. Dove conta ogni cifra, rendi il campo "json.Number".
- Tipi misti conservati come "json.RawMessage". Guarda ogni valore quando arriva e decodificalo secondo il tipo che si rivela avere; dove uno dei tipi è un oggetto, la sua struct resta comunque nell’output, per decodificarvi quel valore.
- Nulla da cui dedurre un tipo. Il campo è "any", o una map di "any", perché lì il campione non conteneva alcun valore da cui imparare. Sostituiscilo con il tipo che sai essere quello di quel campo, oppure converti di nuovo da un JSON in cui ha valori reali.
- Mancante in alcuni oggetti, null in altri. Go conserva entrambi i casi come un nil, quindi la chiave viene omessa quando la struct viene codificata di nuovo. Conta soltanto se ciò che legge il tuo output tratta un null diversamente da una chiave che non c’è.
- Chiavi che Go 1.26 non legge. Compila con Go 1.27, oppure rinomina quelle chiavi dove vengono prodotte; con Go 1.26 restano non lette.
- Chiavi che restano fuori dalla struct. Non hanno un campo. Per leggerne una, decodifica l’oggetto in una "map[string]json.RawMessage" oppure scrivi un metodo "UnmarshalJSON" per la struct, come suggerisce l’avviso stesso.
- Una chiave scritta due volte. I tipi seguono l’ultima copia della chiave, ma Go decodifica ogni copia a turno, quindi una copia precedente che il tipo non può contenere, come una stringa dove l’ultima copia è un numero, fa sì che la decodifica restituisca un errore, e una chiave che compare soltanto in una copia precedente di un oggetto non viene letta. Correggi il JSON alla riga e alla colonna indicate.
- Questo browser non distingue 10 da 10.0. Non indica come è scritto un numero, quindi un campo di tipo "int64" a causa di un valore scritto con un punto decimale o un esponente non decodificherà il tuo JSON. Controlla i campi i cui valori sono scritti così, oppure converti il JSON in un browser che lo indichi.
Non c’è alcun avviso su date, formati o insiemi fissi di valori, perché la pagina non ne indovina mai uno da una stringa: "created_at" nell’esempio resta un "string", qualunque aspetto abbia.
Domande frequenti
- Perché il mio prezzo è float64 se ogni prezzo nel mio JSON è un numero tondo?
- Perché ogni prezzo è scritto con un punto decimale, come 10.0, e un campo "int64" rifiuta un numero scritto così, tondo o no. La pagina legge come è scritto ogni numero anziché soltanto il suo valore, quindi il campo è "float64" e il tuo JSON viene decodificato. Se il campo fosse di tipo "int64" in base ai soli valori, la decodifica si fermerebbe al primo prezzo.
- Quando un numero esce come json.Number?
- Quando uno dei suoi valori è un intero oltre l’intervallo di "int64", un numero troppo grande per un "float64", oppure un intero lungo accanto a una frazione, che un "float64" arrotonderebbe. "json.Number" li conserva tutti esatti, e l’avviso sotto l’output elenca ogni campo per cui è stato scelto.
- Perché alcuni campi sono puntatori, e che cosa ci fa omitzero nei tag?
- Un campo è un puntatore dove la sua chiave manca in alcuni oggetti o è null in alcuni, così un nil può dire che non è arrivato alcun valore. "omitzero" va dove la chiave manca in alcuni oggetti: codificare di nuovo la struct omette allora quella chiave, come faceva il tuo JSON, invece di scrivere null al suo posto.
- Perché json.RawMessage per una chiave con tipi misti, e non any?
- Un "json.RawMessage" conserva i byte di ogni valore finché il tuo programma non ha deciso come leggerli. Un "any" accetterebbe anch’esso i valori, ma un numero decodificato in un "any" è un "float64", e un intero lungo lì perde le sue ultime cifre.
- Perché alcuni nomi di campo cominciano con una X?
- Perché altrimenti il nome non comincerebbe con una maiuscola: la chiave è scritta in un sistema di scrittura senza maiuscole, comincia con una cifra, oppure comincia con una lettera la cui maiuscola non sta in una sola lettera. La X mantiene nel nome le lettere proprie della chiave, e il campo legge ancora la sua chiave — X名前 legge "名前".
- Di quale versione di Go hanno bisogno le struct?
- Sono scritte e controllate per Go 1.27. Il modo in cui Go 1.26 legge i tag viene controllato a sua volta, tramite un sostituto: Go 1.26 non legge una chiave che contiene un carattere al di fuori di lettere, cifre, lo spazio ASCII e un insieme di segni di punteggiatura ASCII, come un simbolo di valuta o un’emoji, e la pagina nomina ogni campo la cui chiave ne è toccata — tranne una chiave che contiene una lettera più recente delle tabelle Unicode di Go 1.26: una chiave così non la vedono né la pagina né quel controllo.
- Perché una delle mie chiavi manca dalla struct?
- Perché la chiave contiene una virgola, una barra rovesciata, delle virgolette doppie, un apostrofo o un accento grave, oppure è vuota, e nessun tag di una struct può contenere una chiave così. Un avviso la elenca attraverso la sua struct e la chiave stessa, come Root["a,b"]; il resto del tuo JSON viene comunque decodificato, e l’avviso dice come leggere quella chiave in un altro modo.
- È sicuro incollare una risposta che contiene token o password?
- Sì. Ogni passaggio avviene dentro questa pagina, sul tuo computer, e la risposta che incolli non va da nessun’altra parte: nessun server la riceve e nulla ne conserva una copia. Le dichiarazioni che ne derivano contengono soltanto nomi di campo, tag e tipi Go, quindi un token o una password nel JSON non vi lascia alcuna traccia oltre a un campo "string" che prende il nome dalla sua chiave.
Strumenti correlati
- Da JSON a TypeScript
Dove i valori di una chiave sono di più di un tipo, questa pagina conserva ciascuno così come è scritto nel tuo JSON perché il tuo programma lo decodifichi, dato che Go non ha union. Quella pagina scrive la stessa forma come tipi TypeScript con gli stessi nomi di tipo, con una union in quel punto, e la sua guida spiega come quella forma viene ricavata.
- Da JSON a Zod
Go non ha un tipo che sia semplicemente un numero JSON, quindi dove ogni numero è scritto come numero intero questa pagina dà al campo un tipo intero, e un valore scritto con un punto decimale non verrà decodificato lì. Quella pagina scrive la stessa forma come schemi Zod con gli stessi nomi di tipo, e uno schema controlla i dati ogni volta che arrivano e accetta anche una frazione in un campo così.
- Tester JSONPath
Testa query JSONPath (RFC 9535) su JSON.
- Generatore di tabelle Markdown
Crea e allinea tabelle Markdown da CSV, TSV o JSON.