JSON
Funciones
type* | json_read (...) |
type* | json_read_str (...) |
void | json_write (...) |
String* | json_write_str (...) |
void | json_destroy (...) |
void | json_destopt (...) |
JSON JavaScript Object Notation, es un formato de datos en modo texto que permite representar de manera sencilla tipos básicos, objetos y arrays. Aunque su uso se ha popularizado en el ámbito Web puede utilizarse también para otros fines, como por ejemplo, archivos de configuración o intercambio local. Su sintaxis es fácil de entender para los humanos y sencilla de procesar para las máquinas. En (Listado 1) reproducimos un pequeño fragmento de la respuesta JSON de un servicio Web:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
{ "code":0, "size":80, "data":[ { "id":0, "code":"i7-8700K", "description":"Intel BX80684I78700K 8th Gen Core i7-8700K Processor", type":0, "price":374.89, "image":"cpu_00.jpg", "image64":"\/9j\/4AAQSkZJRgABAQ...." }, { "id":1, "code":"G3930", "description":"Intel BX80677G3930 7th Gen Celeron Desktop Processors", "type":0, "price":51.95, "image":"cpu_01.jpg", "image64":"\/9j\/4AAQSkZJRgABAQAAAQABAAD..." }, ... ] } |
En su estructura podemos encontrar estos tipos de datos:
- Booleanos: Representados por las constantes
true
ofalse
. - Números: Utiliza la notación exponencial de C para valores en coma flotante:
23
,.76
,-0.54
o5.6e12
son ejemplos válidos de valores numéricos. JSON no distingue entre enteros, negativos o reales. - Cadenas: Cualquier texto entre comillas se considera una cadena. Admite cualquier carácter Unicode en UTF-8 o mediante la secuencia de escape
\uXXXX
para indicar el codepoint. - Arrays: Listas de valores delimitados por cochetes
[ ... ]
y separados por comas (Listado 2). - Objetos: Están delimitados por llaves y compuestos por varios campos separados por comas. Cada campo lo forman un identificador (cadena) seguido del carácter dos puntos y un valor que puede ser cualquier tipo simple, objeto u array (Listado 3).
- null: Indica la ausencia de valor.
- Binarios: JSON no soporta datos binarios por lo que objetos opacos (imágenes, por ejemplo) deben ser codificados en texto y transmitidos como un valor de tipo cadena. El formato más extendido y soportado globalmente es el Base64 donde cada carácter representa 6 bits de información.
1 2 3 |
[ "Red", "Green", "Blue", "Yellow" ] |
1 2 3 4 5 6 7 |
{ "field1" : true, "field2" : 24.67, "field3" : "Hello Pibe", "field4" : [1, 2, 4, 8.4], "field5" : { "x" : 34.32, "y" : -6.19 } } |
El parser JSON de NAppGUI transforma automáticamente los objetos Image a Base64 y viceversa, lo que permite incrustar imágenes como campos de datos.
1. Análisis de JSON y conversión a datos en C
NAppGUI permite el análisis automático de información Json.
- Utiliza json_read para traducir un Json a C.
- Utiliza json_destroy para destruir un objeto previamente leído.
Mostraremos a continuación diferentes ejemplos con tipos básicos, arrays y objetos. En Lectura/Escritura de Json tienes el código completo. El primer paso es crear un Stream con el contenido del Json (Listado 4):
1 2 3 4 5 6 7 8 9 |
/* Json data from web service */ Stream *stm = http_dget("http://serv.nappgui.com/dproducts.php", NULL, NULL); /* Json data from disk file */ Stream *stm = hfile_stream("/home/fran/appdata/products.json", NULL); /* Json data from memory block */ const char_t *data = "[12, 34, 67, 45]"; Stream *stm = stm_from_block((const byte_t*)data, str_len_c(data)); |
El Stream deberá destruirse con stm_close al finalizar el análisis.
Después utilizaremos json_read
indicando el tipo de dato esperado del Json.
1 2 3 4 5 |
json: true bool_t *json = json_read(stm, NULL, bool_t); bstd_printf("Json boolean: %d\n", *json); json_destroy(&json, bool_t); |
1 2 3 4 5 |
json: 6654 uint16_t *json = json_read(stm, NULL, uint16_t); bstd_printf("Json unsigned int: %d\n", *json); json_destroy(&json, uint16_t); |
1 2 3 4 5 |
json: "Hello World" String *json = json_read(stm, NULL, String); bstd_printf("Json string: %s\n", tc(json)); json_destroy(&json, String); |
1 2 3 4 5 6 7 |
json: "/9j/4QB4RXhpZgAASUkqAAgAAA..." Image *json = json_read(stm, NULL, Image); uint32_t width = image_width(json); uint32_t height = image_height(json); bstd_printf("Json image: width: %d height: %d\n", width, height); json_destroy(&json, Image); |
1 2 3 4 5 6 7 8 9 |
json: [ -321, 12, -8943, 228, -220, 347 ] ArrSt(int16_t) *json = json_read(stm, NULL, ArrSt(int16_t)); bstd_printf("Json array: "); arrst_foreach(id, json, int16_t) bstd_printf("%d ", *id); arrst_end() bstd_printf("\n"); json_destroy(&json, ArrSt(int16_t)); |
1 2 3 4 5 6 7 8 9 |
json: [ "Red", "Green", "Blue", "Yellow", "Orange" ] ArrPt(String) *json = json_read(stm, NULL, ArrPt(String)); bstd_printf("Json array: "); arrpt_foreach(str, json, String) bstd_printf("%s ", tc(str)); arrpt_end() bstd_printf("\n"); json_destroy(&json, ArrPt(String)); |
Para el análisis de objetos es necesario que registremos con Data binding la estructura de los mismos, de tal forma que coincidan los tipos y nombres de los campos del objeto Json con los struct
de C. Dado este Json:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "size" : 3, "data" : [ { "description" : "Intel i7-7700K", "price" : 329.99 }, { "description" : "Ryzen-5-1600", "price" : 194.99 }, { "description" : "GTX-1060", "price" : 449.99 } ] } |
Definimos estos struct
y los registramos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
typedef struct _product_t Product; typedef struct _products_t Products; struct _product_t { String *description; real32_t price; }; struct _products_t { uint32_t size; ArrSt(Product) *data; }; DeclSt(Product); dbind(Product, String*, description); dbind(Product, real32_t, price); dbind(Products, uint32_t, size); dbind(Products, ArrSt(Product)*, data); |
De esta forma ya podemos llamar a json_read
:
1 2 3 4 5 6 7 |
Products *json = json_read(stm, NULL, Products); bstd_printf("Json object: Size %d\n", json->size); arrst_foreach(elem, json->data, Product) bstd_printf("Product: %s Price %.2f\n", tc(elem->description), elem->price); arrst_end() bstd_printf("\n"); json_destroy(&json, Products); |
json_read() ignora (salta) aquellos campos de objetos Json que no estén registrados con dbind. En ningún caso generarán cachés ni memoria dinámica.
2. Mapeo entre Json y C
json_read reconoce los tipos básicos de NAppGUI, así como String, Image, ArrSt y ArrPt. No funcionará con otro tipo de datos como int
o float
. Tampoco reconocerá las estructuras STL vector
, map
, etc. En (Tabla 1) mostramos la equivalencia entre los campos de un Json y los tipos de C que necesitamos para mapearlo correctamente.
Json | C | |
boolean | bool_t | true, false |
number | int8_t, int16_t, int32_t, int64_t | -6785, 45, 0 |
number | uint8_t, uint16_t, uint32_t, uint64_t | 1, 36734, 255, 0, 14 |
number | real32_t, real64_t | 67.554, -3.456, 1.5e7 |
string | String | "Intel Celeron", "Red" |
string | Image | "/9j/4QB4RXhpZgAASUkqAAgAAA..." |
array | ArrSt(uint16_t) | [ 12, 111, 865 ] |
array | ArrSt(real32_t) | [ -34.89, 0.0001, 567.45, 1e6 ] |
array | ArrPt(String) | [ "red", "green", "blue" ] |
array | ArrPt(Image) | [ "/9j/4QB4RXh...", "/9j/4QB4RXh...", ... ] |
object | struct Product (Data binding) |
{ "description" : "i7-8700K", " "price" : 234.54 } |
array | ArrSt(Product) | [ { "description" : "i7-8700K", " "price" : 234.54 }, ... ] |
array | ArrPt(Product) | [ { "description" : "i7-8700K", " "price" : 234.54 }, ... ] |
3. Convertir desde C a JSON
- Utiliza json_write para escribir datos/objetos desde C a Json.
Basándonos de nuevo en (Tabla 1), vamos a realizar el proceso inverso y generar datos Json a partir de tipos y objetos en C. Lo primero es crear un stream de escritura para albergar el resultado (Listado 14):
1 2 3 4 5 |
/* Write stream in memory */ Stream *stm = stm_memory(2048); /* Write stream in disk */ Stream *stm = stm_to_file("/home/fran/appdata/products.json", NULL); |
El Stream deberá destruirse con stm_close cuando ya no sea necesario.
Después utilizaremos json_write
indicando el tipo de dato esperado del Json.
1 2 3 4 5 |
bool_t data_bool = TRUE; stm_writef(stm, "Json from bool_t: "); json_write(stm, &data_bool, NULL, bool_t); // Json from bool_t: true |
1 2 3 4 5 |
uint16_t data_uint = 6654; stm_writef(stm, "Json from uint16_t: "); json_write(stm, &data_uint, NULL, uint16_t); // Json from uint16_t: 6654 |
1 2 3 4 5 6 |
String *data_str = str_c("Hello World"); stm_writef(stm, "Json from String: "); json_write(stm, data_str, NULL, String); str_destroy(&data_str); // Json from String: "Hello World" |
1 2 3 4 5 6 |
Image *data_image = load_image(); stm_writef(stm, "Json from Image: "); json_write(stm, data_image, NULL, Image); image_destroy(&data_image); // Json from Image: "iVBORw0KGgoAAAANSUhEUgAAAAIA..." |
1 2 3 4 5 6 7 8 9 10 11 12 |
ArrSt(int16_t) *array = arrst_create(int16_t); arrst_append(array, -321, int16_t); arrst_append(array, 12, int16_t); arrst_append(array, -8943, int16_t); arrst_append(array, 228, int16_t); arrst_append(array, -220, int16_t); arrst_append(array, 347, int16_t); stm_writef(stm, "Json from int array: "); json_write(stm, array, NULL, ArrSt(int16_t)); arrst_destroy(&array, NULL, int16_t); // Json from int array: [ -321, 12, -8943, 228, -220, 347 ] |
1 2 3 4 5 6 7 8 9 10 11 |
ArrPt(String) *array = arrpt_create(String); arrpt_append(array, str_c("Red"), String); arrpt_append(array, str_c("Green"), String); arrpt_append(array, str_c("Blue"), String); arrpt_append(array, str_c("Yellow"), String); arrpt_append(array, str_c("Orange"), String); stm_writef(stm, "Json from string array: "); json_write(stm, array, NULL, ArrPt(String)); arrpt_destroy(&array, str_destroy, String); // Json from string array: [ "Red", "Green", "Blue", "Yellow", "Orange" ] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
Products *products = heap_new(Products); products->size = 3; products->data = arrst_create(Product); { Product *product = arrst_new(products->data, Product); product->description = str_c("Intel i7-7700K"); product->price = 329.99f; } { Product *product = arrst_new(products->data, Product); product->description = str_c("Ryzen-5-1600"); product->price = 194.99f; } { Product *product = arrst_new(products->data, Product); product->description = str_c("GTX-1060"); product->price = 449.99f; } stm_writef(stm, "Json from object: "); json_write(stm, products, NULL, Products); dbind_destroy(&products, Products); // Json from object: {"size" : 3, "data" : [ {"description" : "Intel i7-7700K", "price" : 329.989990 }, {"description" : "Ryzen-5-1600", "price" : 194.990005 }, {"description" : "GTX-1060", "price" : 449.989990 } ] } |
json_read ()
Procesa un script JSON. Transformará texto JSON en un tipo u objeto en C.
type* json_read(Stream *stm, const JsonOpts *opts, type);
stm | Entrada de datos en formato JSON. |
opts | Opciones. |
type | Tipo de datos. |
Retorna
Objeto resultado.
Observaciones
Ver Análisis de JSON y conversión a datos en C.
json_read_str ()
Igual que json_read, pero aceptando el código JSON a partir de una cadena de caracteres.
type* json_read_str(const char_t *str, const JsonOpts *opts, type);
str | Cadena de texto en formato JSON, acabada en carácter nulo |
opts | Opciones. |
type | Tipo de datos. |
Retorna
Objeto resultado.
Observaciones
Ver Análisis de JSON y conversión a datos en C.
json_write ()
Escribe datos en C a formato JSON.
void json_write(Stream *stm, type *data, const JsonOpts *opts, type);
stm | Salida de datos en formato JSON. |
data | Objeto. |
opts | Opciones. |
type | Tipo de datos. |
Observaciones
json_write_str ()
Igual que json_write, pero devolviendo el resultado en un String.
String* json_write_str(type *data, const JsonOpts *opts, type);
data | Objeto. |
opts | Opciones. |
type | Tipo de datos. |
Retorna
Cadena de texto en formato JSON.
Observaciones
json_destroy ()
Destruye un objeto JSON, previamente creado con json_read.
void json_destroy(type **data, type);
data | Objeto. |
type | Tipo de datos. |
json_destopt ()
Destruye un objeto JSON, previamente creado con json_read, si este no es NULL
.
void json_destopt(type **data, type);
data | Objeto. |
type | Tipo de datos. |