Introduction
The previous guide showed iterators. This guide will be about JSON conversion.
JSON is loved by many due to being human readable, simple to understand and able to represent complex data relationships.
Lite³, being a binary format, is not easily read directly. However to honor these beneficial properties, Lite³ implements JSON Conversion functions.
The goal of this example is to find the densest element in the periodic table, then export this element as a JSON document. For the periodic table, a JSON dataset will be used: examples/periodic_table.json
This example will go through a number of steps:
- Converting a JSON file to Lite³ format
- Iterating over the Lite³ data to find the densest element
- Converting from Lite³ to JSON in two different ways
Any guesses what the densest element is?
Example Code
This guide is based on an example inside the Lite³ repository found in examples/context_api/07-json-conversion.c with a dataset from examples/periodic_table.json:
40 perror(
"Failed to create lite3_ctx *ctx");
46 perror(
"Failed to decode JSON document");
53 perror(
"Failed to get data array");
58 perror(
"Failed to create iterator");
62 size_t el_densest_ofs = 0;
63 double el_densest_kg_per_m3 = 0.0;
71 perror(
"Failed to get element density");
74 if (kg_per_m3 > el_densest_kg_per_m3) {
75 el_densest_ofs = el_ofs;
76 el_densest_kg_per_m3 = kg_per_m3;
80 perror(
"Failed to get iter element");
83 if (el_densest_ofs == 0) {
84 perror(
"Failed to find densest element");
90 perror(
"Failed to get densest element name");
93 printf(
"densest element: %s\n\n",
LITE3_STR(ctx->buf, name));
95 printf(
"Convert Lite³ to JSON by returned heap pointer (prettified):\n");
99 perror(
"Failed encode JSON");
102 printf(
"%s\n\n", json);
105 printf(
"Convert Lite³ to JSON by writing to buffer (non-prettified):\n");
106 size_t json_buf_size = 1024;
107 char *json_buf = malloc(json_buf_size);
110 perror(
"Failed encode JSON");
113 size_t json_buf_len = (size_t)ret_i64;
114 printf(
"%s\n", json_buf);
115 printf(
"json bytes written: %zu\n", json_buf_len);
#define lite3_ctx_get_str(ctx, ofs, key, out)
Get string value by key.
#define lite3_ctx_get_f64(ctx, ofs, key, out)
Get floating point value by key.
#define lite3_ctx_get_arr(ctx, ofs, key, out)
Get array by key.
static int lite3_ctx_iter_create(lite3_ctx *ctx, size_t ofs, lite3_iter *out)
Create a lite3 iterator for the given object or array.
static int lite3_ctx_iter_next(lite3_ctx *ctx, lite3_iter *iter, lite3_str *out_key, size_t *out_val_ofs)
Get the next item from a lite3 iterator.
static int64_t lite3_ctx_json_enc_buf(lite3_ctx *ctx, size_t ofs, char *__restrict json_buf, size_t json_bufsz)
Convert Lite³ to JSON and write to output buffer.
static int lite3_ctx_json_dec_file(lite3_ctx *ctx, const char *__restrict path)
Convert JSON from file path to Lite³
static char * lite3_ctx_json_enc_pretty(lite3_ctx *ctx, size_t ofs, size_t *__restrict out_len)
Convert Lite³ to JSON prettified string.
static lite3_ctx * lite3_ctx_create(void)
Create context with minimum size.
void lite3_ctx_destroy(lite3_ctx *ctx)
Destroy context.
#define lite3_ctx_is_null(ctx, ofs, key)
Find value by key and test for null type.
#define LITE3_ITER_ITEM
Return value of lite3_iter_next(); iterator produced an item, continue;.
#define LITE3_STR(buf, val)
Generational pointer / safe access wrapper.
Lite³ Context API Header.
Struct containing iterator state.
Struct holding a reference to a string inside a Lite³ buffer.
Output:
densest element: Osmium
Convert Lite³ to JSON by returned heap pointer (prettified):
{
"atomic_radius": {
"calculated_pm": 185,
"van_der_waals_pm": null,
"covalent_pm": 128
},
"electron_config": "[Xe] 6s2 4f14 5d6",
"atomic_weight": 190.23,
"density_kg_per_m3": 22590.0,
"isotopes_count": 35,
"ionization_energy_kJ_per_mol": 840.0,
"series": "Transition",
"symbol": "Os",
"thermal_conductivity_W_per_mK": 88.0,
"oxidation_states": [
"-2",
"1",
"2",
"3",
"4c",
"5",
"6",
"7",
"8"
],
"discovery_year": 1803,
"valence": 8,
"electronegativity": 2.2,
"heat": {
"specific_J_per_kgK": 130.0,
"fusion_kJ_per_mol": 31.0,
"vaporization_kJ_per_mol": 630.0
},
"name": "Osmium",
"abundance_percent": {
"crust": 1.8e-7,
"ocean": null,
"universe": 3e-7,
"human_body": null
},
"electron_affinity_kJ_per_mol": 106.1,
"boiling_point_K": 5285.0,
"melting_point_K": 3306.0,
"atomic_number": 76
}
Convert Lite³ to JSON by writing to buffer (non-prettified):
{"atomic_radius":{"calculated_pm":185,"van_der_waals_pm":null,"covalent_pm":128},"electron_config":"[Xe] 6s2 4f14 5d6","atomic_weight":190.23,"density_kg_per_m3":22590.0,"isotopes_count":35,"ionization_energy_kJ_per_mol":840.0,"series":"Transition","symbol":"Os","thermal_conductivity_W_per_mK":88.0,"oxidation_states":["-2","1","2","3","4c","5","6","7","8"],"discovery_year":1803,"valence":8,"electronegativity":2.2,"heat":{"specific_J_per_kgK":130.0,"fusion_kJ_per_mol":31.0,"vaporization_kJ_per_mol":630.0},"name":"Osmium","abundance_percent":{"crust":1.8e-7,"ocean":null,"universe":3e-7,"human_body":null},"electron_affinity_kJ_per_mol":106.1,"boiling_point_K":5285.0,"melting_point_K":3306.0,"atomic_number":76}
json bytes written: 716
Explanation
We will walk through the example code step-by-step, explaining the use of Lite³ library functions.
Lite³ messages are just bytes, stored contiguously inside a buffer. If you want to allocate these messages inside your own custom allocators, you can using the Buffer API. However in this guide, we will be using the Context API so that memory is managed automatically.
Note that Lite³ is a binary format, but the examples print message data as JSON to stdout for better readability.
Converting JSON file to Lite³
As explained in the first guide, we use contexts to store Lite³ buffers (see: Context API):
if (!ctx) {
perror("Failed to create lite3_ctx *ctx");
return 1;
}
This context is now ready to store the dataset, but first we have to convert the JSON document to Lite³ using one of the JSON Conversion functions:
perror("Failed to decode JSON document");
return 1;
}
- Note
- Here JSON is being converted from a file path, but you can also convert from a regular string using
lite3_ctx_json_dec(). Converting from a file pointer is also possible through lite3_ctx_json_dec_fp().
Finding densest element
The next step is to find the densest element inside the periodic table. For this, we use an iterator to loop over all the elements, keeping track of the density for each.
The elements are contained in the "data" subfield, so after obtaining the offset for this field, we create the iterator:
size_t data_ofs;
perror("Failed to get data array");
return 1;
}
perror("Failed to create iterator");
return 1;
}
Now comes the looping part:
size_t el_ofs;
size_t el_densest_ofs = 0;
double el_densest_kg_per_m3 = 0.0;
int ret;
continue;
}
double kg_per_m3;
perror("Failed to get element density");
return 1;
}
if (kg_per_m3 > el_densest_kg_per_m3) {
el_densest_ofs = el_ofs;
el_densest_kg_per_m3 = kg_per_m3;
}
}
if (ret < 0) {
perror("Failed to get iter element");
return 1;
}
if (el_densest_ofs == 0) {
perror("Failed to find densest element");
return 1;
}
Iterators were explained in the previous guide. This loop will read the density of each element. Everytime a greater density is found, it is stored alongside the element offset in these variables:
size_t el_densest_ofs = 0;
double el_densest_kg_per_m3 = 0.0;
To some, this while condition may seem strange:
But if you pay attention to the parentheses, this is just a way of comparing the return value while still storing it for error handling:
if (ret < 0) {
perror("Failed to get iter element");
return 1;
}
If a message is corrupted, invalid or other wise damaged, it is important to know as early as possible. Otherwise, your iterator could unexpectedly stop halfway and it might 'appear to work' for long enough until your problems enter production.
Another remark is that not all elements in the dataset actually contain a density field ("density_kg_per_m3"). This is because some elements are highly unstable and have only ever existed for milliseconds or less. Therefore, their density has never been measured. Reading an null value would cause the program to exit, since lite3_ctx_get_f64() is expecting a double type. So we filter out these elements by checking for null, to skip the current loop iteration:
Printing the densest element
After all that work, the object of the densest element has been found, with the offset being stored inside el_densest_ofs.
Every element also contains a "name" field that is readable:
perror("Failed to get densest element name");
return 1;
}
printf(
"densest element: %s\n\n",
LITE3_STR(ctx->buf, name));
Output:
Did you know this?
Convert to JSON by returned heap pointer
Now that the densest element has been found, we want to create a separate JSON document containing only the information about this element.
Remember that the entire periodic table dataset currently is stored inside the context (ctx) as Lite³. If you want to convert Lite³ to JSON, you do not need to convert the entire message. Instead, it is possible to selectively convert only a sub-object or array. In this case, we only want to target the Osmium element. To do this, we target the el_densest_ofs offset at which it is stored:
printf("Convert Lite³ to JSON by returned heap pointer (prettified):\n");
size_t json_len;
if (!json) {
perror("Failed encode JSON");
return 1;
}
printf("%s\n\n", json);
free(json);
- Warning
- Do not forget to
free() heap pointers returned by lite3_ctx_json_enc() and lite3_ctx_json_enc_pretty().
Output:
Convert Lite³ to JSON by returned heap pointer (prettified):
{
"atomic_radius": {
"calculated_pm": 185,
"van_der_waals_pm": null,
"covalent_pm": 128
},
"electron_config": "[Xe] 6s2 4f14 5d6",
"atomic_weight": 190.23,
"density_kg_per_m3": 22590.0,
"isotopes_count": 35,
"ionization_energy_kJ_per_mol": 840.0,
"series": "Transition",
"symbol": "Os",
"thermal_conductivity_W_per_mK": 88.0,
"oxidation_states": [
"-2",
"1",
"2",
"3",
"4c",
"5",
"6",
"7",
"8"
],
"discovery_year": 1803,
"valence": 8,
"electronegativity": 2.2,
"heat": {
"specific_J_per_kgK": 130.0,
"fusion_kJ_per_mol": 31.0,
"vaporization_kJ_per_mol": 630.0
},
"name": "Osmium",
"abundance_percent": {
"crust": 1.8e-7,
"ocean": null,
"universe": 3e-7,
"human_body": null
},
"electron_affinity_kJ_per_mol": 106.1,
"boiling_point_K": 5285.0,
"melting_point_K": 3306.0,
"atomic_number": 76
}
The conversion was successful! Note that the 'prettified' JSON was obtained to make it easily readble. If you want minified JSON, you can use the lite3_ctx_json_enc() function.
Convert to JSON by writing to buffer
Instead of obtaining a heap pointer, sometimes you may want to write the converted JSON data directly to some buffer.
This is where functions lite3_ctx_json_enc_buf() and lite3_ctx_json_enc_pretty_buf() come in. The caller passes their target buffer as parameter, and the output JSON will be written directly up to the maximum specified size.
It is possible for the caller to pass a buffer that is too small. In that case, the operation will fail. There are two possible return values:
- >= 0 on success (number of bytes written)
- < 0 on error
Here we temporarily allocate a heap buffer, but you can pass any buffer you want:
printf("Convert Lite³ to JSON by writing to buffer (non-prettified):\n");
size_t json_buf_size = 1024;
char *json_buf = malloc(json_buf_size);
int64_t ret_i64;
perror("Failed encode JSON");
return 1;
}
size_t json_buf_len = (size_t)ret_i64;
printf("%s\n", json_buf);
printf("json bytes written: %zu\n", json_buf_len);
free(json_buf);
Output:
Convert Lite³ to JSON by writing to buffer (non-prettified):
{"atomic_radius":{"calculated_pm":185,"van_der_waals_pm":null,"covalent_pm":128},"electron_config":"[Xe] 6s2 4f14 5d6","atomic_weight":190.23,"density_kg_per_m3":22590.0,"isotopes_count":35,"ionization_energy_kJ_per_mol":840.0,"series":"Transition","symbol":"Os","thermal_conductivity_W_per_mK":88.0,"oxidation_states":["-2","1","2","3","4c","5","6","7","8"],"discovery_year":1803,"valence":8,"electronegativity":2.2,"heat":{"specific_J_per_kgK":130.0,"fusion_kJ_per_mol":31.0,"vaporization_kJ_per_mol":630.0},"name":"Osmium","abundance_percent":{"crust":1.8e-7,"ocean":null,"universe":3e-7,"human_body":null},"electron_affinity_kJ_per_mol":106.1,"boiling_point_K":5285.0,"melting_point_K":3306.0,"atomic_number":76}
json bytes written: 716
Now the non-prettified version was used, so the JSON is not very readable and takes up a large horizontal line.
Note that lite3_ctx_json_enc_buf() returns an int64_t value. This value must checked for error handling, then cast to (size_t) before storing json_buf_len.
Cleaning up
We are good citizens, so we clean up after ourselves:
This destroys the context, freeing all the internal buffers so that the memory is released. When you create contexts, don't forget to destroy them, or else you will be leaking memory.
Conclusion
This was the seventh guide showing JSON conversion.
This is the end of the How-to Guide series, showing all the essential functions for getting started with Lite³.
If you still have questions, consider the mailing list as explained in the README.