JSON para Go

Gera structs Go com tags json a partir de JSON: números tipados pela forma como são escritos, ponteiros onde um valor pode faltar — tudo no navegador.

Entrada
Go
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"`
}
  • Em cada posição abaixo, todo número foi lido como número inteiro, então o tipo é int64. Um valor escrito com ponto decimal ou expoente, mesmo 10.0, não será decodificado nele.

    Posições: 2

    • Order.ID
    • Order.UserID
  • Em cada posição abaixo, os valores são de mais de um tipo (por exemplo, números e strings), então o tipo é json.RawMessage, que guarda cada valor exatamente como está escrito no seu JSON, para que seu programa o decodifique quando souber de que tipo ele é. Onde um desses tipos é um objeto, a struct dele continua na saída, para decodificar esse valor nela.

    Posições: 1

    • Shipping.Postcode
  • Em cada posição abaixo, não havia nada de onde inferir um tipo: ali só apareceu null, o array estava sempre vazio, o objeto não tinha chaves ou o valor está aninhado fundo demais para esta ferramenta acompanhar. Então o tipo é any, que aceita qualquer valor, ou map[string]any onde o objeto não tinha chaves, que aceita qualquer objeto ou null e nada mais.

    Posições: 1

    • Order.Tags[]

Structs que decodificam o JSON de onde vieram

Cole uma amostra de JSON e esta página escreve declarações Go para ela: uma struct com nome para cada objeto da amostra e, em cada campo, uma tag json que nomeia a chave que esse campo lê. Uma promessa decide tudo o que vem abaixo. Colocadas em um programa compilado com o Go 1.27, as declarações decodificam, por meio de "encoding/json" e sem erro, o JSON que você colou, e cada chave é lida em um campo, exceto uma chave que nenhuma tag de struct consegue carregar: uma chave assim fica de fora da struct. Onde o Go não deixa essa promessa se cumprir, ou obriga a página a escolher algo que os seus dados não decidiram, um aviso abaixo da saída diz onde, com uma rara exceção, descrita na seção sobre as tags.

A forma em si é calculada antes que qualquer Go seja escrito, pela mesma leitura do seu JSON, a que alimenta a página JSON para TypeScript. Mesclar os elementos de um array, notar as chaves que só alguns deles têm, separar null de uma chave que nunca foi enviada e dar nome a cada objeto encontrado dentro de outro: o guia daquela página cobre tudo isso, então nada disso se repete abaixo. O que vem a seguir é a parte do Go — um tipo para cada número, ponteiros, um tipo para uma chave com valores de tipos diferentes, nomes de campo, tags e as perguntas levantadas pelos avisos.

Três tipos numéricos, escolhidos pela forma como cada número é escrito

Um navegador que lê JSON transforma 10 e 10.0 no mesmo número. O Go não: um campo "int64" rejeita um número escrito com ponto decimal ou expoente — 10.0, 1e3, até -0.0 —, enquanto aceita 11 e -0. Por isso a página não escolhe um tipo numérico só a partir dos valores. Ela pergunta ao próprio analisador do navegador como cada número foi escrito, e decide a partir disso:

  • Onde todo número de uma posição está escrito como número inteiro, sem ponto decimal e sem expoente, o campo é "int64", e o aviso de números inteiros o nomeia.
  • Qualquer outro número é "float64", então um preço escrito 10.0 é "float64" mesmo quando todo preço da amostra é redondo. O módulo "json" do Python escreve um float de valor inteiro exatamente assim, o que faz de uma API servida por Python o lugar onde isso costuma aparecer.
  • "json.Number" fica reservado para os números que nenhum dos dois tipos guarda exatamente: um inteiro além de qualquer um dos extremos do intervalo de "int64", um número grande demais para caber sequer em um "float64", como 1e999, e um inteiro longo demais para um "float64" ao lado de uma fração, como em [9007199254740993, 1.5]. Ele guarda cada um deles exatamente, e o seu programa o converte onde usa o valor.

