← Índice del curso

Internet de las Cosas · Tema 21

JSON para intercambiar datos

JSON es la lengua franca de los datos en la web: lo hablan los navegadores, los servidores y casi todas las plataformas IoT. Escribirlo y leerlo es trivial en una computadora y delicado en un microcontrolador, donde un documento de unos pocos kilobytes puede agotar la memoria. Este tema enseña a usarlo sin sorpresas.

01 · Punto de partida

El formato que todos entienden

En los temas anteriores el cuerpo de las peticiones fue siempre un fragmento de JSON, como {"d":"c07","t":1794061920,"v":[41]}. Es hora de detenerse en él. JSON (JavaScript Object Notation) es un formato de texto para representar datos estructurados: números, textos, listas y objetos anidados. Nació en el mundo web, pero hoy es el formato por defecto de las APIs, de las bases de datos de documentos y de la mayoría de las plataformas IoT en la nube.

Sus virtudes explican su éxito: es legible por personas, autodescriptivo (cada dato lleva su nombre), flexible (se pueden agregar campos sin romper a quien no los conoce) y universal (cualquier lenguaje lo lee y lo escribe). Su defecto, que ya medimos en el tema 12, es que repite los nombres de los campos en cada mensaje y escribe los números como texto. En un servidor eso no importa; en un dispositivo con poca RAM y un canal caro, sí.

Los fragmentos de C++ usan la biblioteca ArduinoJson, la más difundida en la plataforma Arduino. Su interfaz cambió entre versiones mayores; los ejemplos siguen el estilo de la versión 7 y el texto señala las diferencias importantes. Verificalos contra la documentación de la versión que uses.

02 · Reglas

Sintaxis y reglas estrictas

JSON tiene solo seis tipos de valor. Con ellos se arma cualquier estructura:

