JSON permet representar estructures d'objectes en format textual.

Introducció

JSON (JavaScript Object Notation) és un format d’intercanvi de dades creat l’any 2001 per Douglas Crockford, i que permet a qualsevol aplicació comunicar-se a través d’Internet amb una altra aplicació.

Estructures de dades

JSON es basa en una propietat fonamental dels llenguatges de programació: qualsevol estructura de dades es pot representar mitjançant diccionaris i llistes de manera recursiva.

Diccionari

A continuació tens un exemple molt senzill:

{ "nom": "Eva" }

És tracta d’un diccionari amb només una entrada, on "nom" és la clau i "Eva" és el valor.

Els parells nom/valor tenen les característiques següents:

  • El nom ésta al costat esquerra del dos punts :
  • El nom és un string i ha d’estar envoltat de cometes dobles ""
  • El valor es troba a la dreta dels dos punts.

El concepte de diccionari ve del diccionari físic, i és el terme que utilitza Python encara que llenguatges com Kotlin i altres fan servir el nom de Map per referirse al mateix

Originalment JSON es feia servir a l'entorn de Javascript.

És important que tinguis en compte aquest fet perquè un objecte de Javascript no te res a veure amb els objectes dels altres llenguatges de programació.

JavaScript no inclou un tipus de dades nadiu "diccionari" perquè els objectes en JavaScript són força flexibles i es poden utilitzar per crear parells clau-valor. Aquests objectes són força semblants als diccionaris i funcionen igual.

En la majoria dels llenguatges de programació els objectes tenen una estructura fixa (no flexible com un diccionari) i un conjunt de funcions associades tal com pots veure a Python - Objectes.

En resum, que JSON (JavaScript Object Notation) fa referència a un objecte que s'assembla molt a un diccionari, i has de pensar en un diccionari perquè JSON és molt flexible.

Javascript

Per exemple aquí tens un objecte JSON que representa una persona:

{
  "name": "Eva",
  "age": 3
}

A continuació tens el mateix objecte representat amb un objecte de Javascript:

{
    name: "Eva",
    age: 3
}

I un exemple d’ús de l'objecte en Javascript:

Fixa't que l’única diferència és que JSON utilitza cometes dobles per les claus !

I sempre has d’utilitzar cometes dobles al representar un string, mai pots utilitzar cometes simples: 'Eva' no és vàlid en JSON.

Python

Aquí tens un exemple d’un objecte JSON que representa un llibre:

{
  "book": "The Art of Computer Programming",
  "author": "Donald Knuth"
}

El mateix llibre representat com un diccionari en Python - Objectes:

{
  "book": "The Art of Computer Programming",
  "author": "Donald Knuth"
}

Alguna diferència? 🤗

I un exemple del seu ús en Python:

Llista

A continuació tens un exemple d’una llista:

[ 1, "hello", false ]

Una llista és un conjunt ordenat d'elements que es pot accedir per posició.

JSON no fa servir el terme de seqüència, sinò array.

Però un array de Javascript no té res a veure amb un array de Kotlin perquè pot tenir elements de diferents tipus i no té un tamany fixe!

Javascript

Aquí tens una llista de noms en JSON:

["Eva", "Marc", "Joan"]

I el mateix "array" en Javascript: 🤗

["Eva", "Marc", "Joan"]

Pots veure que l’array no té tamany fixe:

Python

Aquí tens una llista en JSON:

[1 ,"hello", false]

I la mateixa llista en Python 🤗 :

[1 ,"hello", False]

Pots veure que la llista no té tamany fixe:

Tipus básics

JSON només té tres tipus bàsics !

String

["fred", "fred\t", "\b", "", "\t", "\u004A" ]

Un string té aquestes propietats:

  • Un string consisteixe en zero o més caràcters Unicode entre cometes dobles ("").
  • Un string entre cometes simples (') no és vàlid – a diferència de Javascript i Python.

A més un string pot contenir els següents caràcters "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
}

Els números segueixen el format de coma flotant de doble precisió de JavaScript i tenen les propietats següents:

  • Els nombres sempre estan en base 10 (només es permeten els dígits del 0 al 9) sense zeros inicials.
  • Els nombres poden tenir una part fraccionària que comença amb un punt decimal (.).
  • Els nombres poden tenir un exponent de 10, que es representa amb la notació e o E amb un signe positiu o negatiu per indicar una exponenciació positiva o negativa.
  • No s'admeten els formats octal i hexadecimal.
  • A diferència de JavaScript, els números no poden tenir un valor de NaN (no és un nombre per a números no vàlids) o Infinity.

Boolean

{
  "isRegistered": true,
  "emailValidated": false
}

Els booleans tenen les següents propietats:

  • Els booleans només poden tenir un valor de true o false.
  • El valor true o false a la part dreta dels dos punts (:) no està envoltat de cometes.

