JSON a Go
Genera structs de Go con etiquetas json desde JSON: números tipados según cómo se escriben, punteros donde puede faltar un valor — todo en tu navegador.
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"`
}
En cada posición de abajo, todos los números se leyeron como enteros, así que el tipo es int64. Un valor escrito con punto decimal o exponente, incluso 10.0, no se decodificará en él.
Posiciones: 2
Order.IDOrder.UserID
En cada posición de abajo, los valores son de más de un tipo (por ejemplo, números y cadenas), así que el tipo es json.RawMessage, que guarda cada valor tal como está escrito en tu JSON para que tu programa lo decodifique cuando sepa de qué tipo es. Donde uno de esos tipos es un objeto, su struct sigue en la salida, para decodificar ese valor en él.
Posiciones: 1
Shipping.Postcode
En cada posición de abajo no había nada de lo que inferir un tipo: ahí solo se vio null, el array siempre estaba vacío, el objeto no tenía claves o el valor está anidado a demasiada profundidad para que esta herramienta lo siga. Así que el tipo es any, que acepta cualquier valor, o map[string]any donde el objeto no tenía claves, que acepta cualquier objeto o null y nada más.
Posiciones: 1
Order.Tags[]
Structs que decodifican el JSON del que salieron
Pega una muestra de JSON y esta página escribe declaraciones de Go para ella: un struct con nombre para cada objeto de la muestra y, en cada campo, una etiqueta json que nombra la clave que ese campo lee. Una promesa decide todo lo que sigue. En un programa compilado con Go 1.27, las declaraciones decodifican el JSON que pegaste a través de "encoding/json" sin error, y cada clave se lee en un campo, salvo una clave que ninguna etiqueta de un struct puede llevar, que queda fuera. Donde Go no deja que esa promesa se cumpla, u obliga a la página a elegir algo que tus datos no decidieron, un aviso debajo de la salida dice dónde, con una excepción poco frecuente que se describe en la sección sobre las etiquetas.
La forma en sí se calcula antes de escribir nada en Go, con la misma lectura de tu JSON que alimenta la página JSON a TypeScript. Fusionar los elementos de un array, detectar las claves que solo tienen algunos de ellos, separar null de una clave que nunca se envió y dar nombre a cada objeto encontrado dentro de otro: la guía de esa página cubre todo eso, así que abajo no se repite nada de ello. Lo que sigue es la parte de Go — un tipo por número, punteros, un tipo para una clave con valores de distintos tipos, nombres de campo, etiquetas y las preguntas que plantean los avisos.
Tres tipos numéricos, elegidos según cómo está escrito cada número
Un navegador que lee JSON convierte 10 y 10.0 en el mismo número. Go no: un campo "int64" rechaza un número escrito con punto decimal o exponente — 10.0, 1e3, incluso -0.0 —, mientras que acepta 11 y -0. Por eso la página no elige un tipo numérico solo a partir de los valores. Le pregunta al propio analizador del navegador cómo se escribió cada número, y decide según eso:
- Donde todos los números de una posición están escritos como enteros, sin punto decimal y sin exponente, el campo es "int64", y el aviso de números enteros lo nombra.
- Cualquier otro número es "float64", así que un precio escrito 10.0 es "float64" aunque todos los precios de la muestra sean redondos. El módulo "json" de Python escribe un float de valor entero exactamente así, lo que hace de una API servida por Python el lugar habitual donde encontrarlo.
- "json.Number" se reserva para los números que ninguno de los dos tipos guarda con exactitud: un entero más allá de cualquiera de los extremos del rango de "int64", un número demasiado grande para caber siquiera en un "float64", como 1e999, y un entero demasiado largo para un "float64" junto a una fracción, como en [9007199254740993, 1.5]. Guarda cada uno de ellos con exactitud, y tu programa lo convierte donde usa el valor.
Un entero largo entre otros enteros no necesita ese cuidado: [9007199254740993, 1] es "[]int64". Una fracción con más dígitos de los que guarda un "float64" se queda en "float64" y se nombra en un aviso propio, ya que, decodificado y vuelto a codificar, 0.30000000000000001 vuelve como 0.3.
Leer cómo se escribió un número requiere un navegador que lo indique. Uno que no lo indica lee cada número solo a partir de su valor, así que ahí 10.0 parece entero y su campo pasa a ser "int64", un campo que no decodificará ese JSON; la página cuenta los números que no pudo distinguir de los enteros y señala dónde está el primero. Un navegador así tampoco ve los dígitos que un "float64" descarta, así que ahí un entero largo junto a una fracción recibe el tipo "float64", y una fracción que se redondea no se nombra.
Punteros donde puede faltar un valor
Una clave que algunos objetos omiten, o ponen a null, es un puntero: "*string", "*int64" o un puntero al struct escrito para un objeto. Los elementos de un array que contiene null también son punteros, así que [1, null] es "[]*int64". Una clave que falta en algunos objetos se marca además con "omitzero" en su etiqueta. Con ambas cosas en su sitio, un campo puede decir si llegó un valor, y al volver a codificar el struct decodificado, una clave que en la muestra siempre faltaba se vuelve a omitir, y una que siempre era null se vuelve a escribir como null.
Un slice, un map, "any" y "json.RawMessage" no llevan puntero, ya que cada uno ya es nil cuando no se decodificó nada en él. Aun así, un slice recibe "omitzero" donde su clave puede faltar, y un array que estaba presente pero vacío se vuelve a escribir como [].
Una distinción no sobrevive. Una clave que falta en algunos objetos y es null en otros es un único nil en Go, así que tu programa no puede distinguir los dos casos, y al volver a codificar el struct la clave se omite incluso donde la muestra tenía null; la página nombra cada campo al que le ocurre esto. Una clave de tipos mezclados conserva la diferencia, porque un "json.RawMessage" guarda un null como los bytes null.
json.RawMessage para tipos mezclados, y any donde no se vio nada
Cuando los valores de una clave no coinciden en tipo — un número en un sitio, una cadena o un objeto en otro —, el campo es "json.RawMessage": los bytes del propio valor, que quedan para que tu programa los decodifique una vez que haya mirado de qué tipo es lo que llegó. Un null entre ellos no cambia nada. Donde uno de los tipos es un objeto, el struct extraído para él se sigue imprimiendo junto a los demás. Un "any" también aceptaría cualquier tipo, y no se usa para una mezcla, porque un número decodificado en un "any" se convierte en un "float64" y pierde todo lo que un "float64" no puede guardar.
Algunas partes de una muestra no ofrecen ningún valor del que aprender: una clave que nunca contuvo más que null, un array que estuvo vacío todas las veces, un objeto sin claves y un valor anidado a más profundidad de la que la inferencia puede seguir. En Go, la clave con null y el valor anidado a esa profundidad reciben "any", los elementos del array vacío "[]any" y el objeto "map[string]any". Un "any" acepta cualquier tipo de valor. El map acepta un objeto o null, y una cadena, un número, un array o un booleano en su lugar hacen que la decodificación falle.
Más allá de esa profundidad, los avisos callan dos cosas. Allí, una clave que falta en algunos objetos y es null en otros solo se nombra como una posición en la que no había nada de lo que inferir un tipo, y Go, al volver a escribirla, la omite donde era null. Y un entero más largo de lo que guarda un "float64" vuelve redondeado, y 9007199254740993 pasa a ser 9007199254740992, porque la posición es un "any".
Nombres de campo al estilo de Go, nombres de struct compartidos con la página de TypeScript
El nombre de un campo es la forma en que Go escribe su clave. La clave se divide en palabras en cada carácter que no es una letra ni un dígito, y allí donde a una minúscula le sigue una mayúscula; después las palabras se unen, cada una empezando por mayúscula, así que "user_name", "last-name" y "firstName" pasan a ser UserName, LastName y FirstName. Una palabra de la lista predeterminada de siglas de staticcheck se escribe en mayúsculas — "id" es ID, "api_key" es APIKey, "video_url" es VideoURL —, pero solo cuenta una palabra entera, así que "idle" es Idle y el plural "ids" es Ids. Una clave escrita toda en mayúsculas se lee como palabras: "USER_ID" es UserID.
Las letras propias de la clave se conservan, así que "имя" pasa a ser Имя. Donde un nombre no empezaría por mayúscula — una clave en un sistema de escritura sin mayúsculas como "名前", una clave que empieza por un dígito como "1st", o una primera letra sin mayúscula de un solo carácter como "ß" —, lleva una X delante, como X名前, X1st y Xß, y el campo lee su clave igualmente. Las marcas combinantes se quitan del nombre y se conservan en la etiqueta. Una clave sin ninguna letra ni ningún dígito, como "@", se llama Field, y un nombre ya usado en el mismo struct recibe un número: "user_id", "userId" y "USER_ID" juntos son UserID, UserID2 y UserID3.
El estilo de Go llega hasta los nombres de campo y se detiene ahí: cada struct conserva el nombre que la inferencia eligió para su objeto, así que las declaraciones de aquí y las interfaces de la página de TypeScript se llaman igual, y un campo puede escribirse de forma distinta al struct que contiene. Cómo se elige ese nombre le corresponde a la guía de la página de TypeScript.
Etiquetas para Go 1.27, y las claves que ninguna etiqueta puede llevar
La etiqueta de cada campo nombra su clave exactamente, como en json:"user_id", con ,omitzero después de la clave donde la clave puede faltar. Un carácter de control en una clave se escribe con la secuencia de escape que usa el propio Go, y la clave "-" se escribe json:"-,", una forma que Go lee como esa misma clave.
Las etiquetas están escritas para Go 1.27. Go 1.26 lee menos claves a través de una etiqueta: una clave que contiene algo distinto de letras, dígitos, el espacio ASCII y un conjunto de signos de puntuación ASCII — "Price (€)", "temp °C", un emoji, un carácter de control, un acento combinante — no se lee ahí, y la página nombra cada campo así. Esa mitad se comprueba con Go 1.27 compilado con "GOEXPERIMENT=nojsonv2", que lee las etiquetas como Go 1.26 pero con las tablas de Unicode más recientes de Go 1.27, así que una clave con una letra que las tablas más antiguas de Go 1.26 no tienen se lee en esa comprobación y no genera ningún aviso.
Una clave que contiene una coma, una barra invertida, una comilla doble, un apóstrofo o un acento grave, o la clave vacía, es una clave que ninguna etiqueta de un struct puede llevar, así que no recibe ningún campo. Tu JSON se decodifica igualmente: "json.Unmarshal" omite esa clave sin protestar, aunque un "json.Decoder" configurado con "DisallowUnknownFields" se detiene en ella. El aviso enumera una clave así por su struct y por la clave entre comillas, tal como Go entrecomilla una cadena, como Order["note,internal"], y un objeto bajo una clave así sigue teniendo su struct impreso. Un caso pasa sin aviso: dos claves que solo se diferencian en un sustituto aislado, una secuencia de escape que no representa ningún carácter, son una misma clave para Go, y ninguno de sus campos se rellena.
Solo declaraciones, con la disposición de gofmt
La salida contiene declaraciones de tipos y nada más: ni cláusula package ni import, así que va en un archivo que ya tienes, bajo la propia cláusula package de ese archivo. Donde usa "json.RawMessage" o "json.Number", el archivo también importa "encoding/json", y esa es la línea que queda para ti o para tu editor. La disposición es la del propio gofmt — un tabulador antes de cada campo, y los nombres, tipos y etiquetas de cada struct en columnas alineadas —, así que gofmt la deja exactamente como está. El tipo de la raíz se imprime primero, cada objeto es un tipo con nombre en vez de un struct escrito dentro del campo que lo contiene, y un JSON que es un array o un único valor en el nivel superior también es un tipo con nombre, como "type Root []RootItem", así que siempre hay un tipo en el que decodificarlo.
Lo que los avisos te piden comprobar
Cada clase de decisión que Go impuso recibe una entrada debajo de las declaraciones, y la entrada reúne todos los lugares a los que se aplica, así que una muestra con muchos campos de números enteros recibe una entrada y no una por campo. Los lugares se escriben como los escriben las declaraciones — Order.UserID es un campo, Order.Tags[] los elementos de un array, y la raíz es su nombre sin más —, salvo en dos entradas que en su lugar señalan dentro de tu JSON una línea y una columna: una clave escrita dos veces y los números que este navegador no pudo distinguir de los enteros. Ninguna de ellas altera las declaraciones. Lee cada una como una pregunta sobre tus datos:
- Números enteros con tipo "int64". Todos los números de ahí se escribieron como enteros. Si un valor que llegue más tarde puede llevar punto decimal o exponente, como puede llevarlos un precio o una medida, no se decodificará, así que haz que ese campo sea "float64"; un ID o un recuento pueden quedarse como están.
- "json.Number". Ningún otro tipo numérico guarda esos valores con exactitud. Convierte cada uno donde tu programa lo use, o, si sabes que los valores reales caben en un tipo más estrecho, cambia el campo tú mismo.
- Una fracción que "float64" redondea. Un número de ahí tiene más dígitos de los que guarda un "float64", así que tu programa ve un valor cercano y no el que está escrito. Donde importe cada dígito, haz que el campo sea "json.Number".
- Tipos mezclados guardados como "json.RawMessage". Mira cada valor cuando llegue y decodifícalo según el tipo que resulte ser; donde uno de los tipos es un objeto, su struct sigue en la salida, para decodificar ese valor en él.
- Nada de lo que inferir un tipo. El campo es "any", o un map de "any", porque la muestra no tenía ahí ningún valor del que aprender. Sustitúyelo por el tipo que sabes que lleva ese campo, o vuelve a convertir desde un JSON en el que tenga valores reales.
- Falta en algunos objetos, null en otros. Go guarda ambos casos como un nil, así que la clave se omite al volver a escribirla. Eso solo importa si lo que lee tu salida trata un null de forma distinta a una clave que no está.
- Claves que Go 1.26 no lee. Compila con Go 1.27, o cambia el nombre de esas claves donde se generan; en Go 1.26 quedan sin leer.
- Claves que quedan fuera. No tienen campo. Para leer una, decodifica el objeto en un "map[string]json.RawMessage" o escribe un método "UnmarshalJSON" para el struct, como sugiere el propio aviso.
- Una clave escrita dos veces. Los tipos siguen la última copia de la clave, pero Go decodifica cada copia por turno, así que una copia anterior que el tipo no puede guardar, como una cadena donde la última copia es un número, hace que la decodificación devuelva un error, y una clave que solo aparece en una copia anterior de un objeto no se lee. Corrige el JSON en la línea y la columna indicadas.
- Este navegador no distingue 10 de 10.0. No indica cómo está escrito un número, así que un campo con tipo "int64" por un valor escrito con punto decimal o exponente no decodificará tu JSON. Revisa los campos cuyos valores se escribieron así, o convierte el JSON en un navegador que lo indique.
Sobre fechas, formatos o conjuntos fijos de valores no hay ningún aviso, porque la página nunca adivina nada de eso a partir de una cadena: "created_at" en el ejemplo sigue siendo un "string", tenga el aspecto que tenga.
Preguntas frecuentes
- ¿Por qué mi precio es float64 si todos los precios de mi JSON son números redondos?
- Porque cada precio está escrito con punto decimal, como 10.0, y un campo "int64" rechaza un número escrito así, sea redondo o no. La página lee cómo está escrito cada número y no solo su valor, así que el campo es "float64" y tu JSON se decodifica. Si el tipo "int64" se eligiera solo a partir de los valores, la decodificación se detendría en el primer precio.
- ¿Cuándo sale un número como json.Number?
- Cuando uno de sus valores es un entero fuera del rango de "int64", un número demasiado grande para un "float64" o un entero largo junto a una fracción, que un "float64" redondearía. "json.Number" guarda cada uno de ellos con exactitud, y el aviso de debajo de la salida enumera cada campo para el que se eligió.
- ¿Por qué algunos campos son punteros, y qué hace omitzero en las etiquetas?
- Un campo es un puntero donde su clave falta en algunos objetos o es null en algunos, para que un nil pueda decir que no llegó ningún valor. "omitzero" va donde la clave falta en algunos objetos: así, al volver a codificar el struct, esa clave se omite, como hacía tu JSON, en lugar de escribirse como null.
- ¿Por qué json.RawMessage para una clave de tipos mezclados, y no any?
- Un "json.RawMessage" guarda los bytes de cada valor hasta que tu programa decide cómo leerlos. Un "any" también aceptaría los valores, pero un número decodificado en un "any" es un "float64", y un entero largo pierde ahí sus últimos dígitos.
- ¿Por qué algunos nombres de campo empiezan por X?
- Porque de otro modo el nombre no empezaría por mayúscula: la clave está escrita en un sistema de escritura sin mayúsculas, empieza por un dígito o empieza por una letra que no tiene forma mayúscula de un solo carácter. La X mantiene en el nombre las letras propias de la clave, y el campo sigue leyendo su clave — X名前 lee "名前".
- ¿Qué versión de Go necesitan los structs?
- Están escritos y comprobados para Go 1.27. Cómo lee Go 1.26 las etiquetas también se comprueba, a través de algo que hace sus veces: Go 1.26 no lee una clave que contenga un carácter fuera de las letras, los dígitos, el espacio ASCII y un conjunto de signos de puntuación ASCII, como un símbolo de moneda o un emoji, y la página nombra cada campo cuya clave se ve afectada — salvo una clave con una letra más nueva que las tablas de Unicode de Go 1.26: una clave así no la pueden ver ni la página ni esa comprobación.
- ¿Por qué falta una de mis claves en el struct?
- Porque la clave contiene una coma, una barra invertida, una comilla doble, un apóstrofo o un acento grave, o está vacía, y ninguna etiqueta de un struct puede llevar una clave así. Un aviso la enumera por su struct y por la propia clave, como Root["a,b"]; el resto de tu JSON se decodifica igualmente, y el aviso dice cómo leer esa clave de otra manera.
- ¿Es seguro pegar una respuesta que contiene tokens o contraseñas?
- Sí. Cada paso se ejecuta dentro de esta página en tu ordenador, y la respuesta que pegas no va a ningún otro sitio: ningún servidor la recibe y nada guarda una copia. Las declaraciones que salen de ella solo llevan nombres de campo, etiquetas y tipos de Go, así que un token o una contraseña del JSON no deja en ellas más rastro que un campo "string" con el nombre de su clave.
Herramientas relacionadas
- JSON a TypeScript
Donde los valores de una clave son de más de un tipo, esta página guarda cada uno tal como está escrito en tu JSON para que tu programa lo decodifique, porque Go no tiene uniones. Esa página escribe la misma forma como tipos de TypeScript con los mismos nombres de tipo, con una unión en ese lugar, y su guía explica cómo se calcula esa forma.
- JSON a Zod
Go no tiene un tipo que sea simplemente un número de JSON, así que, donde todos los números están escritos como enteros, esta página le da al campo un tipo entero, y un valor escrito con punto decimal no se decodificará en él. Esa página escribe la misma forma como esquemas de Zod con los mismos nombres de tipo, y un esquema comprueba los datos cada vez que llegan y también acepta una fracción en un campo así.
- Probador de JSONPath
Prueba consultas JSONPath (RFC 9535) contra JSON.
- Generador de tablas Markdown
Crea y alinea tablas Markdown desde CSV, TSV o JSON.