Introducción al formato JSON
JSON (JavaScript Object Notation) es un formato de intercambio de datos ligero y autodescriptivo. Su sintaxis deriva de JavaScript, pero es independiente del lenguaje. Los datos se organizan en pares clave-valor, separados por comas, donde los objetos se delimitan con llaves {} y los arreglos con corchetes []. Los valores pueden ser números, cadenas, booleanos, arreglos, objetos o nulos.
Descripción de la biblioteca cJSON
cJSON es un analizador JSON implementado en C puro (ANSI C89), compuesto por un único archivo de cabecera y uno de implementación. Utiliza una estructura de lista enlazada para representar los datos. Para integrarlo en un proyecto, basta con añadir los archivos cJSON.c y cJSON.h al código fuente.
Obtención y compilación
El código fuente se encuentra disponible en el repositorio de GitHub. Para clonarlo y compilarlo, se siguen estos pasos:
git clone https://github.com/DaveGamble/cJSON.git
mkdir compilacion
cd compilacion
cmake ..
make
sudo make install
La instalación predeterminada coloca los archivos de cabecera en /usr/local/include/cjson y las bibliotecas en /usr/local/lib. Se pueden personalizar parámetros durante la configuración con CMake, como deshabilitar pruebas o habilitar utilidades adicionales.
Estructuras de datos y funciones principales
La biblioteca define una estrucutra central para representar nodos JSON. Cada nodo incluye punteros para navegación en listas, campos para valores de diferentes tipos y un nombre para identificarlo en objetos.
#define TIPO_INVALIDO 0
#define TIPO_BOOLEANO_FALSO 1
#define TIPO_BOOLEANO_VERDADERO 2
#define TIPO_NULO 4
#define TIPO_NUMERO 8
#define TIPO_CADENA 16
#define TIPO_ARREGLO 32
#define TIPO_OBJETO 64
typedef struct NodoJSON {
struct NodoJSON *anterior;
struct NodoJSON *siguiente;
struct NodoJSON *hijos;
int tipo;
char *cadena_valor;
int entero_valor;
double doble_valor;
char *nombre_clave;
} NodoJSON;
Funciones de parseo y liberación
La función analizar_json() convierte una cadena JSON en una estructura de nodos. La función eliminar_json() libera la memoria asignada.
NodoJSON* analizar_json(const char* texto_json);
void eliminar_json(NodoJSON* nodo_raiz);
Funciones de acceso y manipulación
Para recuperar datos específicos, se usan funciones como obtener_elemento_arreglo() y buscar_en_objeto(). También existen funciones de creación para cada tipo de dato y métodos para agregar nodos a arreglos u objetos.
int cantidad_elementos_arreglo(NodoJSON* arreglo);
NodoJSON* obtener_elemento_arreglo(NodoJSON* arreglo, int indice);
NodoJSON* buscar_en_objeto(NodoJSON* objeto, const char* clave);
NodoJSON* crear_nodo_cadena(const char* texto);
void agregar_a_objeto(NodoJSON* objeto, const char* clave, NodoJSON* nuevo_nodo);
Ejemplos de uso práctico
Parseo simple de una cadena JSON
#include <stdio.h>
#include <stdlib.h>
#include "cjson/cJSON.h"
int main() {
const char* datos_entrada = "{\"marca_temporal\":\"2023-10-05 14:30:00\", \"lectura\":42}";
NodoJSON* raiz = analizar_json(datos_entrada);
if (!raiz) {
printf("Error en el JSON: %s\n", cJSON_GetErrorPtr());
return EXIT_FAILURE;
}
NodoJSON* nodo_lectura = buscar_en_objeto(raiz, "lectura");
if (nodo_lectura && nodo_lectura->tipo == TIPO_NUMERO) {
printf("Valor numérico: %d\n", nodo_lectura->entero_valor);
}
NodoJSON* nodo_tiempo = buscar_en_objeto(raiz, "marca_temporal");
if (nodo_tiempo && nodo_tiempo->tipo == TIPO_CADENA) {
printf("Marca temporal: %s\n", nodo_tiempo->cadena_valor);
}
eliminar_json(raiz);
return EXIT_SUCCESS;
}
Construcción programática de un objeto JSON
#include <stdio.h>
#include <stdlib.h>
#include "cjson/cJSON.h"
int main() {
NodoJSON* raiz = crear_nodo_objeto();
NodoJSON* seccion = crear_nodo_objeto();
agregar_a_objeto(raiz, "resultado", crear_nodo_entero(0));
agregar_a_objeto(raiz, "accion", crear_nodo_cadena("REPRODUCIR"));
agregar_a_objeto(raiz, "titulo", crear_nodo_cadena("Canción favorita"));
agregar_a_objeto(raiz, "detalle", seccion);
agregar_a_objeto(seccion, "artista", crear_nodo_cadena("Artista Desconocido"));
agregar_a_objeto(seccion, "duracion", crear_nodo_entero(180));
char* texto_salida = cJSON_Print(raiz);
printf("JSON generado:\n%s\n", texto_salida);
free(texto_salida);
eliminar_json(raiz);
return EXIT_SUCCESS;
}
Recorrido recursivo de un JSON complejo
#include <stdio.h>
#include <stdlib.h>
#include "cjson/cJSON.h"
void mostrar_valores_internos(NodoJSON* nodo_actual) {
for (int i = 0; i < cantidad_elementos_arreglo(nodo_actual); i++) {
NodoJSON* elemento = obtener_elemento_arreglo(nodo_actual, i);
if (elemento->tipo == TIPO_OBJETO) {
mostrar_valores_internos(elemento);
} else {
printf("Clave: %s, Valor: %s\n", elemento->nombre_clave, cJSON_Print(elemento));
}
}
}
int main() {
const char* json_complejo = "{\"config\":{\"servidor\":\"localhost\"}, \"activo\":true, \"lista\":[1,2,3]}";
NodoJSON* raiz = analizar_json(json_complejo);
if (raiz) {
printf("Contenido completo:\n%s\n\n", cJSON_Print(raiz));
printf("Valores en el nivel más profundo:\n");
mostrar_valores_internos(raiz);
eliminar_json(raiz);
}
return EXIT_SUCCESS;
}