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

Overview

Structurize your JSON

Build Status Build status

json_struct is a single header only library that parses JSON to C++ structs/classes and serializing structs/classes to JSON.

It is intended to be used by copying the json_struct.h file from the include folder into the include path for the project. It is only the json_struct.h file that is needed to serialize and deserialize json from structures.

It is dependent on some C++11 features and is tested on newer versions of gcc and clang. It is also tested on VS 2015 and newer.

Structs

json_struct can parse JSON and automatically populate structures with content by adding some metadata to the C++ structs.

{
    "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);

Populating the struct would look like this:

JS::ParseContext context(json_data);
JsonObject obj;
context.parseTo(obj);

Serializing the struct to json could be done like this:

std::string pretty_json = JS::serializeStruct(obj);
// or
std::string compact_json = JS::serializeStruct(obj, JS::SerializerOptions(JS::SerializerOptions::Compact));

Maps

Sometimes the structure of the JSON is dependent on some value in the JSON. Say there is some input JSON that describes a transportation vehicle. It can looks something like this

{
  "type" : "car",
  "wheels" : 4,
  "electric" : true,
...
}

or it could look like this:

{
  "type" : "sailboat",
  "sail_area_m2" : 106.5,
  "swimming_platform": true,
...
}

This doesn't fit well with the static nature of json_struct. However, it is possible to parse the JSON into a map structure, query some child members for data and then dispatch the conversion into an appropriate type.

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<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;
  }
}

Here we parse the JSON into a JS::Map. This map lets us query if the map contains a member and it enables us to convert that member into a type. In the example we convert it to the VehicleType:

  VehicleType vehicleType = map.castTo<VehicleType>("type", parseContext);

Then we can inspect the value of the type child and cast the entire object into the desired type. The cast functions have two signatures: castTo and castToValue. castTo returnes the value type, however, if the object has already been allocated and just needs to populated then castToType. castToType has the added bonus of not needing to specify the template type since this is deduced by the parameter. Casting the whole object has the same semantics it only misses the "name" parameter:

    Car car = map.castTo<Car>(parseContext);
    // or
    Sailboat sailboat;
    map.castToType(parseContext, sailboat);

Demystifying the Macros

The JS_OBJ macro adds a static meta object to the struct/class. It does not affect the semantics or size of the struct/class. It automatically applies another macro to the member arguments, getting the name and member pointer. There are other macros that are more verbose, but that gives more flexibility.

The JS_OBJECT macro requires that all the members are passed in a JS_MEMBER macro. An example of these macros being applied would look like this:

struct JsonObject
{
    int One;
    std::string Two;
    double Three;

    JS_OBJECT(JS_MEMBER(One)
            , JS_MEMBER(Two)
            , JS_MEMBER(Three));
};

This doesn't add any value, but say you want to have a different JSON key for a member than the name, or maybe you want to add some alias keys, then this could be done like this:

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"));
};

The difference between the _WITH_NAME and _ALIASES macros is that the _WITH_NAME macro ignores the member name and uses the supplied name, while the aliases adds a list of names to be checked after the name of the member is checked.

Its not possible to use the JS_MEMBER macros with the JS_OBJ macro, since then it tries to apply the JS_MEMBER macro twice on member.

TypeHandler

For objects the meta information is generated with JS_OBJ and JS_OBJECT macros, but there might be types that doesn't fit the meta information interface, ie they are not JSON Object types. Then its possible to define how these specific classes are serialized and deserialized with the TypeHandler interface.

When the JS::ParseContext tries to parse a type it will look for a template specialisation of the type:

namespace JS {
    template<typename T>
    struct TypeHandler;
}

There are a number of predefined template specialisations for types such as:

  • 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]

Its not often necessary, but when you need to define your own serialization and deserialization it's done like this:

namespace JS {
template<>
struct TypeHandler<uint32_t>
{
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<unsigned int>(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 you complete control of serialization deserialization of a type and it can unfold to a JSON object or array if needed.

For more information checkout the examples at: https://github.com/jorgen/json_struct/tree/master/examples

and have a look at the more complete unit tests at: https://github.com/jorgen/json_struct/tree/master/tests

Comments
  • How to Install this?

    How to Install this?

    Hello, I am trying to use this in a C++ dll project I have. I am just not sure how to install this?

    seems like I just copy the json_tools.h into my project, but this is causing the following error on line 4088: C2589 '(': illegal token on right side of '::'

    Which makes no since considering the code does not have that at all. What am I missing?

    Thanks, Bob

    opened by Bobisback 12
  • It's not a bug. How to count elements of nested array?

    It's not a bug. How to count elements of nested array?

    Hi, on this array "vec" : [ { "key" : 4, "value": 1.0 }, { "key" : 5, "value": 2.0 }, { "key" : 6, "value": 3.0 } ] how to know the number of elements ? On this sample, 3. Thank's

    opened by clabnet 8
  • JS_OBJ_EXT triggers

    JS_OBJ_EXT triggers "ISO C++11 requires at least one argument for the "..." in a variadic macro" warning

    I get this warning when compiling with GCC 10.3.0, which is a bit of an issue for me since my repo is configured to treat warnings as errors, and these warnings are difficult to turn off because they don't seem to have a -Wno-... switch. As the related code is generated inline from the macros, I'd have to switch either the pedantic or warnings-as-errors settings off for my whole repo. Is there a way to resolve this properly?

    opened by noodlecollie 7
  • does parseTo check struct members ?

    does parseTo check struct members ?

    for example

    struct a{
      int a;
      JS_OBJ(a);
    }
    
    struct b{
      int b;
      JS_OBJ(b);
    }
    
    a s_a;
    s_a.a = 19;
    b s_b;
    s_b.b = 0;
    
    auto json = JS::serializeStruct(s_a);
    JS::ParseContext context(json);
    auto error = context.parseTo(s_b);
    

    the code give me no error, but s_b.b is still zero.

    opened by yanzixiang 6
  • Reading JSON which contains certain extraneous values breaks parsing

    Reading JSON which contains certain extraneous values breaks parsing

    I discovered this issue when I was trying to parse a small subset of data from a large JSON message. If I created a set of nested structs with members corresponding to only the data I needed, the parse would fail with an ExpectedObjectStart error at an arbitrary location. However, if I added in all of the values present in the message, it would parse correctly.

    I spent some time investigating this and have managed to come up with a minimal reproducible example. In the example the parsing doesn't explicitly fail, but rather the parser seems to get out of sync and places incorrect values into structs. I believe that the parser getting out of sync was the cause of my original issue.

    The fundamental issue seems to be surrounding nested objects. Take the following JSON:

    {
        "object1":
        {
            "value": 1,
            "nested_object":
            {
                "nested_1":
                {
                        "some_value": "foo"
                },
                "nested_2":
                {
                        "some_value": "bar"
                }
            }
        },
        "object2":
        {
                "value": 2
        }
    }
    

    If this message is parsed using structs, where every key-value pair is catered for, then parsing succeeds as expected. However, if nested_object is omitted from the struct definition, this triggers the parsing issue. Note that the issue does not occur unless the nested_object has at least two other nested JSON objects within it.

    The attached C++ project encapsulates the minimal reproducible example. In the case where the nested object fails to parse correctly. the struct representing object2 contains a value of 0 when it should contain a value of 2.

    json_struct MRE.zip

    opened by noodlecollie 6
  • GCC11 compatibility

    GCC11 compatibility

    Hello.

    I'm now trying to migrate to GCC11 and see those errors:

    /lt2http/src/external/json_struct/include/json_struct.h: In function ‘T JS::Internal::ft::iabs(typename std::enable_if<std::is_signed<_Tp>::value, T>::type)’:
    /lt2http/src/external/json_struct/include/json_struct.h:5095:17: error: ‘numeric_limits’ is not a member of ‘std’
     5095 |   if (a == std::numeric_limits<T>::min())
          |                 ^~~~~~~~~~~~~~
    /lt2http/src/external/json_struct/include/json_struct.h:5095:33: error: expected primary-expression before ‘>’ token
     5095 |   if (a == std::numeric_limits<T>::min())
          |                                 ^
    /lt2http/src/external/json_struct/include/json_struct.h:5095:36: error: ‘::min’ has not been declared
     5095 |   if (a == std::numeric_limits<T>::min())
          |                                    ^~~
    /lt2http/src/external/json_struct/include/json_struct.h:5095:36: note: suggested alternatives:
    In file included from /usr/x86_64-linux-musl-cross/x86_64-linux-musl/include/c++/11.2.1/algorithm:62,
                     from /lt2http/src/external/json_struct/include/json_struct.h:115,
                     from /lt2http/src/bittorrent/meta_info.h:6,
                     from /lt2http/src/bittorrent/meta_info.cpp:1:
    /usr/x86_64-linux-musl-cross/x86_64-linux-musl/include/c++/11.2.1/bits/stl_algo.h:3455:5: note:   ‘std::min’
     3455 |     min(initializer_list<_Tp> __l, _Compare __comp)
          |     ^~~
    In file included from /lt2http/src/bittorrent/meta_info.h:6,
                     from /lt2http/src/bittorrent/meta_info.cpp:1:
    
    

    Google bring me to this page - https://www.gnu.org/software/gcc/gcc-11/porting_to.html I think they now require explicit use of specific header files, and that is also a change in Clang 12 as well.

    opened by elgatito 5
  • GNUInstallDirs is required for CMAKE_INSTALL_DATAROOTDIR

    GNUInstallDirs is required for CMAKE_INSTALL_DATAROOTDIR

    You have commented out #include(GNUInstallDirs) in the root CMakeLists.txt, but it actually is required for CMAKE_INSTALL_DATAROOTDIR, otherwise it defaults to /, at least it does so in my builds (on Windows, GNU/Linux and Mac OS):

    $ mkdir build && cd $_
    $ cmake -DCMAKE_INSTALL_PREFIX="../install" -DJSON_STRUCT_OPT_BUILD_BENCHMARKS=0 -DJSON_STRUCT_OPT_BUILD_EXAMPLES=0 -DJSON_STRUCT_OPT_BUILD_TESTS=0 ..
    $ cmake --build . --target install --config Release
    
    -- Installing: D:/code/json-struct/install/lib/cmake/json_struct/json_structConfigVersion.cmake
    -- Installing: D:/code/json-struct/install/lib/cmake/json_struct/json_structConfig.cmake
    -- Installing: D:/code/json-struct/install/./include
    -- Installing: D:/code/json-struct/install/./include/json_struct.h
    -- Installing: D:/code/json-struct/install/./include/json_struct_diff.h
    -- Installing: /json_struct/package.xml
    

    And if I uncomment include(GNUInstallDirs), then CMAKE_INSTALL_DATAROOTDIR gets a proper value and installation goes correctly:

    ...
    -- Installing: D:/code/json-struct/install/share/json_struct/package.xml
    

    So, I am not sure why you commented it out, maybe there was a good reason, but I decided to let you know about this, just in case.

    While we are at it, perhaps you'd want to do something with . destination for installing include. It certainly works as it is, but doesn't look entirely correct (D:/code/json-struct/install/./include/json_struct.h).

    opened by retifrav 4
  • Errors  'js_static_meta_data_info' and 'js_static_meta_super_info'

    Errors 'js_static_meta_data_info' and 'js_static_meta_super_info'

    Hi, I have this struct into a variable cfg_

       struct Config
        {
            std::string webInterface = "0.0.0.0";
            int webPort = 9898;
            std::string apiKey = "xxxyyyzzz";
            std::string productId = "xxxxxxxxxx";
            std::string productVersion = "0.1";
            std::string licFile = "licfiles\\licfile_9001494765.lic";
            bool useSwagger = false;
            std::string revalidate = "0 */30 * * * ?";
            int thresholdOfflineHours = 48;
            int thresholdValidatedHours = 720;
            int cicoModule = -1;
            std::vector<Module> moduleList = fillDefaultModuleList();
            Config() {}
            Config(int argc, char* argv[]);
            JS_OBJECT(
                JS_MEMBER(webInterface),
                JS_MEMBER(webPort),
                JS_MEMBER(apiKey),
                JS_MEMBER(productId),
                JS_MEMBER(productVersion),
                JS_MEMBER(licFile),
    
                JS_MEMBER(useSwagger),
                JS_MEMBER(revalidate),
                JS_MEMBER(thresholdOfflineHours),
                JS_MEMBER(thresholdValidatedHours),
                JS_MEMBER(cicoModule),
                JS_MEMBER(moduleList)
            );
        };
    

    The JS::ParseContext parseContext(jsonData) is ok, but I try to serialize it to obtain a JSON string:

    std::string prettyJson = JS::serializeStruct(cfg_);
    

    I have these errors on build:

    C2039	'js_static_meta_data_info': is not a member of 'std::shared_ptr<licmgrapi::Config>'	
    C2039	'js_static_meta_super_info': is not a member of 'std::shared_ptr<licmgrapi::Config>'
    

    What I'm missing ?

    Thanks in advance

    opened by clabnet 4
  • Parsing std::vector<MyStruct*>

    Parsing std::vector

    How do I setup a member std::vector<MyStruct*> for serialization and deserialization? If I make it a vector of values rather than pointers it works fine.

    opened by keanine 3
  • min and max ?

    min and max ?

    #if defined(min) || defined(max)
    
    #error min or max macro is defined. Make sure these are not defined before including json_struct.h.\
     Use "#define NOMINMAX 1" before including Windows.h
    #endif
    

    I have #define NOMINMAX 1, but I can NOT compile this either,

    using vs 2022 and qt5. How can I find where are minx and max macro is defined and disable it ?

    opened by yanzixiang 3
  • Added user data member to ParseContext

    Added user data member to ParseContext

    For my particular use case, I have to use const char* members in my structs instead of std::string. To accomplish this, I have to keep the parsed strings somewhere in memory and use a custom type handler to provide the const char* pointer to the output struct.

    I realised this process would be made at least a little safer if the ParseContext class had support for some user data, since then my string memory pools could be provided on a per-parse basis rather than needing to be static global pools. I've created this PR simply to add a void* member to the ParseContext for this purpose. This should allow anyone else to provide context-specific data for when they do their own parsing.

    opened by noodlecollie 3
Owner
Jørgen Lind
Jørgen Lind
A simple class for parsing JSON data into a QVariant hierarchy and vice versa.

The qt-json project is a simple collection of functions for parsing and serializing JSON data to and from QVariant hierarchies. NOTE: Qt5 introduced a

null 301 Sep 22, 2022
A C++, header-only library for constructing JSON and JSON-like data formats, with JSON Pointer, JSON Patch, JSON Schema, JSONPath, JMESPath, CSV, MessagePack, CBOR, BSON, UBJSON

JSONCONS jsoncons is a C++, header-only library for constructing JSON and JSON-like data formats such as CBOR. For each supported data format, it enab

Daniel Parker 525 Sep 27, 2022
A small header-only library for converting data between json representation and c++ structs

Table of Contents Table of Contents What Is json_dto? What's new? v.0.3.0 v.0.2.14 v.0.2.13 v.0.2.12 v.0.2.11 v.0.2.10 v.0.2.9 v.0.2.8 v.0.2.7 v.0.2.6

Stiffstream 99 Sep 22, 2022
https://github.com/json-c/json-c is the official code repository for json-c. See the wiki for release tarballs for download. API docs at http://json-c.github.io/json-c/

\mainpage json-c Overview and Build Status Building on Unix Prerequisites Build commands CMake options Testing Building with vcpkg Linking to libjson-

json-c 2.5k Sep 26, 2022
A convenience C++ wrapper library for JSON-Glib providing friendly syntactic sugar for parsing JSON

This library is a wrapper for the json-glib library that aims to provide the user with a trivial alternative API to the API provided by the base json-

Rob J Meijer 16 May 10, 2022
An easy-to-use and competitively fast JSON parsing library for C++17, forked from Bitcoin Cash Node's own UniValue library.

UniValue JSON Library for C++17 (and above) An easy-to-use and competitively fast JSON parsing library for C++17, forked from Bitcoin Cash Node's own

Calin Culianu 24 Sep 21, 2022
A fast streaming JSON parsing library in C.

********************************************************************** This is YAJL 2. For the legacy version of YAJL see https

Lloyd Hilaiel 2.1k Sep 24, 2022
Very fast Python JSON parsing library

cysimdjson Fast JSON parsing library for Python, 7-12 times faster than standard Python JSON parser. It is Python bindings for the simdjson using Cyth

TeskaLabs 226 Oct 1, 2022
🗄️ single header json parser for C and C++

??️ json.h A simple single header solution to parsing JSON in C and C++. JSON is parsed into a read-only, single allocation buffer. The current suppor

Neil Henning 513 Sep 24, 2022
single-header json parser for c99 and c++

ghh_json.h a single-header ISO-C99 (and C++ compatible) json loader. why? obviously this isn't the first json library written for C, so why would I wr

garrison hinson-hasty 15 May 26, 2022
A very sane (header only) C++14 JSON library

JeayeSON - a very sane C++14 JSON library JeayeSON was designed out of frustration that there aren't many template-based approaches to handling JSON i

Jeaye Wilkerson 128 Jun 7, 2022
C++ header-only JSON library

Welcome to taoJSON taoJSON is a C++ header-only JSON library that provides a generic Value Class, uses Type Traits to interoperate with C++ types, use

The Art of C++ 487 Sep 26, 2022
A small header-only json library in C.

xjson A small header-only json library for C. The "unique" feature is that it allows use of the same code to serialize as well as deserialize, greatly

Stefan Bachmann 23 Jul 19, 2022
Fast JSON serialization and parsing in C++

DAW JSON Link v2 Content Intro Default Mapping of Types API Documentation - Member mapping classes and methods Cookbook Get cooking and putting it all

Darrell Wright 322 Oct 1, 2022
Jvar - JS inspired Variants and JSON parsing for C++

jvar jvar tries to capture some of the expressiveness of JavaScript and bring it into C++ In particular, it implements a Variant type which is similar

Yasser Asmi 23 Jun 15, 2022
jstruct is an automatic C code generation tool for generating JSON parsing and stringifying code.

jstruct is an automatic C code generation tool for generating JSON parsing and stringifying code. The C code generated by this tool needs to depend on the cJSON library for execution.

acoinfo 27 Apr 18, 2022
This is a JSON C++ library. It can write and read JSON files with ease and speed.

Json Box JSON (JavaScript Object Notation) is a lightweight data-interchange format. Json Box is a C++ library used to read and write JSON with ease a

Anhero inc. 108 Aug 26, 2022
Parsing gigabytes of JSON per second

simdjson : Parsing gigabytes of JSON per second JSON is everywhere on the Internet. Servers spend a *lot* of time parsing it. We need a fresh approach

null 16.1k Oct 4, 2022
Parsing gigabytes of JSON per second

simdjson : Parsing gigabytes of JSON per second JSON is everywhere on the Internet. Servers spend a *lot* of time parsing it. We need a fresh approach

null 16.1k Oct 5, 2022