JSON Serialization & Deserialization
Relevant source files
The following files were used as context for generating this wiki page:
The JSON Serialization and Deserialization module provides a reflection-based mechanism to convert C++ objects to JSON strings and vice-versa. It supports various data types including primitives, standard containers, enums, and nested reflective structures. This module is primary used for API communication, configuration loading, and logging within the HXLibs project.
Sources: include/HXLibs/reflection/json/JsonRead.hpp, include/HXLibs/reflection/json/JsonWrite.hpp, examples/ai/ai.cpp:115-125
Architecture Overview
The system consists of two primary namespaces under HX::reflection: toJson for serialization and fromJson for deserialization. The core logic relies on C++20 concepts and compile-time reflection to inspect object members and map them to JSON keys and values.
The following diagram illustrates the high-level data flow for both operations:
Serialization
The toJson function serializes C++ objects into a stream-like object (typically std::string) that supports the .append() method. It uses the internal::ToJson struct to handle different data types through template specialization and concepts.
Supported Data Types
The serializer handles types according to the following rules:
| Type Category | Logic |
|---|---|
| Primitives | Boolean values map to true/false. Numbers (integrals and floating points) use std::format. |
| Reflective Structs | Uses reflection::forEach to iterate over members, wrapping them in {}. |
| Containers | Sequences (vector, list, span) map to []. Key-value containers (map, unordered_map) map to {}. |
| Strings | Encodes characters with mandatory escaping for control characters and Unicode sequences. |
| Smart Pointers/Optionals | Maps to null if empty; otherwise, serializes the underlying value. |
| Enums | Converts enum values to their string representations using reflection::toEnumName. |
Sources: include/HXLibs/reflection/json/JsonWrite.hpp:264-418, tests/reflection/json/01_json_write.cpp:48-69
UTF-8 and Escape Handling
The serializer includes a dedicated UTF-8 decoder and an escape table to ensure valid JSON output. It specifically escapes control characters (e.g., \n, \t, \r) and provides Unicode escape sequences (\uXXXX) for non-ASCII characters.
// Example of internal escape handling logic
if (static_cast<unsigned>(*it) >= 0x80) {
writeUnicodeToString(it, s);
} else if (escape[static_cast<unsigned char>(*it)]) {
s.push_back('\\');
s.push_back(escape[static_cast<unsigned char>(*it)]);
}
Sources: include/HXLibs/reflection/json/JsonWrite.hpp:226-253
Deserialization
The fromJson function parses a std::string_view containing JSON data into a target C++ object. It performs single-pass parsing and populates the object members by matching JSON keys to reflective member names.
Parsing Flow
The deserializer uses a set of verify and skipWhiteSpace utilities to navigate the JSON structure.
Key Features and Constraints
- Reflective Mapping: For aggregate types, the parser uses a pre-computed hash map of member names to offsets (
reflection::makeNameToIdxVariantHashMap). - Error Handling: The system throws
std::runtime_errorfor unexpected characters, premature end of input, or insufficient array space. - Loose Parsing: An internal
LooseParsingflag can be set to toggle between strict whitespace checking and more permissive behavior. - Pointer Support: When deserializing into a
std::shared_ptrorstd::unique_ptr, the system automatically allocates a new instance of the element type if the JSON value is notnull.
Sources: include/HXLibs/reflection/json/JsonRead.hpp:34-37, include/HXLibs/reflection/json/JsonRead.hpp:240-270, include/HXLibs/reflection/json/JsonRead.hpp:463-485
Usage Example
Reflective structs must be compatible with the project's reflection macros or structure requirements. The following example demonstrates a typical round-trip:
struct Cat {
std::string name;
int num;
std::vector<Cat> sub;
};
// Serialization
Cat myCat = {"Mimi", 10, {}};
std::string json;
HX::reflection::toJson(myCat, json);
// Deserialization
Cat otherCat;
HX::reflection::fromJson(otherCat, json);
Sources: tests/reflection/json/01_json_write.cpp:10-46, benchmarks/http/servers/hxlibs_server.cpp:60-65
Performance Considerations
For high-performance scenarios, such as the hxlibs_server benchmark, the module supports std::string_view for input to avoid unnecessary copies during deserialization. In serialization, it uses std::format for numeric conversion and static escape tables for string processing.
Sources: benchmarks/http/servers/hxlibs_server.cpp:1-68, include/HXLibs/reflection/json/JsonWrite.hpp:220-225
Conclusion
The JSON Serialization & Deserialization module provides a type-safe, automated way to handle JSON data in C++. By leveraging C++20 reflection, it eliminates the need for manual boilerplate code while supporting complex, nested data structures and standard C++ containers.