Um inteiro longo entre outros inteiros não precisa desse cuidado: [9007199254740993, 1] é "[]int64". Uma fração com mais dígitos do que um "float64" guarda continua "float64" e é nomeada em um aviso próprio, já que, decodificado e codificado de novo, 0.30000000000000001 volta como 0.3.

Ler como um número foi escrito exige um navegador que informe isso. Um navegador que não informa lê cada número só a partir do valor, então ali 10.0 parece inteiro e o campo dele vira "int64", um campo que não vai decodificar esse JSON; a página conta os números que não conseguiu distinguir de inteiros e mostra onde está o primeiro. Um navegador assim também não vê os dígitos que um "float64" descarta, então ali um inteiro longo ao lado de uma fração recebe o tipo "float64", e uma fração que é arredondada não é nomeada.

Ponteiros onde um valor pode faltar

Uma chave que alguns objetos omitem, ou definem como null, é um ponteiro: "*string", "*int64" ou um ponteiro para a struct escrita para um objeto. Os elementos de um array que contém null também são ponteiros, então [1, null] é "[]*int64". Uma chave que falta em alguns objetos também é marcada com "omitzero" na tag. Com as duas coisas no lugar, um campo consegue dizer se um valor chegou, e codificar de novo a struct decodificada omite outra vez uma chave que a amostra nunca fez senão omitir, e escreve de volta como null uma chave que ela nunca fez senão definir como null.

Um slice, um map, "any" e "json.RawMessage" não levam ponteiro, já que cada um já é nil quando nada foi decodificado nele. Mesmo assim, um slice recebe "omitzero" onde a chave dele pode faltar, e um array que estava presente mas vazio é escrito de volta como [].

Uma distinção não sobrevive. Uma chave que falta em alguns objetos e é null em outros é um único nil no Go, então o seu programa não consegue distinguir os dois casos, e codificar a struct de novo omite a chave mesmo onde a amostra tinha null; a página nomeia cada campo em que isso acontece. Uma chave de tipos mistos mantém a diferença, porque um "json.RawMessage" guarda um null como os bytes null.

json.RawMessage para tipos mistos, e any onde nada foi visto

Quando os valores de uma chave discordam no tipo — um número em um lugar, uma string ou um objeto em outro —, o campo é "json.RawMessage": os próprios bytes do valor, deixados para o seu programa decodificar depois de ver de que tipo é o que chegou. Um null entre eles não muda nada. Onde um dos tipos é um objeto, a struct extraída para ele continua sendo impressa ao lado das outras. Um "any" também aceitaria todos os tipos, e não é usado para uma mistura, porque um número decodificado em um "any" vira um "float64" e perde tudo o que um "float64" não consegue guardar.

Algumas partes de uma amostra não oferecem valor nenhum de onde aprender: uma chave que nunca teve senão null, um array que esteve vazio todas as vezes, um objeto sem chaves e um valor enterrado mais fundo do que a inferência lê. No Go, a chave com null e o valor enterrado recebem "any", os elementos do array vazio recebem "[]any" e o objeto recebe "map[string]any". Um "any" aceita qualquer tipo de valor. O map aceita um objeto ou null, e uma string, um número, um array ou um booleano no lugar dele faz a decodificação falhar.

Além dessa profundidade, duas coisas ficam sem ser ditas pelos avisos. Uma chave ali que falta em alguns objetos e é null em outros só é nomeada como uma posição sem nada de onde inferir um tipo, e, onde ela era null, o Go a omite ao escrevê-la de volta. E volta arredondado um inteiro mais longo do que um "float64" guarda: 9007199254740993 volta como 9007199254740992, porque a posição é um "any".

Nomes de campo no estilo do Go, nomes de struct compartilhados com a página de TypeScript