Null

Tot i que tècnicament no és un tipus de valor, null és un valor especial en JSON.

{
  "address": {
    "line1": "555 Any Street",
    "line2": null,
    "city": "Denver",
    "stateOrProvince": "CO",
    "zipOrPostalCode": "80202",
    "country": "USA"
  }
}

Els valors nuls tenen les característiques següents:

  • No estan envoltats de cometes
  • Indica que una clau/propietat no té valor
  • Actua com a marcador de posició

Estructura de dades recursiva

Combinant diccionaris i llistes, i amb només tres tipus básics, pots representar qualsevol tipus de dades:

{
  "clients": [
    { "id": 11, "nom": "Eva"},
    { "id": 23, "nom": "Marc"},
    { "id": 36, "nom": "Esther", "adreça": 
        { "carrer": "Girona", "ciutat": "Barcelona"}
    }
  ]
}

A continuació tens un altre exemple amb JSON:

{
  "students": [ 
     { 
        "id":"01", 
        "name": "Tom", 
        "lastname": "Price" 
     }, 
     { 
        "id":"02", 
        "name": "Nick", 
        "lastname": "Thameson" 
     } 
  ]   
}

I el mateix exemple amb XML

<?xml version="1.0" encoding="UTF-8" ?>
<students>
	<student>
		<id>01</id>
		<name>Tom</name>
		<lastname>Price</lastname>
	</student>
	<student>
		<id>02</id>
		<name>Nick</name>
		<lastname>Thameson</lastname>
	</student>
</students>

Pots veure que en JSON queda clar que és una llista i que és un diccionari, i perquè ha substituit a XML com a format d'intercanvi de dades d'Internet.

Altres

Versions

Segons Douglas Crockford, mai hi haurà una altra versió de l'estàndard JSON bàsic. Això no és perquè JSON sigui perfecte; res és perfecte. L'objectiu d'una única versió de JSON és evitar els inconvenients d'haver de suportar la compatibilitat amb versions anteriors. Crockford creu que un nou format de dades hauria de substituir JSON quan sorgeixi la necessitat de la comunitat de desenvolupament.

Comentaris

No hi ha comentaris en un document JSON.

Crockford va permetre inicialment comentaris, però els va eliminar al principi pels motius següents:

  • Creia que els comentaris no eren útils.
  • Els "parsers" JSON tenien dificultats per donar suport als comentaris.
  • La gent abusava dels comentaris. Per exemple, es va adonar que s'estaven utilitzant comentaris per fer "parsing" de directives, cosa que hauria destruït la interoperabilitat.
  • Eliminar els comentaris simplifica i permet que JSON sigui multiplataforma.

Fitxer i tipus MIME

Segons l'especificació bàsica de JSON, .json és el tipus de fitxer JSON estàndard quan s'emmagatzemen dades JSON als sistemes de fitxers.

El tipus de suport (o MIME) d'Internet Assigned Numbers Authority (IANA) de JSON és application/json, que es pot trobar al lloc de tipus de mitjans de l'IANA. Els productors i consumidors de serveis web RESTful utilitzen una tècnica coneguda com a negociació de contingut (que aprofita el tipus MIME JSON a les capçaleres HTTP) per indicar que estan intercanviant dades JSON.

Convencions

JSON només té tres tipus bàsics perquè amb un string pots representar qualsevol tipus de dades.

Només cal que tots ens posem d’acord en com representem una dada.

En aquest enllaç tens la guia d’estil de Google respecte JSON: JSON Style Guide.

Data

Per exemple, Google prefereix que les dates segueixin el format RFC 3339:

{
  "dateRegistered": "2014-03-01T23:46:11-05:00"
}

La data anterior proporciona un desplaçament de l'hora universal coordinada (UTC) (des de UTC/GMT—Hora mitjana de Greenwich) de -5 hores, que és l'hora estàndard de l'est dels EUA. Tingueu en compte que la RFC 3339 és un perfil de la ISO 8601. La principal diferència és que la ISO 8601 de l'Organització Internacional d'Estàndards permet substituir la T (que separa la data i l'hora) per un espai, i la RFC 3339 no ho permet.

Valors de latitud/longitud

Les API geogràfiques (p. ex., Google Maps) i les API relacionades amb un sistema d'informació geogràfica (SIG) utilitzen dades de latitud/longitud. Per garantir la coherència, Google recomana que les dades de latitud/longitud segueixin l'estàndard ISO 6709.

Segons Google Maps, les coordenades de l'Empire State Building a la ciutat de Nova York són 40,748747° N, 73,985547° O i estarien representades en JSON.

{
    "empireStateBuilding": "40.748747-73.985547"
}

