JsonX — is a declarative C layer over popular cJSON library with embedded-first memory model and structured mapping via JX_ELEMENT[].
- ✅ ThreadX-compatible memory allocation via
TX_BYTE_POOL - ✅ FreeRTOS support via
pvPortMalloc()/vPortFree() - ✅ Baremetal support using either malloc or static buffer
- ✅ Custom allocator hooks support (
malloc/freeor user-defined) - ✅ Clean
jx_namespace in the style of Azure RTOS (nx_,fx_, etc.) - ✅ Structured conversion between C data and JSON using
JX_ELEMENT - ✅ Support for complex nested objects and arrays
- ✅ Strict and non-strict parsing modes
- ✅ Optional logging with centralized
jx_log()support - ✅ Lightweight footprint (suitable for STM32 / Cortex-M targets)
| File | Description |
|---|---|
jx_api.h |
Public API headers (includes all public-facing declarations) |
jx_config.h |
Global configuration flags and compile-time feature toggles |
jx_debug.h |
Optional debug interface (logging abstraction) |
jx_internal.h |
Internal interfaces and sanity checks (JX_RETURN_IF_NULL, etc.) |
jx_static_allocator.h |
Interface of static buffer allocator for baremetal mode |
jx_types.h |
Core type definitions and macros (enums, constants, structures) |
jx_user.h |
Helper macros for declaring arrays, objects, and element sets |
jx_version.h |
Library version definition and constants |
example.c |
Demo usage with serialization of a Person_t structure |
jx_parser.c |
Core implementation: parsing, serialization, validation |
jx_static_allocator.c |
Implementation of the static allocator |
jx_version.c |
Version constant implementation |
README.md |
Project overview, usage examples, limitations, and build notes |
JsonX operates using a flat array of JX_ELEMENT structures. Each element represents a property or item in the JSON object, describing its type, memory location, and size.
Types supported:
JX_STRING,JX_NUMBER,JX_BOOLEANJX_OBJECT(nested),JX_ARRAY(list of elements)JX_NULL
JsonX uses declarative macros to map static C structures directly into JSON-compatible formats. This allows memory-efficient, low-footprint mapping of nested objects, arrays, and strings without dynamic type inspection or reflection.
Key benefits:
- Avoids runtime heap usage in static mode
- Enables fully static definitions for embedded firmware
- Clear mapping between struct layout and resulting JSON
The JX_ELEMENT[] flat array acts as an intermediary representation for both parsing and serialization.
JsonX provides a set of declarative macros to simplify the definition of JSON ↔ C mappings.
They are split into two categories:
These do not include a property name and are intended for use inside JX_PROPERTY_ARRAY(...), i.e., array elements:
| Macro | Description |
|---|---|
JX_STRING_PTR(p) |
Maps a string pointer (char *). You manage the memory. |
JX_STRING_REF(v) |
Maps a string buffer (char v[32]). Expands to &v. |
JX_BOOLEAN_VAL(v) |
Maps a boolean value (bool, uint8_t, etc.). |
JX_NUMBER_VAL(v) |
Maps a numeric value (int, float, double...). |
JX_ARRAY_VAL(arr) |
Maps a nested array (JX_ELEMENT[]). |
JX_OBJECT_VAL(obj) |
Maps a nested object (JX_ELEMENT[]). |
JX_OBJECT_EMPTY |
Represents an empty object with no members. |
🔸 Use
_PTRwhen you already have a pointer (char *str), and_REFwhen you have a fixed-size buffer (e.g.,char name[32]).
These include the property name ("key" in JSON) and are used when building JSON objects:
| Macro | Description |
|---|---|
JX_PROPERTY_STRING(key, val) |
"key": string mapping to char[] |
JX_PROPERTY_BOOLEAN(key, val) |
"key": true/false mapping |
JX_PROPERTY_NUMBER(key, val) |
"key": number mapping |
JX_PROPERTY_ARRAY(key, arr) |
"key": [...] array of nested elements |
JX_PROPERTY_OBJECT(key, obj) |
"key": {} nested object |
JX_PROPERTY_OBJECT_EMPTY(key) |
"key": {} with no members |
These macros help construct fixed-size arrays with multiple values or objects:
| Macro | Description |
|---|---|
JX_PROPERTY_STRING_ARRAY_N(name, ref) |
Declare an array of N strings (buffers) |
JX_PROPERTY_NUMBER_ARRAY_N(name, ref) |
Declare an array of N numbers |
JX_PROPERTY_OBJECT_ARRAY_2(name, obj0, obj1) |
Declare a 2-element object array |
🔸 All these macros expand into
JX_ELEMENT[]declarations.
🔸*_ARRAY_N(...)macros help initialize common use cases like[3][32]string buffers.
🔸*_OBJECT_ARRAY_N(...)are ideal for grouping multiple sub-objects as array items.
All composite helpers are defined in jx_user.h, which is designed to hold such convenience macros.
This file is fully under user control — modifying or extending it does not affect the core functionality of the library.
Each JSON node is represented by the JX_ELEMENT structure:
typedef struct json_element_s
{
char property[JX_PROPERTY_MAX_SIZE]; // JSON key (property name)
JX_ELEMENT_TYPE type; // Data type: STRING, NUMBER, ARRAY, etc.
uint8_t value_len; // Size of array/object (if applicable)
const void *value_p; // Pointer to value (primitive or buffer)
JX_ELEMENT_STATUS status; // Internal state: parsed, serialized, etc.
struct json_element_s *element; // Pointer to sub-elements (for object/array)
uint16_t element_size; // Number of sub-elements
} JX_ELEMENT;
⚠️ Notes:
- property: Stores the JSON key (i.e., field name like "name", "id", etc).
- 🔧
JX_PROPERTY_MAX_SIZEis defined injx_config.h.
You can adjust it to save RAM or support longer property names.
Example: set to32for compact mode,64for compatibility with long field names.- type: Enumerated value from JX_ELEMENT_TYPE (e.g., JX_STRING, JX_NUMBER, etc).
- value_p: Pointer to primitive value (e.g., string/number/boolean).
- element: For objects and arrays, points to the sub-elements.
- value_len: Runtime length of the array or number of valid sub-elements (during parsing).
- element_size: Static capacity (allocated size) of
element[]. Required for parsing bounds checks.- status: Used internally by the parser/serializer to track element state.
🔸 All
JX_NUMBERfields must be mapped to variables of typedouble.
This is a limitation inherited from the underlying cJSON,
which stores all numeric values internally asdouble.
🔸 See
JX_ELEMENT_TYPEandJX_ELEMENT_STATUSinjx_types.hfor available constants.
This structure is the backbone of JsonX, enabling both serialization and deserialization of complex JSON structures.
See example.c for a full demonstration of how to define nested JSON layouts using JX_PROPERTY_OBJECT_ARRAY_2() and other helpers.
Depending on system configuration, initialization differs. One of the following must be used depending on mode:
TX_BYTE_POOL byte_pool;
CHAR byte_pool_buffer[1024];
tx_byte_pool_create(&byte_pool, "JsonX byte pool", byte_pool_buffer, sizeof(byte_pool_buffer));
jx_init(&byte_pool);jx_init();jx_init();CHAR static_buffer[1024];
jx_init(static_buffer, sizeof(static_buffer));JX_HOOKS hooks = {
.alloc = malloc,
.free = free
};
jx_init(&hooks);struct {
char name[32];
double coords[2];
} test_struct;
// JSON element array
JX_ELEMENT user[] = {
{ .type = JX_STRING, .property = "name", .value_p = test_struct.name },
{ .type = JX_ARRAY, .property = "position", .element = (JX_ELEMENT[]) {
JX_NUMBER_VAL(test_struct.coords[0]),
JX_NUMBER_VAL(test_struct.coords[1])
}, .value_len = 2 }
};For a more advanced example of nested object arrays, see example.c
char *json_buf = jx_alloc_memory(256);
jx_struct_to_json(user, 2, json_buf, 256, JX_FORMATTED);const char *input = "{\"name\":\"Eve\",\"position\":[12, 34]}";
jx_json_to_struct(input, user, 2, JX_MODE_STRICT);test_printf("Name: %s, X: %d, Y: %d", test_struct.name, (int)test_struct.coords[0], (int)test_struct.coords[1]);jx_free_memory(json_buf);
jx_parser_deinit();If JX_DEBUG is set in jx_config.h, debug logging will be activated via the jx_log(...) macro:
#define jx_log(...) logger_printf(__VA_ARGS__)Additionally, a debug-only utility function is available:
void jx_dump_structure(JX_ELEMENT *desc, size_t count);This function prints a human-readable tree of the current JSON structure — useful for verifying layout, element types, and parsing results.
⚠️ Notes: jx_dump_structure() is defined as static inline in jx_debug.h and compiled only when JX_DEBUG defined. No external linking or implementation is needed. This makes it lightweight and easy to enable/disable for debug builds.
- In baremetal static mode (
JX_USE_BAREMETALwithoutJX_USE_HEAP_BAREMETAL),jx_alloc_memory()must not be used by the user during JSON parsing or serialization.
Doing so may lead to undefined behavior due to cJSON_Delete() being internally called by JsonX, which uses the same static buffer allocator. This breaks allocator consistency when user code simultaneously allocates memory using jx_alloc_memory(). Workaround: allocate required user buffers before calling JsonX APIs, or disable static mode usingJX_USE_HEAP_BAREMETAL.
#define JX_VERSION_MAJOR 1
#define JX_VERSION_MINOR 0
#define JX_VERSION_PATCH 0
const CHAR* jx_version(); // Returns static string: "JsonX v1.0.0 - (C) Mihail Zamurca, MIT Licensed"JsonX uses cJSON under MIT license and wraps it for embedded systems.
© Mihail Zamurca — all rights reserved.