O nome de um campo é a forma como o Go escreve a chave dele. A chave é dividida em palavras em cada caractere que não é letra nem dígito, e onde quer que uma letra minúscula seja seguida de uma maiúscula; depois as palavras são unidas, cada uma começando com maiúscula, então "user_name", "last-name" e "firstName" viram UserName, LastName e FirstName. Uma palavra da lista padrão de siglas do staticcheck é escrita em maiúsculas — "id" vira ID, "api_key" vira APIKey, "video_url" vira VideoURL —, mas só conta uma palavra inteira, então "idle" vira Idle e o plural "ids" vira Ids. Uma chave escrita toda em maiúsculas é lida como palavras: "USER_ID" vira UserID.

As letras da própria chave são mantidas, então "имя" vira Имя. Onde um nome não começaria com letra maiúscula — uma chave em um sistema de escrita sem maiúsculas, como "名前", uma chave que começa com um dígito, como "1st", ou uma primeira letra cuja maiúscula não é uma só letra, como "ß" —, ele recebe um X na frente, como em X名前, X1st e Xß, e o campo lê a chave dele mesmo assim. As marcas combinantes ficam fora do nome e são mantidas na tag. Uma chave sem nenhuma letra nem dígito, como "@", se chama Field, e um nome já usado na mesma struct recebe um número: "user_id", "userId" e "USER_ID" juntos viram UserID, UserID2 e UserID3.

O estilo do Go chega aos nomes de campo e para aí: cada struct mantém o nome que a inferência escolheu para o objeto dela, então as declarações daqui e as interfaces da página de TypeScript têm os mesmos nomes, e um campo pode ser escrito de forma diferente da struct que ele contém. Como esse nome é escolhido cabe ao guia de TypeScript.

Tags para o Go 1.27, e as chaves que nenhuma tag consegue carregar

A tag de cada campo nomeia a chave dele exatamente, como em json:"user_id", com ,omitzero depois da chave onde a chave pode faltar. Um caractere de controle em uma chave é escrito com a sequência de escape que o próprio Go usa, e a chave "-" é escrita json:"-,", uma forma que o Go lê como essa mesma chave.

As tags são escritas para o Go 1.27. O Go 1.26 lê menos chaves por meio de uma tag: uma chave que contém qualquer coisa além de letras, dígitos, o espaço ASCII e um conjunto de sinais de pontuação ASCII — "Price (€)", "temp °C", um emoji, um caractere de controle, um acento combinante — não é lida ali, e a página nomeia cada campo assim. Essa metade é verificada contra o Go 1.27 compilado com "GOEXPERIMENT=nojsonv2", que lê as tags como o Go 1.26 lê, mas com as tabelas Unicode mais novas do Go 1.27, então uma chave que contém uma letra que as tabelas mais antigas do Go 1.26 não têm é lida nessa verificação e não gera aviso nenhum.

Uma chave que contém uma vírgula, uma barra invertida, aspas duplas, um apóstrofo ou um acento grave, ou a chave vazia, é uma chave que nenhuma tag de struct consegue carregar, então não recebe campo nenhum. O seu JSON ainda é decodificado: "json.Unmarshal" ignora essa chave sem se queixar, embora um "json.Decoder" configurado com "DisallowUnknownFields" pare nela. O aviso lista uma chave assim pela struct dela e pela chave entre aspas do jeito que o Go põe uma string entre aspas, como Order["note,internal"], e um objeto sob uma chave assim continua tendo a struct dele impressa. Um caso passa sem aviso: duas chaves que diferem só em um substituto isolado, um escape que não representa caractere nenhum, são uma mesma chave para o Go, e nenhum dos campos delas é preenchido.

Só declarações, no layout do gofmt

A saída contém declarações de tipo e nada mais: nem cláusula package nem import, então ela vai para um arquivo que você já tem, sob a própria cláusula package desse arquivo. Onde ela usa "json.RawMessage" ou "json.Number", o arquivo também importa "encoding/json", e essa é a linha que fica por conta sua ou do seu editor. O layout é o do próprio gofmt — uma tabulação antes de cada campo, e os nomes, tipos e tags de cada struct em colunas alinhadas —, então o gofmt o deixa exatamente como está. O tipo da raiz é impresso primeiro, cada objeto é um tipo com nome em vez de uma struct escrita dentro do campo que a contém, e um JSON que é um array ou um único valor no nível superior também é um tipo com nome, como "type Root []RootItem", então sempre existe um tipo em que decodificá-lo.

O que os avisos pedem que você verifique

Cada espécie de decisão que o Go impôs recebe uma entrada abaixo das declarações, e a entrada reúne todos os lugares a que se aplica, então uma amostra com muitos campos de números inteiros recebe uma entrada e não uma por campo. Os lugares são escritos como as declarações os escrevem — Order.UserID é um campo, Order.Tags[] são os elementos de um array, e a raiz é o nome dela, sem mais nada —, exceto em duas entradas que, em vez disso, apontam para dentro do seu JSON por linha e coluna: uma chave escrita duas vezes e os números que este navegador não conseguiu distinguir de inteiros. Nenhuma delas altera as declarações. Leia cada uma como uma pergunta sobre os seus dados:

  • Números inteiros com tipo "int64". Todo número ali foi escrito como inteiro. Se um valor que chegue depois puder trazer um ponto decimal ou um expoente, como pode acontecer com um preço ou uma medição, ele não será decodificado, então mude esse campo para "float64"; um ID ou uma contagem pode ficar como está.
  • "json.Number". Nenhum outro tipo numérico guarda esses valores exatamente. Converta cada um onde o seu programa o usa, ou, se você sabe que os valores reais cabem em um tipo mais estreito, mude o campo você mesmo.
  • Uma fração que "float64" arredonda. Um número ali tem mais dígitos do que um "float64" guarda, então o seu programa vê um valor próximo em vez do que foi escrito. Onde cada dígito importa, mude o campo para "json.Number".
  • Tipos mistos guardados como "json.RawMessage". Olhe cada valor quando ele chegar e decodifique-o como o tipo que ele acabar sendo; onde um dos tipos é um objeto, a struct dele continua na saída, para decodificar esse valor nela.
  • Nada de onde inferir um tipo. O campo é "any", ou um map de "any", porque a amostra não tinha ali valor nenhum de onde aprender. Troque-o pelo tipo que você sabe que esse campo carrega, ou converta de novo a partir de um JSON em que ele tenha valores reais.
  • Falta em alguns objetos, null em outros. O Go guarda um só nil para os dois casos, então a chave é omitida quando é escrita de volta. Isso só importa se o que quer que leia a sua saída tratar um null de forma diferente de uma chave que não está lá.
  • Chaves que o Go 1.26 não lê. Compile com o Go 1.27, ou renomeie essas chaves onde elas são produzidas; no Go 1.26 elas ficam sem ser lidas.
  • Chaves que ficam de fora da struct. Elas não têm campo. Para ler uma delas, decodifique o objeto em um "map[string]json.RawMessage" ou escreva um método "UnmarshalJSON" para a struct, como o próprio aviso sugere.
  • Uma chave escrita duas vezes. Os tipos seguem a última cópia da chave, mas o Go decodifica cada cópia por vez, então uma cópia anterior que o tipo não consegue guardar, como uma string onde a última cópia é um número, faz a decodificação retornar um erro, e uma chave que só aparece em uma cópia anterior de um objeto não é lida. Corrija o JSON na linha e na coluna mostradas.
  • Este navegador não distingue 10 de 10.0. Ele não informa como um número está escrito, então um campo com tipo "int64" por causa de um valor escrito com ponto decimal ou expoente não vai decodificar o seu JSON. Verifique os campos escritos assim, ou converta o JSON em um navegador que informe isso.

Não há aviso sobre datas, formatos ou conjuntos fixos de valores, porque a página nunca adivinha um deles a partir de uma string: "created_at" no exemplo continua sendo um "string", seja qual for a aparência dele.

Perguntas frequentes

