JSON es un formato de texto que permite representar cualquier estructura de datos.
Introducción
JSON se basa en una propiedad fundamental de los lenguajes de programación: cualquier estructura de datos se puede representar mediante diccionarios y listas de forma recursiva.
JSON permite convertir cualquier estructura de datos a una representación de texto simple.
De esta forma, tanto tu como cualquier lenguaje de programación puede leer y escribir datos JSON.
Diccionario
El concepto de diccionario viene del diccionario físico, y es el término que utiliza Python aunque en otros lenguajes tiene otro nombre.
A continuación tienes un ejemplo muy sencillo de una representación JSON:
{ "name": "Eva" }
Se trata de un diccionario con sólo una entrada, donde "name"
es la clave y "Eva"
es el valor.
El nombre es un string
y debe estar rodeado de comillas dobles ""
TypeScript
TypeScript no incluye un tipo de datos nativo "diccionario" porque los objetos en TypeScript se pueden utilizar como si fueran diccionarios.
Por ejemplo aquí tienes un objeto JSON que representa a una persona:
{
"name": "Eva",
"age": 30
}
A continuación tienes el mismo objeto representado como un objeto de TypeScript:
{
name: "Eva",
age: 30
}
¿Alguna diferencia? 🤗
¡Fíjate que la única diferencia es que JSON utiliza comillas dobles para las claves!
Y siempre debes utilizar comillas dobles al representar un string, nunca puedes utilizar comillas simples: 'Eva' no es válido en JSON.
Instala Bun
Crea el directorio json
:
> mkdir json
> cd json
Importa la biblioteca bun
:
> bun add bun
> bun add -d @types/bun
Verás que se ha creado un fichero JSON package.json
que define las dependencias del proyecto:
{
"dependencies": {
"bun": "^1.2.2"
},
"devDependencies": {
"@types/bun": "^1.2.2"
}
}
JSON nos permite convertir una estructura a un string
con la función JSON.stringify()
.
Crea un fichero write.ts
.
let eva = {
name : "Eva",
age: 30
}
let json = JSON.stringify(eva)
console.log(json)
Puedes ver que port pantalla se imprime un string
: name
i age
van entre comillas dobles (""
):
> bun run .\write.ts
{"name":"Eva","age":30}
Una vez que tenemos el JSON convertido en string
, podemos escribirlo en varios destinos, como una respuesta de API, un archivo o cualquier otra cosa.
Por ejemplo, escribe el string
en el fichero eva.json
:
export {}
let eva = {
name : "Eva",
age: 30
}
let json = JSON.stringify(eva)
await Bun.write("eva.json", json)
Tienes que añadir export {}
al principio del fichero porque las expresiones await
solo se permiten en el nivel superior de un archivo cuando ese archivo es un módulo.
Todas las operaciones E/S has de ser asíncronas!
Si ejecutas el script puedes ver que se crea un fichero eva.json
que tanto tu como cualquier lenguaje de programación puede leer sin problemas:
> bun run .\write.ts
> Get-Content .\eva.json
{"name":"Eva","age":30}
Python
Aquí tienes el objeto JSON que representa a una persona:
{
"name": "Eva",
"age": 30
}
A continuación tienes el mismo objeto representado como un diccionario en Python:
{
"name": "Eva",
"age": 30
}
¿Alguna diferencia? 🤗
Crea el script read.py
:
import json
str = '{"name": "Eva", "age": 30}'
eva = json.loads(str)
print(eva)
Con la función loads
puedes leer un str
y convertirlo en un diccionario Python.
> python.exe .\read.py
{'name': 'Eva', 'age': 30}
Modifica el fichero read.py
para leer el fichero eva.json
que a creado un script de TypeScript:
import json
with open('eva.json', 'r') as file:
eva = json.load(file)
print(eva["name"])
Puedes ver que TypeScript y Python pueden compartir datos 💞:
python.exe .\read.py
Eva
El problema es que pueden compartir datos con cualquier lenguaje que pase por ahí, incluso contigo, no son un buen ejemplo de fidelidad, JSON es muy, muy simple 😒
Actividades
1.- Aquí tienes un objeto Typescript que representa un libro:
{
title: "The Art of Computer Programming",
author: "Donald Knuth"
}
Modifica:
- El script
write.ts
para que escriba el libro enbook.json
- El script
read.py
para que lea el libro e imprima el título.
write.ts
export { }
let book = {
book: "The Art of Computer Programming",
author: "Donald Knuth"
}
let json = JSON.stringify(book)
await Bun.write("book.json", json)
read.py
import json
with open('book.json', 'r') as file:
book = json.load(file)
print(book["author"])
Funciona ! 😀
> bun run .\write.ts
PS C:\Users\david\Workspace\ts-json> python.exe .\read.py
Donald Knuth
2.- Importa el fichero package.json
y imprime por consola las dependencias del proyecto:
export{}
let str = await Bun.file("package.json").text()
let pack = JSON.parse(str)
console.log(pack.dependencies)
Fíjate que lo que se imprime es un objeto TypeScript, el nombre de la propiedad no va entre ""
:
{
bun: "^1.2.2",
}
Lista
Una lista es un conjunto ordenado de elementos que puede accederse por posición.
A continuación tienes un ejemplo de una lista:
[ 1, "hello", false ]
Python
Aquí tienes una lista en JSON:
[1 ,"hello", false]
Y la misma lista en Python 🤗 :
[1 ,"hello", False]
Crea el script write.py
:
import json
list = [1 ,"hello", False]
with open('data.json', 'w') as file:
json.dump(list, file)
Con la función json.dump
conviertes un objeto Python en un str
JSON.
> python.exe .\write.py
TypeScript
Una lista "json" se representa con un array TypeScript: 🤗
[1 ,"hello", False]
Crea el fichero read.ts
para leer los datos del fichero data.json
:
export{}
let str = await Bun.file("data.json").text()
let list = JSON.parse(str)
console.log(list)
> python.exe .\write.py
> bun .\read.ts
[ 1, "hello", false ]
Actividad
Modifica el fichero write.py
:
import json
customers = [
{ "name": "Eva", "age": 43},
{ "name": "Esther", "age": 51},
{ "name": "David", "age": 52}
]
with open('data.json', 'w') as file:
json.dump(customers, file)
Ejecuta el script para crear el fichero data.json
:
> python.exe .\write.py
Modifica el fichero read.ts
para que importe los datos de data.json
e imprima sólo los nombres de los clientes:
Tipos
Tipos básicos
JSON sólo tiene tres tipos básicos: string
, number
y boolean
!
string
["fred", "fred\t", "\b", "", "\t", "\u004A" ]
Un string tiene estas propiedades:
- Un string consiste en cero o más caracteres Unicode entre comillas dobles (
""
). - Un string entre comillas simples (
'
) no es válido – a diferencia de TypeScript y Python.
Además, un string puede contener los siguientes caracteres "backslash-escaped":
\" |
Double quote |
\\ |
Backslash |
\/ |
Forward slash |
\b |
Backspace |
\f |
Form feed |
\n |
Newline |
\r |
Carriage return |
\t |
Tab |
\u |
Trailed by four hex digits |
number
{
"age": 29,
"cost": 299.99,
"temperature": -10.5,
"unitCost": 0.2,
"speedOfLight": 1.23e11,
"speedOfLight2": 1.23e+11,
"avogadro": 6.023E23,
"avogadro2": 6.023E+23,
"oneHundredth": 10e-3,
"oneTenth": 10E-2
}
Los números siguen el formato de coma flotante de doble precisión de TypeScript y tienen las siguientes propiedades:
- Los números siempre están en base 10 (sólo se permiten los dígitos del 0 al 9) sin ceros iniciales.
- Los números pueden tener una parte fraccionaria que comienza con un punto decimal (.).
- Los números pueden tener un exponente de 10, que se representa con la notación eo E con un signo positivo o negativo para indicar una exponenciación positiva o negativa.
- No se admiten los formatos octal y hexadecimal.
- A diferencia de TypeScript, los números no pueden tener un valor de
NaN
(no es un número para números no válidos) oInfinity
.
boolean
{
"isRegistered": true,
"emailValidated": false
}
Los booleanos tienen las siguientes propiedades:
- Los booleanos sólo pueden tener un valor de
true
ofalse
. - El valor
true
ofalse
en el lado derecho de ambos puntos (:
) no está rodeado de comillas.
null
Aunque técnicamente no sea un tipo de valor, null
es un valor especial en JSON.
{
"address": {
"line1": "555 Any Street",
"line2": null,
"city": "Denver",
"stateOrProvince": "CO",
"zipOrPostalCode": "80202",
"country": "USA"
}
}
Los valores nulos tienen las siguientes características:
- No están rodeados de comillas
- Indica que una clave/propiedad no tiene valor
- Actúa como marcador de posición
"Tipos" derivados
JSON sólo tiene tres tipos básicos porque con un string
puedes representar cualquier tipo de datos.
Sólo hace falta que todos nos pongamos de acuerdo en cómo representamos un dato.
En este enlace tienes la guía de estilo de Google respecto a JSON: JSON Style Guide.
Fecha
Por ejemplo, Google prefiere que las fechas sigan el formato RFC 3339:
{
"date": "2014-03-01T23:46:11-05:00"
}
La fecha anterior proporciona un desplazamiento de la hora universal coordinada (UTC) (desde UTC/GMT—Hora media de Greenwich) de -5 horas, que es la hora estándar del este de EE.UU. Tenga en cuenta que la RFC 3339 es un perfil de la ISO 8601.
const date = new Date()
// RFC 3339 format
console.log(date.toISOString()) // 2025-02-17T22:20:57.141Z
La principal diferencia es que la ISO 8601 de la Organización Internacional de Estándares permite sustituir a la T
(que separa la fecha y la hora) por un espacio, y la RFC 3339 no lo permite.
Valores de latitud/longitud
Las API geográficas (p. ej., Google Maps) y las API relacionadas con un sistema de información geográfica (SIG) utilizan datos de latitud/longitud. Para garantizar la coherencia, Google recomienda que los datos de latitud/longitud sigan el estándar ISO 6709.
Según Google Maps, las coordenadas del Empire State Building en la ciudad de Nueva York son 40,748747° N, 73,985547° O y estarían representadas en JSON..
{
"empireStateBuilding": "40.748747-73.985547"
}
Este ejemplo sigue el formato ±DD.DDDD±DDD.DDDD
, con las siguientes convenciones:
- La latitud es lo primero.
- La latitud norte (del ecuador) es positiva.
- La longitud este (del primer meridiano) es positiva.
- La latitud/longitud se representa con un string. No puede ser un número debido al signo menos.
Estructuras complejas
Combinando diccionarios y listas, y con sólo tres tipos básicos, puedes representar cualquier tipo de datos:
{
"customers": [
{ "id": 11, "name": "Eva"},
{ "id": 23, "name": "Marc"},
{ "id": 36, "name": "Esther", "address":
{ "street": "Avda. Diagonal", "city": "Barcelona"}
}
]
}
A continuación tienes el diagrama de clases correspondiente:
classDiagram direction LR class Person { id: number name: string } class Address { street: String city: String } Person --> Address
Referencias repetidas
Cuando transformamos una estructura de datos en un texto JSON, el proceso no codifica referencias, sinó que va codificando los objetos que encuentra a su paso.
Por ejemplo, en un proyecto el owner
y el mantainer
son del tipo User
:
classDiagram direction LR class Project { name: string } class User { name: String } Project --> User : owner Project --> User : mantainer
A continuación tienes un ejemplo en que en el proyecto "Quantum System", owner
y mantainer
hacen referencia al mismo objecto de tipo User
:
type Project = { name: string, owner: User, mantainer: User }
type User = { name: string }
const david: User = { name: "David" }
const project: Project = { name: "Quantum System", owner: david, mantainer: david }
console.log(JSON.stringify(project))
Si ejecutas el código, puedes ver que el owner
y el matainer
se codifican som si fueran dos objetos distintos.
> bun run .\test.ts
{"name":"Quantum System","owner":{"name":"David"},"mantainer":{"name":"David"}}
Esto significa que puede haber datos repetidos porque un objeto puede hacer referencia al mismo de forma directa o indirecta.
flowchart LR project["Quantum System"] david["🐱 David"] project -- owner --> david project -- mantainer --> david
En nuestro ejemplo, si guardamos el proyecto en un fichero y lo volvemos a leer, tendremos un proyecto en que el owner
y el mantainer
son dos objetos distintos aunque tengan el mismo nombre:
export {}
type Project = { name: string, owner: User, mantainer: User }
type User = { name: string }
const david: User = { name: "David" }
let project: Project = { name: "Quantum System", owner: david, mantainer: david }
await Bun.write("project.json", JSON.stringify(project))
project = JSON.parse(await Bun.file("project.json").text())
project.owner.name = "Eva"
console.log(project.mantainer.name) // Eva
Si ejecutas el script, puedes ver que si cambio el nombre del owner
, el nombre del mantainer
no cambia porque son dos objetos distintos:
> bun run .\test.ts
David
Debes evitar un código de este tipo si vas a compartir estos datos con JSON 💩 !
No hay referencias, hay redundancia!
Referencias circulares
Como ya se ha explicado antes, la serialización no tiene en cuenta si un objeto ya se había serializado.
Esto significa que además de la redundancia, la serialización sólo funciona con árboles: un objeto raíz, ramificaciones y todo termina en hojas.
flowchart TB boeing["✈ Boeing 747"] pilot["🤠 Peter"] boeing -- pilot --> pilot passengers["[🤗 Julia, 🫣 Mary, 🙄 Tom, 😮💨 Jane, 😮 Albert, 🤢 Josephine, 🥴 Clara, 😵💫 Mike"] boeing -- passengers --> passengers
Se comienza con un objeto y se van procesando de forma recursiva todas las propiedades que son referencias hasta que no queda ningún objeto referenciado por procesar.
Por ejemplo, si David
tiene una referencia directa o indirecta con Esther
, y Esther
tiene una referencia directa o indirecta con David
...
flowchart LR david["😺 David"] esther["🦝 Esther"] david --> esther esther --> david
la recursión no terminará nunca 💫💫💫💫💫 ...
export {}
type Person = { name: string, couple: Person | null }
const david: Person = { name: "David", couple:null }
const esther: Person = { name: "Esther", couple: david}
david.couple = esther
await Bun.write("david.json", JSON.stringify(david))
pero JSON no lo permite 😱 ... no cree en el amor enterno 💞 ...
> bun run .\test.ts
4 |
5 | const david: Person = { name: "David", couple:null }
6 | const esther: Person = { name: "Esther", couple: david}
7 | david.couple = esther
8 |
9 | await Bun.write("david.json", JSON.stringify(david))
^
TypeError: JSON.stringify cannot serialize cyclic structures.
at C:\Users\david\Workspace\json\test.ts:9:36
que puede hacer el pobre gatito 🙀🙀 ??
Cambiar el disseño 😼 ... y no poner fecha de finalización (siempre optimista, pensar que las parejas son para siempre 💞) ...
classDiagram direction LR class Person { name: string } class Couple { start: string } Couple --> Person : one Couple --> Person : two
Y ya podemos escribir el código:
export {}
type Person = { name: string }
type Couple = { start: string, one: Person, two: Person}
const david: Person = { name: "David"}
const esther: Person = { name: "Esther"}
const couple: Couple = { start: "Un noche de lluvía con luna en el cielo", one: david, two: esther}
await Bun.write("david-and-esther.json", JSON.stringify(couple))
Y JSON dice que sí 😀, además es más romántico 🌹:
> bun run .\test.ts
> Get-Content .\david-and-esther.json
{"start":"Un noche de lluvía con luna en el cielo","one":{"name":"David"},"two":{"name":"Esther"}}
El que la fecha tenga que estar en formato RFC 3339 es un estilo Google ... el start
de este còdigo también es una fecha "tipo" string
🌈.
Pero hay un problema, una pareja es cosa de dos, one
y two
que no es lo mismo que first
y second
aunque algunos lo interpretarán de esta manera, y el he
y she
hace tiempo que no se admite como exclusivo ...
La próxima vez dieseñamos un zoo 😒: 🐯 🦒 🐮 🐰 🦓 🐼 🐨 🐑 🦛 🦏 🦬 🐆
Actividad - Vuelos Pájaro Bobo
La empresa "Vuelos Pájaro Bobo" es una start-up que necesita un sistema básico de gestión de vuelos.
A continuación tienes el diseño de la estructura de datos:
classDiagram direction TB class Flight { << 🐦🔥 >> id: string 🔑 ufoSeen: boolean 🛸 } class Plane { << ✈ >> name: string 🔑 seats: number 💺 } class Airport { << ⛩ >> code: srting 🔑 name: string city: string country: string } class Passenger { << 🙂 >> name: string 🔑 face?: string } Flight --> Plane Flight --> "1..* 🤗 🫣 🙄 😮" Passenger Flight --> Airport : departure 🛫 Flight --> Airport: arrival 🛬
De momento sólo tiene vuelos desde "Barcelona", código BCN
, y sí, los pasajeros se identifican por el nombre.
Por tanto, si en un vuelo ya hay un pasajero con nombre "Eva" y otra "Eva" quiere viajar, o se cambia el nombre o no puede viajar en ese vuelo 👏.
A continuación tienes un ejemplo para el vuelo bcn-cdg-tomorrow-morning-after-breakfast.json
:
{
"id": "bcn-cdg-tomorrow-morning-after-breakfast",
"ufoSeen": false,
"plane": {
"name": "fearless-ostrich",
"seats": 50
},
"departure": {
"code": "BCN",
"name": "Aeroport Josep Tarradellas Barcelona",
"city": "Barcelona",
"country": "Spain"
},
"arrival": {
"code": "CDG",
"name": "Paris-Charles de Gaulle",
"city": "Paris",
"country": "France"
},
"passengers": [
{"name": "Julia", "face": "🤗" },
{"name": "Mary", "face": "🫣" },
{"name": "Josephine", "face": "🙄" },
{"name": "Albert", "face": "🤢" },
{"name": "Mike", "face": "😵💫" }
]
}
Tienes que completar el script bobo.ts
para que permita añadir pasajeros:
export {}
console.log("Welcomo to Vuelos Pájaro Bobo")
// 👉 tu turno!
Para gestionar ficheros mira esta documentación: https://bun.sh/docs/api/file-io
Al finalizar tienes que crear un binario ejecutable:
> bun build ./bobo.ts --compile --outfile bobo
[31ms] bundle 1 modules
[394ms] compile bobo.exe
Este binario se puede ejecutar direcamente en cualquier sistema Windows:
> .\bobo.exe
Welcome to Vuelos Pájaro Bobo