Los seis tipos de valor de JSON.
TipoEjemploNotas
Objeto{"id":401,"accion":"abrir"}Pares nombre-valor entre llaves; los nombres son textos entre comillas dobles
Arreglo[41,44,47]Lista ordenada de valores entre corchetes; pueden ser de tipos distintos
Cadena"camara-07"Texto entre comillas dobles, en UTF-8; los caracteres especiales se escapan (\", \\, \n, á)
Número22.3, -5, 1.5e3Sin distinción entre entero y decimal; sin ceros a la izquierda ni punto inicial
Booleanotrue, falseEn minúscula, sin comillas
NulonullValor ausente o desconocido

Lo que desconcierta a quien viene de JavaScript, Python o YAML es lo estricto que es JSON. Cualquier desvío lo vuelve inválido, y un analizador (parser) rechaza el documento entero:

Errores frecuentes que vuelven inválido un JSON.
InválidoPor quéCorrecto
{'t': 22.3}Comillas simples{"t": 22.3}
{t: 22.3}Nombre sin comillas{"t": 22.3}
{"t": 22.3,}Coma final{"t": 22.3}
{"t": 22,3}Coma decimal (la coma separa elementos){"t": 22.3}
{"t": .5}Punto sin cero inicial{"t": 0.5}
{"t": NaN}JSON no tiene NaN ni infinito{"t": null} (con un campo de calidad)
{"t": 22.3 // grados}JSON no admite comentariosSin comentarios
{"t": 22.3Falta la llave de cierre (mensaje cortado){"t": 22.3}

El último caso es el más relevante en IoT: un mensaje truncado por un búfer chico o una conexión cortada es JSON inválido, y un parser lo detecta, cosa que no hace un formato de ancho fijo. Es una ventaja de la verbosidad: el JSON es autoverificable en su estructura.

03 · Decisiones de formato

Cómo representar los datos

JSON no dice cómo representar tu información; eso lo decidís vos, y las decisiones pesan en bytes y en claridad. Las que más importan:

  • Números. Un entero escalado ("t":223 para 22,3 °C) es más corto y exacto que un decimal ("t":22.3), y evita los errores de representación de punto flotante. La escala se documenta en el esquema (tema 12). JSON no admite NaN ni infinito: un dato inválido se marca con null y un campo de calidad, no con un número.
  • Tiempo. Como número entero de segundos desde 1970 (1794061920) o como texto ISO 8601 en UTC ("2026-11-07T14:32:00Z"). El número es más corto; el texto, más legible. Nunca una hora local sin zona.
  • Valores ausentes. Hay dos formas: omitir el campo o ponerlo en null. Conviene fijar una regla: por ejemplo, «un campo ausente significa que no cambió, un null significa que no hay dato».
  • Booleanos. true y false, no "true" ni 1: un tipo claro evita ambigüedades en el servidor.
  • Datos binarios. JSON es texto y no puede llevar bytes crudos. Se codifican en base64, que agrega un tercio de tamaño; para cantidades grandes conviene un formato binario (tema 12) o enviar el archivo aparte.
  • Nombres. Cortos y constantes en dispositivos con canal limitado ("v" en lugar de "valores_de_temperatura"), con el significado en el esquema. En interfaces con personas, nombres claros.
  • Forma de los lotes. Para muchas lecturas, un arreglo de números con tiempo base e intervalo es mucho más compacto que un arreglo de objetos donde cada lectura repite los nombres. Lo veremos con cifras en el laboratorio.

04 · Escribir

Armar JSON en el firmware

Hay dos maneras de producir un mensaje JSON, con ventajas distintas.

Con snprintf: rápido y sin biblioteca

Para mensajes de forma fija, formatear el texto con snprintf es lo más liviano: no necesita memoria dinámica ni biblioteca.

char mensaje[96];
int n = snprintf(mensaje, sizeof(mensaje),
                 "{\"d\":\"%s\",\"t\":%lu,\"i\":%d,\"v\":[%d,%d,%d],\"b\":%d}",
                 ID_DISPOSITIVO, (unsigned long)instante, 60, v1, v2, v3, bateria);
if (n < 0 || n >= (int)sizeof(mensaje)) {
  // el mensaje no cabe: el texto quedó truncado y NO es JSON válido
  Serial.println("Mensaje demasiado grande");
  return;
}

El control de la longitud devuelta es esencial: snprintf corta en silencio si no hay lugar, y un JSON cortado se rechaza en el servidor. Los riesgos de este método: los textos con comillas o barras (un nombre como cámara "norte" rompería el documento si no se escapa), los decimales (%f puede imprimir nan o inf, que no son JSON, y en algunas plataformas no formatea flotantes) y el mantenimiento: al crecer el mensaje, la cadena de formato se vuelve ilegible. Por eso se recomienda usar enteros escalados y reservar este método para mensajes chicos y fijos.

Con una biblioteca: ArduinoJson

Cuando el mensaje es variable o anidado, una biblioteca construye el documento y se ocupa del escape y de la sintaxis.

#include <ArduinoJson.h>

JsonDocument doc;                               // ArduinoJson 7: crece según haga falta
doc["d"] = "c07";
doc["t"] = (uint32_t)instante;
doc["i"] = 60;
JsonArray v = doc["v"].to<JsonArray>();
for (int i = 0; i < cantidad; i++) v.add(lecturas[i]);
doc["b"] = bateria;

size_t largo = measureJson(doc);                // cuántos bytes va a ocupar el texto
char mensaje[256];
if (largo >= sizeof(mensaje)) return;           // no entra en el búfer
serializeJson(doc, mensaje, sizeof(mensaje));   // escribe el texto, con escapes correctos

La biblioteca ofrece measureJson para saber el tamaño antes de escribir, lo que permite reservar el búfer justo o decidir enviar en dos mensajes. En versiones anteriores (6) el documento se declara con un tamaño fijo (por ejemplo StaticJsonDocument<256>), que se calcula de antemano y se asigna en la pila; la ventaja es que el uso de memoria es previsible y no fragmenta el montón. En ambos casos, el texto sale con los escapes correctos, por lo que los nombres con comillas no rompen nada.

05 · Leer

Leer JSON con poca memoria

Analizar un JSON (deserializar) tiene un costo que sorprende: el documento en memoria ocupa varias veces el tamaño del texto. El analizador construye un árbol donde cada valor (número, texto, elemento de arreglo, miembro de objeto) ocupa su propio espacio, del orden de 16 bytes en un microcontrolador de 32 bits, más los textos de los nombres y de las cadenas. Un texto de 1464 bytes puede pedir 3183 bytes de memoria, y uno de 4814 bytes, más de 10.000.

El laboratorio de este tema calcula esas cifras. Mientras tanto, estas son las estrategias que realmente reducen el consumo:

  • Pedir mensajes compactos al servidor. Un arreglo de números pesa mucho menos que un arreglo de objetos con los mismos datos; una respuesta con solo los campos que el dispositivo usa, menos que una completa. Es la medida más efectiva y se decide en la interfaz (tema 20).
  • Leer del flujo de la red en lugar de guardar primero todo el texto en un búfer: así el texto recibido no ocupa RAM al mismo tiempo que el árbol.
  • Usar un filtro. ArduinoJson permite indicar qué campos interesan y descartar el resto durante el análisis; el árbol contiene solo lo que se pidió.
  • Analizar por partes las respuestas muy largas (cientos de lecturas): procesar un elemento por vez y soltarlo, sin guardar la lista completa.
  • Limitar el tamaño y la profundidad aceptados, para que una respuesta inesperada no agote la memoria.
// Leer la configuración directamente del flujo de la red, quedándose solo con dos campos.
JsonDocument filtro;
filtro["periodo_s"] = true;
filtro["umbral_c"] = true;

JsonDocument doc;
DeserializationError error = deserializeJson(doc, http.getStream(),
                                             DeserializationOption::Filter(filtro));
if (error) {
  Serial.print("JSON inválido: ");
  Serial.println(error.c_str());                 // por ejemplo "NoMemory" o "IncompleteInput"
  return;                                        // conservar la configuración anterior
}

int   periodo = doc["periodo_s"] | 900;          // si falta el campo, usar el valor por defecto
float umbral  = doc["umbral_c"]  | 8.5;

El operador | de ArduinoJson devuelve el valor por defecto si el campo falta o tiene otro tipo. Es la forma idiomática de leer sin que un campo ausente cause un error: si el servidor agrega o quita un campo opcional, el firmware sigue funcionando. Y fijate en el return ante un error: si el JSON es inválido, el dispositivo conserva su configuración anterior y no la reemplaza por valores a medias.

06 · Desconfiar

Validar lo que llega

Que el JSON sea sintácticamente válido no significa que su contenido sea correcto ni seguro. Una respuesta puede llegar bien formada y decir cualquier cosa. El firmware valida en dos niveles:

  • Del mensaje: que sea realmente JSON (a veces un servidor o un portal cautivo responde con una página HTML de error; conviene comprobar el Content-Type y que el primer carácter sea { o [), que no exceda el tamaño máximo y que la versión del esquema sea una que el equipo entiende.
  • De los campos: que existan los obligatorios, que tengan el tipo esperado y que sus valores estén en un rango razonable. Un período de medición de 0 segundos, un umbral de 10.000 °C o un tiempo de riego de 40 horas son órdenes que el dispositivo debe rechazar, aunque estén bien escritas. Es el mismo principio de los límites duros del tema 11.

Cuatro reglas de robustez que conviene fijar desde el principio:

  1. Un valor por defecto para cada campo opcional, definido en el firmware.
  2. Se aplican cambios completos o ninguno: si la validación falla en un solo campo, la configuración nueva se descarta entera y se informa el motivo.
  3. Se ignoran los campos desconocidos, para que servidores nuevos convivan con firmware viejo.
  4. Nunca se arma JSON concatenando texto que viene de afuera sin escapar (un nombre con comillas puede cambiar la estructura del mensaje, el equivalente en JSON de una inyección). Se usa la biblioteca o se escapan los caracteres.

07 · Analogía con programación

Medir la memoria de un documento en Python

El programa construye cinco mensajes JSON de este curso: una lectura, sesenta lecturas en un arreglo, sesenta lecturas como objetos (cada una con sus claves), una configuración y doscientas lecturas históricas. Para cada uno mide el tamaño del texto, cuenta los valores del árbol y estima la memoria que necesitaría un analizador en un microcontrolador de 32 bits: 16 bytes por valor más los bytes de los nombres y las cadenas. Después indica cuáles caben con 2, 8 y 32 KB, y termina leyendo una configuración con valores por defecto y rechazando tres documentos inválidos.

Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

import json

BYTES_POR_VALOR = 16       # lo que ocupa cada valor del árbol en memoria (estimación para un microcontrolador de 32 bits)


def valores_y_textos(x):
    """Cuenta los valores del documento y los bytes de texto (claves y cadenas, con su terminador)."""
    if isinstance(x, dict):
        valores, textos = len(x), sum(len(k) + 1 for k in x)
        for v in x.values():
            a, b = valores_y_textos(v)
            valores, textos = valores + a, textos + b
        return valores, textos
    if isinstance(x, list):
        valores, textos = len(x), 0
        for v in x:
            a, b = valores_y_textos(v)
            valores, textos = valores + a, textos + b
        return valores, textos
    return 0, (len(x) + 1 if isinstance(x, str) else 0)


def lectura_larga(i):
    return {"m": 1794061920 + 60 * i, "t": 41 + (i * 3) % 8}


MENSAJES = {
    "una lectura": {"d": "c07", "t": 1794061920, "i": 60, "v": [41], "b": 87},
    "60 lecturas en un arreglo": {"d": "c07", "t": 1794061920, "i": 60, "v": [41 + (i * 3) % 8 for i in range(60)], "b": 87},
    "60 lecturas como objetos": {"d": "c07", "lecturas": [lectura_larga(i) for i in range(60)]},
    "configuración": {"v": 17, "periodo_s": 900, "umbral_c": 8.5, "servidor": "datos.ejemplo.com",
                      "comandos": [{"id": 401, "accion": "abrir", "min": 10}],
                      "wifi": {"canal": 6, "bssid": "AA:BB:CC:DD:EE:FF"}},
    "200 lecturas históricas": {"lecturas": [lectura_larga(i) for i in range(200)]},
}


def analizar(nombre):
    texto = json.dumps(MENSAJES[nombre], separators=(",", ":"))
    valores, textos = valores_y_textos(MENSAJES[nombre])
    valores += 1                                            # el valor raíz
    memoria = valores * BYTES_POR_VALOR + textos
    return len(texto), valores, memoria


print("mensaje                       texto    valores   memoria estimada   memoria/texto")
for nombre in MENSAJES:
    texto, valores, memoria = analizar(nombre)
    print(f"{nombre:<26} {texto:>8} B {valores:>8} {memoria:>12} B {memoria / texto:>14.1f}")

print()
for ram_kb in (2, 8, 32):
    caben = [n for n in MENSAJES if analizar(n)[2] <= ram_kb * 1024]
    print(f"Con {ram_kb} KB para el documento caben: {', '.join(caben)}")

print()
texto = '{"periodo_s": 900, "umbral_c": 8.5, "comandos": [{"id": 401, "accion": "abrir"}]}'
datos = json.loads(texto)
print("periodo_s =", datos["periodo_s"], "| primer comando:", datos["comandos"][0]["accion"], "| campo ausente:", datos.get("modo", "automático"))
for roto in ('{"periodo_s": 900,}', "{'periodo_s': 900}", '{"periodo_s": 900'):
    try:
        json.loads(roto)
    except json.JSONDecodeError as e:
        print(f"{roto!r:<28} no es JSON válido")

Resultado con los valores iniciales. Una lectura: 49 bytes de texto, 7 valores, 126 bytes de memoria (2,6 veces el texto). Sesenta lecturas en un arreglo: 226 bytes de texto, 66 valores, 1070 bytes (4,7 veces). Sesenta lecturas como objetos: 1464 bytes de texto, 183 valores, 3183 bytes (2,2 veces). Configuración: 167 bytes de texto, 13 valores, 320 bytes. Doscientas lecturas históricas: 4814 bytes de texto, 602 valores, 10.441 bytes. Con 2 KB caben la lectura suelta, el arreglo de 60 y la configuración; con 8 KB, además los 60 objetos; con 32 KB, todos. El programa lee periodo_s = 900 y el primer comando abrir, usa el valor por defecto automático para el campo ausente modo y rechaza los tres documentos inválidos.

Tres conclusiones. Una: la memoria del árbol es entre 2 y 5 veces el texto; un mensaje que parece chico puede no entrar. Otra: la forma del mensaje manda sobre su contenido: las mismas sesenta lecturas ocupan 1070 bytes de memoria como arreglo y 3183 como objetos, tres veces más, y el texto pesa 6,5 veces más. Y la tercera: los datos históricos largos no se cargan enteros: 10 KB para analizar una respuesta ya exigen un microcontrolador mediano.

08 · Exploración interactiva

¿Cabe este mensaje?, de extremo a extremo

Este laboratorio ejecuta el mismo modelo del código anterior. Elegí el mensaje, la RAM que podés dedicar al documento y cómo llega el texto: desde un búfer en RAM (hay que sumar el texto al árbol) o leído directamente del flujo de la red. Las barras muestran la memoria necesaria para los cinco mensajes en escala logarítmica; son celestes si caben y rojas si no, y la línea naranja marca la RAM disponible. La estimación de 16 bytes por valor es aproximada.

Memoria para analizar un JSON

Tamaño del texto—
Valores en el árbol—
Memoria del documento—
RAM total necesaria—
Relación memoria / texto—
Resultado—

Elegí los parámetros y presioná «Calcular».

Recorrido guiado

  1. Con los valores iniciales (60 objetos, 8 KB, flujo), calculá. 1464 bytes de texto, 183 valores, 3183 bytes de memoria (2,2 veces): «Cabe con holgura».
  2. Cambiá a 60 lecturas en un arreglo: el texto baja a 226 bytes y la memoria a 1070. Mismos datos, tres veces menos memoria: la forma del mensaje decide.
  3. Probá 200 lecturas históricas con 8 KB: 10.441 bytes, «No cabe». Subí a 32 KB y entra, pero ocuparía casi un tercio de esos 32 KB.
  4. Con 60 lecturas como objetos, cambiá la RAM a 2 KB: «No cabe» (3183 bytes). Con el mensaje en arreglo, entra con 2 KB.
  5. Elegí Guardado antes en un búfer con 60 objetos y 8 KB: la RAM necesaria sube a 4647 bytes (el texto de 1464 más el árbol de 3183). Leer del flujo evita pagar el texto dos veces.
  6. Mirá la Configuración: 167 bytes de texto, 320 de memoria, la relación más chica (1,9 veces). Una respuesta chica y plana es la que mejor convive con un dispositivo.
Desafío: el costo de repetir los nombres

Sesenta lecturas en un arreglo de objetos {"m":…,"t":…} pesan 1464 bytes de texto: cada lectura repite las claves, las comillas, los dos puntos y las llaves, unos 24 bytes por lectura, de los cuales solo 2 (el valor de temperatura) son información nueva: la hora podría deducirse de un tiempo base. Pasando a un arreglo de números con tiempo base e intervalo, el texto pesa 226 bytes: 6,5 veces menos, y la memoria 1070 contra 3183 bytes: 3 veces menos. Con un POST cada 15 minutos (96 por día) y 10.000 equipos, el formato objeto genera 1464 × 96 × 30 × 10.000 ≈ 42 GB de cuerpos de petición por mes, contra 6,5 GB del arreglo. Y en el servidor no pasa nada: ambos son JSON válido. La elección de la forma es una decisión del diseño de la interfaz, y su efecto se multiplica por toda la flota.

09 · Método de trabajo

Lista de verificación para usar JSON

  1. Fijá el esquema: campos, tipos, unidades, escalas y versión, por escrito.
  2. Compactá los mensajes del dispositivo: nombres cortos, enteros escalados, lotes en arreglos con tiempo base.
  3. Pedí respuestas chicas y planas al servidor, con solo los campos que el dispositivo usa.
  4. Calculá la memoria del mayor documento que vas a analizar, con margen, y reservala; si no entra, filtrá o analizá por partes.
  5. Leé del flujo y usá filtros cuando la respuesta tenga más de lo necesario.
  6. Controlá la longitud al armar con snprintf, o usá una biblioteca que escape y mida.
  7. Valores por defecto para los campos opcionales, e ignorar los desconocidos.
  8. Validá tipos y rangos, aplicá la configuración completa o descartala entera, y registrá el motivo del rechazo.
  9. Limitá el tamaño máximo y la profundidad que aceptás.
  10. Probá con documentos malos: cortados, vacíos, con HTML, con campos de tipo equivocado, con números enormes y con textos muy largos.

Una herramienta útil en el escritorio: validá tus mensajes con un validador de JSON antes de enviarlos, y mantené en el repositorio un conjunto de documentos de prueba (válidos e inválidos) para ejecutarlos contra tu analizador.

10 · Comprensión

Confusiones frecuentes

«Si el texto ocupa 1 KB, necesito 1 KB para analizarlo»

El árbol en memoria suele ocupar entre 2 y 5 veces el texto, y si además guardás el texto en un búfer, se suman. Un mensaje de 1464 bytes pedía 3183 bytes de árbol y 4647 en total con búfer.

«En JSON puedo usar comillas simples o dejar una coma final, como en JavaScript»

JSON es mucho más estricto que JavaScript: comillas dobles obligatorias, nada de coma final, sin comentarios. Un analizador estricto rechaza el documento entero por cualquiera de esos desvíos.

«Un número decimal siempre se lee igual en cualquier lugar»

En JSON el separador decimal es siempre el punto, sin importar el idioma. Un firmware que formatea con la configuración regional (coma decimal) produce un JSON inválido. Los enteros escalados evitan el problema.

«Si el JSON es válido, el contenido es seguro»

La validez sintáctica no dice nada del contenido: puede traer tipos equivocados, valores fuera de rango u órdenes peligrosas. Cada campo se valida antes de usarlo.

«Armo el JSON concatenando cadenas, es más simple»

Funciona hasta que un texto trae una comilla, una barra o un salto de línea. Entonces el documento queda inválido, o peor, cambia de significado. Se usa una biblioteca, o se escapan los caracteres especiales.

«JSON es el mejor formato para todo»

Es excelente para interoperar y depurar, y costoso en bytes y memoria. Para canales muy limitados o mensajes muy frecuentes, CBOR u otros formatos binarios (tema 12) llevan la misma estructura en una fracción del tamaño.

11 · Práctica guiada

Predecí antes de comprobar

1. ¿Es JSON válido?

Indicá cuáles son válidos: (a) {"t":22.3}; (b) {'t':22.3}; (c) {"t":22,3}; (d) {"t":null,"q":"sin_respuesta"}; (e) {"t":NaN}; (f) {"v":[41,44,47,]}.

Ver solución

Válidos: (a) y (d). Inválidos: (b) comillas simples; (c) coma decimal; (e) NaN no existe en JSON; (f) coma final en el arreglo.

2. Memoria de un arreglo

Un mensaje {"v":[...]} trae 100 números de dos cifras. Con 16 bytes por valor, ¿cuánta memoria necesita el árbol, aproximadamente? ¿Y cuánto pesa el texto?

Ver solución

Valores: 1 raíz + 1 miembro (v) + 100 elementos = 102, es decir unos 1632 bytes, más 2 bytes de la clave: cerca de 1,6 KB. El texto: 100 números de 2 cifras con coma son 299 caracteres, más 8 de estructura: unos 307 bytes. La memoria es cinco veces el texto.

3. El mensaje truncado

Un snprintf con un búfer de 32 bytes arma un mensaje que necesitaría 49. ¿Qué queda en el búfer y qué hay que hacer?

Ver solución

Quedan los primeros 31 caracteres más el terminador: un JSON cortado y por tanto inválido, y snprintf no avisa salvo por su valor de retorno (49, mayor o igual que 32). Hay que comparar el valor devuelto con el tamaño del búfer y, si no entra, agrandar el búfer o dividir el mensaje; nunca enviar el texto truncado.

4. Escapes

El nombre de un dispositivo es cámara "norte". ¿Cómo debe aparecer en el JSON? ¿Qué pasa si se concatena sin escapar?

Ver solución

Con las comillas escapadas: "nombre":"cámara \"norte\"" (la «á» puede ir tal cual en UTF-8 o como á). Si se concatena sin escapar queda "nombre":"cámara "norte"": JSON inválido, y si el texto lo controla un tercero, puede cambiar la estructura del mensaje (inyección).

5. Valores por defecto

El servidor envía {"umbral_c":9.5} sin el campo periodo_s, que el firmware necesita. ¿Qué hace?

Ver solución

Usa el valor por defecto del campo ausente (por ejemplo, 900 s con el operador | de ArduinoJson) o conserva el que ya tenía; aplica el nuevo umbral después de validar su rango. Un campo ausente no es un error si el esquema lo declara opcional.

6. Elegir la forma

Vas a enviar 60 lecturas de temperatura por mensaje. Compará las dos formas con los números del tema: texto y memoria de «arreglo» y de «objetos».

Ver solución

Arreglo: 226 bytes de texto y 1070 de memoria. Objetos: 1464 bytes y 3183. El arreglo usa 6,5 veces menos tráfico y 3 veces menos memoria. Se elige el objeto solo si cada lectura debe llevar campos propios (por ejemplo, calidad individual) y el volumen lo permite.

12 · Síntesis

Ideas para recordar

  • JSON es el formato universal de las APIs: legible, autodescriptivo y flexible, a costa de repetir nombres y escribir los números como texto.
  • Tiene seis tipos (objeto, arreglo, cadena, número, booleano, nulo) y reglas estrictas: comillas dobles, sin coma final, sin comentarios, sin NaN y con punto decimal.
  • Un JSON truncado es inválido, lo que permite detectar mensajes cortados; también por eso hay que controlar la longitud al armarlo.
  • Se representan los números como enteros escalados, el tiempo en segundos Unix o en ISO 8601 UTC, y los valores ausentes con una regla clara entre omitir y null.
  • Con snprintf se arman mensajes chicos y fijos, siempre verificando la longitud devuelta; con una biblioteca se arman los variables y anidados, con los escapes correctos.
  • El árbol de un documento analizado ocupa de 2 a 5 veces el texto; hay que calcular la memoria del mayor documento antes de aceptarlo.
  • La forma del mensaje manda: un arreglo de números usa 3 veces menos memoria y 6,5 veces menos texto que un arreglo de objetos con los mismos datos.
  • Se reduce el consumo pidiendo respuestas chicas, leyendo del flujo, filtrando campos y analizando por partes.
  • Todo lo que llega se valida (tipo, rango, versión), se aplica completo o se descarta y se registra el motivo; nunca se arma JSON concatenando texto externo sin escapar.

¿Listo para el tema 22?

Antes de avanzar, escribí el mensaje de telemetría y la respuesta de configuración de tu dispositivo y calculá, con la regla de 16 bytes por valor, cuánta RAM necesita analizar el más grande. Escribí también tres documentos inválidos y comprobá que tu analizador los rechaza sin colgarse ni perder la configuración anterior.

En el próximo tema pasamos a las APIs REST para IoT: cómo se organizan los recursos, las rutas y los métodos de una interfaz pensada para dispositivos, y cómo se versiona y se autentica.