Por que meu preço é float64 se todo preço no meu JSON é um número redondo?
Porque cada preço está escrito com ponto decimal, como 10.0, e um campo "int64" rejeita um número escrito assim, redondo ou não. A página lê como cada número está escrito em vez de só o valor dele, então o campo é "float64" e o seu JSON é decodificado. Se o tipo "int64" fosse escolhido só a partir dos valores, a decodificação pararia no primeiro preço.
Quando um número sai como json.Number?
Quando um dos valores dele é um inteiro além do intervalo de "int64", um número grande demais para um "float64" ou um inteiro longo que um "float64" arredondaria, ao lado de uma fração. "json.Number" mantém cada um deles exato, e o aviso abaixo da saída lista cada campo para o qual ele foi escolhido.
Por que alguns campos são ponteiros, e o que omitzero faz nas tags?
Um campo é um ponteiro onde a chave dele falta em alguns objetos ou é null em alguns, para que um nil possa dizer que nenhum valor chegou. "omitzero" vai onde a chave falta em alguns objetos: codificar a struct de novo então omite essa chave, como o seu JSON fazia, em vez de escrever null para ela.
Por que json.RawMessage para uma chave de tipos mistos, e não any?
Um "json.RawMessage" guarda os bytes de cada valor até que o seu programa tenha decidido como lê-los. Um "any" também aceitaria os valores, mas um número decodificado em um "any" é um "float64", e um inteiro longo perde ali os últimos dígitos.
Por que alguns nomes de campo começam com um X?
Porque, do contrário, o nome não começaria com letra maiúscula: a chave está escrita em um sistema de escrita sem maiúsculas, começa com um dígito ou começa com uma letra que não tem forma maiúscula de uma só letra. O X mantém no nome as letras da própria chave, e o campo continua lendo a chave dele — X名前 lê "名前".
De qual versão do Go as structs precisam?
Elas são escritas e verificadas para o Go 1.27. A forma como o Go 1.26 lê as tags também é verificada, por meio de algo que faz as vezes dele: o Go 1.26 não lê uma chave que contém um caractere fora das letras, dos dígitos, do espaço ASCII e de um conjunto de sinais de pontuação ASCII, como um símbolo de moeda ou um emoji, e a página nomeia cada campo cuja chave é afetada por isso — exceto uma chave que contém uma letra mais nova do que as tabelas Unicode do Go 1.26: uma chave assim, nem a página nem essa verificação conseguem vê-la.
Por que uma das minhas chaves falta na struct?
Porque a chave contém uma vírgula, uma barra invertida, aspas duplas, um apóstrofo ou um acento grave, ou está vazia, e nenhuma tag de struct consegue carregar uma chave assim. Um aviso a lista pela struct dela e pela própria chave, como Root["a,b"]; o resto do seu JSON ainda é decodificado, e o aviso diz como ler essa chave de outra forma.
É seguro colar uma resposta que contém tokens ou senhas?
Sim. Cada etapa roda dentro desta página no seu computador, e a resposta que você cola não vai para nenhum outro lugar: nenhum servidor a recebe e nada guarda uma cópia. As declarações que saem dela carregam só nomes de campo, tags e tipos Go, então um token ou uma senha no JSON não deixa nelas nenhum rastro além de um campo "string" com o nome da chave dele.

Ferramentas relacionadas

  • JSON para TypeScript

    Onde os valores de uma chave são de mais de um tipo, esta página guarda cada um exatamente como está escrito no seu JSON para que seu programa o decodifique, já que o Go não tem união. Aquela página escreve a mesma forma como tipos TypeScript com os mesmos nomes de tipo, com uma união nesse lugar, e o guia dela explica como essa forma é calculada.

  • JSON para Zod

    O Go não tem um tipo que seja simplesmente um número JSON, então, onde todo número está escrito como número inteiro, esta página dá ao campo um tipo inteiro, e um valor escrito com ponto decimal não será decodificado nele. Aquela página escreve a mesma forma como esquemas Zod com os mesmos nomes de tipo, e um esquema verifica os dados toda vez que eles chegam e também aceita uma fração em um campo assim.

  • Testador de JSONPath

    Teste consultas JSONPath (RFC 9535) em JSON.

  • Gerador de tabelas Markdown

    Crie e alinhe tabelas Markdown a partir de CSV, TSV ou JSON.