Skip to content

json_struct is a single header only C++ library for parsing JSON directly to C++ structs and vice versa

License

Notifications You must be signed in to change notification settings

jorgen/json_struct

Repository files navigation

Structurize your JSON

CIClusterFuzzLite PR fuzzing

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:

structJsonObject{int One; std::string Two; double Three; JS_OBJ(One, Two, Three)};

or

structJsonObject{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_dbmax_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:

voidhandle_data(constchar *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<VehicleType>("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<Car>(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:

structJsonObject{int One; std::string Two; double Three; JS_OBJECT(JS_MEMBER(One) , JS_MEMBER(Two) , JS_MEMBER(Three))};

Custom JSON keys and aliases:

structJsonObject{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 name
  • JS_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:

namespaceJS{template<typename T> structTypeHandler}

Built-in type handlers:

  • std::string
  • double
  • float
  • uint8_t
  • int16_t
  • uint16_t
  • int32_t
  • uint32_t
  • int64_t
  • uint64_t
  • std::unique_ptr
  • bool
  • std::vector
  • [T]

Custom type handler example:

namespaceJS{template<> structTypeHandler<uint32_t>{public:staticinline Error to(uint32_t &to_type, ParseContext &context){char *pointer; unsignedlong value = strtoul(context.token.value.data, &pointer, 10); to_type = static_cast<unsignedint>(value); if (context.token.value.data == pointer) return Error::FailedToParseInt; return Error::NoError} staticvoidfrom(constuint32_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

Quality Assurance

Static Analysis:

Dynamic Analysis: All pull requests are tested with:

About

json_struct is a single header only C++ library for parsing JSON directly to C++ structs and vice versa

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors 12

Languages