📜 ⬆️ ⬇️

Embed JSON in Embedded? Easy peasy

image

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:
  1. Write your own binary format editor.
  2. 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:


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



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.

/** JSON value structure. */ struct jfes_value { jfes_value_type_t type; /**< JSON value type. */ jfes_value_data_t data; /**< Value data. */ }; 

In turn, the type field can take values:

 /** JFES token types */ typedef enum jfes_token_type { jfes_undefined = 0x00, /**< Undefined token type. */ jfes_null = 0x01, /**< Null token type. */ jfes_boolean = 0x02, /**< Boolean token type. */ jfes_integer = 0x03, /**< Integer token type. */ jfes_double = 0x04, /**< Double token type. */ jfes_string = 0x05, /**< String token type. */ jfes_array = 0x06, /**< Array token type. */ jfes_object = 0x07, /**< Object token type. */ } jfes_token_type_t; /** Json value type is the same as token type. */ typedef jfes_token_type_t jfes_value_type_t; 

And the data field is a union:

 /** JFES value data union. */ typedef union jfes_value_data { int bool_val; /**< Boolean JSON value. */ int int_val; /**< Integer JSON value. */ double double_val; /**< Double JSON value. */ jfes_string_t string_val; /**< String JSON value. */ jfes_array_t *array_val; /**< Array JSON value. */ jfes_object_t *object_val; /**< Object JSON value. */ } jfes_value_data_t; 

Initialization


To initialize the library, you need to initialize the jfes_config_t object.

 /** JFES config structure. */ typedef struct jfes_config { jfes_malloc_t jfes_malloc; /**< Memory allocation function. */ jfes_free_t jfes_free; /**< Memory deallocation function. */ } 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: JFES

I 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!

Source: https://habr.com/ru/post/269383/


All Articles