
Not so long ago, I had the need to load the application configuration with very limited resources. There was practically no access to any standard C functions. It was lucky that there were standard
malloc () / free () functions for working with memory.
The following situation has developed: the configuration is read from the file when the application is loaded on a system with limited resources. The configuration itself should be easily edited on a regular computer, to the extent that it will be necessary to quickly correct several values ​​directly on the object when shown to the customer.
')
From this we can conclude that it is necessary either:
- Write your own binary format editor.
- Use text format.
The first option does not suit us due to its limitations and problematic scalability. If we add several new fields to the structure of a binary file, we need to recompile (at best) and the file editor. Therefore, we will use the second option.
But I didn't want to fence my text format, especially since everything was invented before us. We are not the first to have a similar problem. Therefore, we take one of the more or less standard text formats for data storage.
There are some to choose from:
- XML
- Json
- Yaml
- INI
- Lua files (something more advanced than just a config)
- Files like `key = value`
I will say right away that XML disappeared immediately, because for our task it was too redundant. YAML is an interesting format, but it is still widespread in fairly narrow circles. Files like `key = value` are too simple. They are hard enough to store any complex values. Try to save the exact time with the date in this format, here or write your key (year = ..., month = ..., ..., second = ...) for each value, which is just a parsit, but it looks awful, or write your date parser to the value `date = 2005-08-09T18: 31: 42`. And if you need to preserve the entity that represents the employee, in which the fields, so, fifty? For the same reason, INI files are no longer available. Lua-configs are very interesting and are integrated quite simply, while adding a sea of ​​possibilities. But, again, I did not want to start a virtual machine for parsing a text file.
Therefore, in the end, the choice fell on JSON. Common enough. Enough scalable. Easy to edit and learn (in case you have to edit it for a near specialist).
So, we decided on the format, it remains to find the parser of this format absolutely without any dependencies ... so, stop. The only thing that was found with such a “filter” is the
jsmn parser. But the problem is that this is the parser. It does not form json objects, but only parses the string into tokens. And if the format of the loaded * .json file is known in advance, then you can simply get all the values. But stop, if the format of the downloadable file is known in advance, why not use the binary format? Therefore, imagine that the file format we do not know. Therefore, quickly write your wrapper over jsmn.
This is how the
Json For Embedded Systems (JFES) project was born.
Main features
- Compatible with C99
- Absolutely no dependencies.
- Easy to port.
- Can only be used as a parser.
The JFES library is based on two files: jfes.h and jfes.c, and the object around which everything is spinning is jfes_value_t.
struct jfes_value { jfes_value_type_t type; jfes_value_data_t data; };
In turn, the type field can take values:
typedef enum jfes_token_type { jfes_undefined = 0x00, jfes_null = 0x01, jfes_boolean = 0x02, jfes_integer = 0x03, jfes_double = 0x04, jfes_string = 0x05, jfes_array = 0x06, jfes_object = 0x07, } jfes_token_type_t; typedef jfes_token_type_t jfes_value_type_t;
And the data field is a union:
typedef union jfes_value_data { int bool_val; int int_val; double double_val; jfes_string_t string_val; jfes_array_t *array_val; jfes_object_t *object_val; } jfes_value_data_t;
Initialization
To initialize the library, you need to initialize the jfes_config_t object.
typedef struct jfes_config { jfes_malloc_t jfes_malloc; jfes_free_t jfes_free; } jfes_config_t;
Remember I said that JFES is completely free of dependencies? Indeed, she herself cannot even allocate memory, and you will not find a single #include in its source code. You can specify your memory allocation functions, if you want, for debugging purposes, to check the allocated memory, or because you only have your memory management functions.
jfes_config_t config; config.jfes_malloc = malloc; config.jfes_free = free;
Everything! After that, you can parse the json string as you like. Any value you can edit, and then save again to the line. A small example of working with JFES.
We will parse this JSON:
{ "first_name": "John", "last_name": "Black", "age": 35, "children": [ { "first_name": "Alice", "age": 5 }, { "first_name": "Robert", "age": 8 }, ], "wife": null, "simple" : [ 12, 15, 76, 34, 75, "Test", 23.1, 65.3, false, true, false ] }
Parse it using this code:
jfes_config_t config; config.jfes_malloc = malloc; config.jfes_free = free; jfes_value_t value; jfes_parse_to_value(&config, json_data, json_size, &value); /* children. , children JFES_NULL. */ jfes_value_t *children = jfes_get_child(&value, "children", 0); /* . : 1. . 2. . 3. . */ jfes_value_t *child = jfes_create_object_value(&config); /* "Paul" "first_name". */ jfes_set_object_property(&config, child, jfes_create_string_value(&config, "Paul", 0), "first_name", 0); /* "middle_name" "age". */ jfes_set_object_property(&config, child, jfes_create_string_value(&config, "Smith", 0), "middle_name", 0); jfes_set_object_property(&config, child, jfes_create_integer_value(&config, 1), "age", 0); /* 2. : , . , . */ jfes_set_object_property(&config, child, jfes_create_integer_value(&config, 2), "age", 0); /* child "middle_name" */ jfes_remove_object_property(&config, child, "middle_name", 0); /* `child` `children` 1. */ status = jfes_place_to_array_at(&config, children, child, 1); jfes_set_object_property(&config, &value, jfes_create_null_value(&config), "null_property", 0); /* . dump_size, , . -. 1, 0, , , .. (ugly). */ jfes_size_t dump_size = 1024; char *beauty_dump = malloc(dump_size * sizeof(char)); jfes_value_to_string(&value, &beauty_dump[0], &dump_size, 1); beauty_dump[dump_size] = '\0'; free(beauty_dump); /* . */ jfes_free_value(&config, &value);
Afterword
The library is not perfect, like any other code. It has errors, shortcomings, typos. But this is the power of open source. I will be glad to any pull-request, comments, tickets and wishes.
Link to my github:
JFESI hope that at least someone it will be useful and save a week of cycling. As far as my strength and capabilities are concerned, I will refine it. All nightly commits will be in the experimental branch, the master will be more or less stable and tested code. The exception will be only for the alpha-stage - as long as the product is alpha, there may be breakdowns of the master branch, but I will try not to break anything and still use experimental as much as possible.
Up to version 1.0.0 API may change, be careful. But the API changes will be written in the commit ʻa description.
Thank you so much
zserge for the
jsmn library.
Successes!