Aquest exemple segueix el format ±DD.DDDD±DDD.DDDD, amb les convencions següents:

  • La latitud és el primer.
  • La latitud nord (de l'equador) és positiva.
  • La longitud est (del primer meridià) és positiva.
  • La latitud/longitud es representa amb un string. No pot ser un nombre a causa del signe menys.

Activitat

Executa tots els exemples que s’han mostrat en entorn Javascript i Python amb alguna modificació.

Arquitectura noBackEnd

El concepte de noBackend fa referència a un procés de construcció del software en que en les primeres etapes del desenvolupament d'aplicacions no ens hem d’ocupar dels servidors d'aplicacions o bases de dades que donaran suport a l’aplicació de servidor.

El focus de la nostra aplicació és en l’API (primer els serveis i les dades).

API

Mitjançant aquest enfoc, primer dissenyem la interficie i construim l’API, de tal manera que:

  • Desenvolupem el frontend de manera més àgil, ràpida i de manera iterativa gràcies al desacoblament amb el backend.
  • Aconseguim una retroalimentació més ràpida sobre la pròpia API.
  • Una interfície més neta entre l'API i els seus Consumers.
  • Una separació lógica entre el Resource exposat per l'API (representat amb dades JSON) i la seva (eventual) implementació interna (servidor, base de dades). Això fa que sigui més fàcil canviar la implementació en el futur.

Per tant, és necessita una Stub API que fa el següent:

  • Elimina la necessitat inicial de treballar amb servidors i bases de dades
  • Permet als API Producers (els desenvolupadors que escriuen l'API) centrar-se en el disseny de l'API, la millor manera de presentar les dades als Consumers i les proves inicials
  • Permet als API Consumers (per exemple, desenvolupadors d'interfície d'usuari) treballar amb l'API des d’una etapa inicial i proporcionar comentaris a l'equip de desenvolupament de l’API.

Dummy

A internet tens serveis amb dades “dummy” com https://dummyjson.com/ o https://jsonplaceholder.typicode.com per fer proves amb una API REST.

Per interactuar amb l’API pots utilitzar el navegador o httpie.

Per exemple, pots obtenir un producte per index:

http -b https://dummyjson.com/products/1
{
    "availabilityStatus": "Low Stock",
    "brand": "Essence",
    "category": "beauty",
    "description": "The Essence Mascara Lash Princess is a popular mascara known for its volumizing and lengthening effects. Achieve dramatic lashes with this long-lasting and cruelty-free formula.",
    "dimensions": {
        "depth": 28.01,
        "height": 14.43,
        "width": 23.17
    },
    ...

Activitat

Consulta altres dades.

AEMET

Ves a la pàgina de AEMET i registra't per obtenir una API Key: AEMET OpenData.

Guarda la teva API Key en una variable (la clau que et mostro no és valida!):

$ KEY=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkZGVtaW5nb0B4dGVjLmNhdCIsImp0aSI6ImE4N2I3M2Y5LWQ0YWYtNDc2My1hMzcwLTZlOTZjZjRiM2UxYSIsImlzcyI6IkFFTUVUIiwiaWF0IjoxNzI0MTc4MTg4LCJ1c2VySWQiOiJhODdiNzNmOS1kNGFmLTQ3NjMtYTM3MC02ZTk2Y2Y0YjNlMWEiLC

En aquesta pàgina tens una descripció de tots els serveis de consulta disponibles: AEMET OpenData - Data specification

Si mires la documentació tenim un endpoint en que podem consultar la predicció específica diaria d'un municipi: /api/prediccion/especifica/municipio/diaria/{municipio}.

Els codis de poblacions els pots trobar en l'enllaç Código de municipio.

A continuació cosultarem la predicció específica diaria del municipi de barcelona que té el codi 08019:

http -b https://opendata.aemet.es/opendata/api/prediccion/especifica/municipio/diaria/08019/?api_key=$KEY       
{
    "datos": "https://opendata.aemet.es/opendata/sh/a5a38371",
    "descripcion": "exito",
    "estado": 200,
    "metadatos": "https://opendata.aemet.es/opendata/sh/dfd88b22"
}

Si vaig a l'enllaç de "datos" puc obtenir les dades que he demanat:

$ http -b https://opendata.aemet.es/opendata/sh/a5a38371 
[
    {
        "elaborado": "2024-08-20T18:36:08",
        "id": 8019,
        "nombre": "Barcelona",
        ...

A l'activitat JSON - Path aprendrás com processar aquestes dades.

Per exemple:

$ http -b https://opendata.aemet.es/opendata/api/prediccion/especifica/municipio/diaria/08019/?api_key=$KEY | jq .datos
"https://opendata.aemet.es/opendata/sh/a5a38371"

Consultes

Consulta altres endpoints

Python

La serialització i la deserialització JSON són els processos de conversió de dades JSON a i des d'altres formats, com ara objectes o strings de Python, per transmetre o emmagatzemar les dades.

A continuació tens un exemple em que serialitzem un objecte Python a un string JSON.

Crea el fitxer data.py:

import json

data = {
    "email": None,
    "name": "John Doe",
    "age": 42.5,
    "married": True,
    "children": ["Alice", "Bob"],
}

text = json.dumps(data, indent=4, sort_keys=True)

print(text)

Executem el fitxer:

$ python3 data.py 
{
    "age": 42.5,
    "children": [
        "Alice",
        "Bob"
    ],
    "email": null,
    "married": true,
    "name": "John Doe"
}

Pots veure que el resultat és un string de Python formatejat segons les regles gramaticals de JSON.

Funcions comunes

A continuació es mostrens algunes funcions comunes de la llibreria json que s'utilitzen per a la serialització i la deserialització.

json.dumps()

Aquesta funció s'utilitza per serialitzar un objecte Python en una string JSON.

La funció dumps() pren un sol argument, l'objecte Python, i retorna una string JSON.

Aquí tens un exemple: import json

# Python object to JSON string
python_obj = {'name': 'John', 'age': 30}

json_string = json.dumps(python_obj)
print(json_string)  

# output: {"name": "John", "age": 30}

json.loads()

Aquesta funció s'utilitza per un "parse" d’un srting JSON en un objecte Python.

La funció loads() pren un sol argument, el string JSON, i retorna un objecte Python.

Aquí tens un exemple:

import json

# JSON string to Python object
json_string = '{"name": "John", "age": 30}'


python_obj = json.loads(json_string)


print(python_obj)  

# output: {'name': 'John', 'age': 30}

json.dump()

Aquesta funció s'utilitza per serialitzar un objecte Python i escriure'l en un fitxer JSON.

La funció dump() pren dos arguments, l'objecte Python i l'objecte fitxer.

Aquí tens un exemple:

import json
# serialize Python object and write to JSON file
python_obj = {'name': 'John', 'age': 30}
with open('data.json', 'w') as file:
    json.dump(python_obj, file)

json.load()

Aquesta funció s'utilitza per llegir un fitxer JSON i analitzar-ne el contingut en un objecte Python.

La funció load() pren un sol argument, l'objecte fitxer, i retorna un objecte Python.

Aquí tens un exemple:

import json

# read JSON file and parse contents
with open('data.json', 'r') as file:
    python_obj = json.load(file)
print(python_obj)  

# output: {'name': 'John', 'age': 30}

AEMET

A continuació tens un codi que utilitza les llibreries requests i json per fer una sol·licitud a l'AEMET i recuperar dades:

import requests
import json

municipio = "08019"

key = "eyJhbGciOiJIUzI1NiJ9..."  # Utlitza la teva API Key
url =  f"https://opendata.aemet.es/opendata/api/prediccion/especifica/{municipio}/diaria/08019/?api_key={key}"

response = requests.get(url)

if response.status_code == 200:
    data = json.loads(response.text)
    print(data["datos"])
else:
    print(f"Error retrieving data, status code: {response.status_code}")
  • La línia requests.get(url) fa la sol·licitud real i emmagatzema la resposta a la variable response.

  • La línia if response.status_code == 200: comprova si el codi de resposta és 200, la qual cosa significa que la sol·licitud ha tingut èxit.

  • Si la sol·licitud té èxit, el codi carrega el text de resposta en un diccionari de Python mitjançant el mètode json.loads() i l'emmagatzema a la variable data.

Si executes el codi tens la URL on estan les dades meteorològiques:

$ python3 aemet.py 
https://opendata.aemet.es/opendata/sh/a5a38371

Modifica el codi perquè consulti "datos" i torni la probabilitat de precipitació:

import requests
import json

municipio = "08019"

key = "eyJhbGciOiJIUzI1NiJ9..."
url =  f"https://opendata.aemet.es/opendata/api/prediccion/especifica/municipio/diaria/{municipio}/?api_key={key}"

response = requests.get(url)

if response.status_code == 200:
    data = json.loads(response.text)
    datos = data["datos"]
else:
    print(f"Error retrieving data, status code: {response.status_code}")
    exit

response = requests.get(datos)

if response.status_code == 200:
    data = json.loads(response.text)
    data = data[0]["prediccion"]["dia"][0]["probPrecipitacion"]
    print(json.dumps(data, indent=4))
else:
    print(f"Error retrieving data, status code: {response.status_code}")
    exit

Resultat:

[
    {
        "value": 0,
        "periodo": "00-24"
    },
    ...
]

Javascript

Amb node.js

Document web

Activitat

TMB