GitHub - jorgen/json_struct: json_struct is a single header only C++ library for parsing JSON directly to C++ structs and vice versa (original) (raw)
Structurize your JSON
json_struct is a single-header C++ library that parses JSON to structs/classes and serializes structs/classes back to JSON. With support for relaxed parsing rules, it's also excellent for configuration files and human-editable data formats.
Getting Started: Simply copy json_struct.h from the include folder into your project's include path.
Requirements: C++11 or newer. Tested on GCC, Clang, and Visual Studio 2015+.
Quick Start
json_struct automatically maps JSON to C++ structs by adding simple metadata declarations.
{ "One" : 1, "Two" : "two", "Three" : 3.333 }
can be parsed into a structure defined like this:
struct JsonObject { int One; std::string Two; double Three;
JS_OBJ(One, Two, Three);};
or
struct JsonObject { int One; std::string Two; double Three; }; JS_OBJ_EXT(JsonObject, One, Two, Three);
Parse JSON to struct:
JS::ParseContext context(json_data); JsonObject obj; context.parseTo(obj);
Serialize struct to JSON:
std::string pretty_json = JS::serializeStruct(obj); // or std::string compact_json = JS::serializeStruct(obj, JS::SerializerOptions(JS::SerializerOptions::Compact));
Relaxed Parsing for Config Files
json_struct supports relaxed JSON parsing rules, making it ideal for configuration files and human-editable data. Enable optional features for a more forgiving syntax:
- Comments using
//syntax - Unquoted property names and string values (supports
A-Z,a-z,0-9,_,-,.,/) - Newlines instead of commas as delimiters
- Trailing commas in objects and arrays
Example configuration file:
{ // Server configuration host: localhost port: 8080
database: { name: myapp_db max_connections: 100, // Trailing comma is OK }
log_file: /var/log/app.log }
Enable relaxed parsing:
JS::ParseContext context(config_data); context.tokenizer.allowComments(true); context.tokenizer.allowAsciiType(true); context.tokenizer.allowNewLineAsTokenDelimiter(true); context.tokenizer.allowSuperfluousComma(true); context.parseTo(config_obj);
Dynamic JSON with Maps
When the JSON structure depends on runtime values, you can parse into a JS::Map first, inspect the data, then dispatch to the appropriate type. For example, consider JSON describing different vehicle types:
{ "type" : "car", "wheels" : 4, "electric" : true, ... }
or it could look like this:
{ "type" : "sailboat", "sail_area_m2" : 106.5, "swimming_platform": true, ... }
Parse into a map, query for the type field, then convert to the specific struct:
void handle_data(const char *data, size_t size) { JS::Map map; JS::ParseContext parseContext(data, size, map); if (parseContext.error != JS::Error::NoError) { fprintf(stderr, "Failed to parse Json:\n%s\n", parseContext.makeErrorString().c_str()); return; } VehicleType vehicleType = map.castTo("type", parseContext); if (parseContext.error != JS::Error::NoError) { fprintf(stderr, "Failed to extract type:\n%s\n", parseContext.makeErrorString().c_str()); return; } switch (vehicleType) { case VehicleType::car: { Car car = map.castTo(parseContext); if (parseContext.error != JS::Error::NoError) { //error handling } handle_car(car); break; } case VehicleType::sailboat: Sailboat sailboat; map.castToType(parseContext, sailboat); if (parseContext.error != JS::Error::NoError) { //error handling } handle_sailboat(sailboat); break; } }
The JS::Map allows querying and extracting individual fields before converting the entire object. Two casting styles are available:
Car car = map.castTo<Car>(parseContext);
// or
Sailboat sailboat;
map.castToType(parseContext, sailboat);Advanced Macro Usage
The JS_OBJ macro adds a static metadata object to your struct without affecting its size or semantics. For more control, use the verbose JS_OBJECT macro with explicit member declarations:
struct JsonObject { int One; std::string Two; double Three;
JS_OBJECT(JS_MEMBER(One)
, JS_MEMBER(Two)
, JS_MEMBER(Three));};
Custom JSON keys and aliases:
struct JsonObject { int One; std::string Two; double Three;
JS_OBJECT(JS_MEMBER(One)
, JS_MEMBER_WITH_NAME(Two, "TheTwo")
, JS_MEMBER_ALIASES(Three, "TheThree", "the_three"));};
JS_MEMBER_WITH_NAME- Uses only the supplied name, ignoring the member nameJS_MEMBER_ALIASES- Checks aliases after the primary member name
Note: Don't mix JS_MEMBER macros with JS_OBJ as it will double-apply the macro.
Custom Type Handlers
For types that don't fit the standard object model (e.g., custom serialization logic), implement a TypeHandler specialization. The JS::ParseContext looks for template specializations:
namespace JS { template struct TypeHandler; }
Built-in type handlers:
std::stringdoublefloatuint8_tint16_tuint16_tint32_tuint32_tint64_tuint64_tstd::unique_ptrboolstd::vector[T]
Custom type handler example:
namespace JS { template<> struct TypeHandler { public: static inline Error to(uint32_t &to_type, ParseContext &context) { char *pointer; unsigned long value = strtoul(context.token.value.data, &pointer, 10); to_type = static_cast(value); if (context.token.value.data == pointer) return Error::FailedToParseInt; return Error::NoError; }
static void from(const uint32_t &from_type, Token &token, Serializer &serializer)
{
std::string buf = std::to_string(from_type);
token.value_type = Type::Number;
token.value.data = buf.data();
token.value.size = buf.size();
serializer.write(token);
}}; }
This gives complete control over serialization/deserialization and can represent any JSON structure (objects, arrays, primitives).
Examples and Tests
- Examples - See practical usage patterns
- Unit Tests - Comprehensive test coverage
Quality Assurance
Static Analysis:
- PVS-Studio - C, C++, C#, and Java static analyzer
**Dynamic Analysis:**All pull requests are tested with: