Giter Club home page Giter Club logo

json_struct's Introduction

Structurize your JSON

Build status ClusterFuzzLite PR fuzzing

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

SAST Tools

  • PVS-Studio - static analyzer for C, C++, C#, and Java code.

DAST Tools

All tests are run with Clang Address Sanitizer and Memory Sanitizers on pull requests.

json_struct's People

Contributors

amincheloh avatar biochimia avatar davidkorczynski avatar fraben avatar jorgen avatar jrmelsha avatar kolanich avatar noodlecollie avatar oysteinmyrmo avatar talhasaruhan avatar tvstensbyhexagon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

json_struct's Issues

Compilation errors with GCC 14.1.1

I got these after just caling ./build.sh:

In file included from /tmp/json_struct/examples/11_calling_functions.cpp:5:
In function 'int JS::Internal::ft::integer::to_buffer(T, char*, int, int*) [with T = int]',
    inlined from 'static void JS::TypeHandlerIntType<T>::from(const T&, JS::Token&, JS::Serializer&) [with T = int]' at /tmp/json_struct/include/json_struct/json_struct.h:7027:48,
    inlined from 'void JS::Internal::serializeMember(const T&, MemberInfo<MI_T, MI_M, MI_NC>&, JS::Token&, JS::Serializer&, const char*) [with T = FunctionCReturn; MI_T = int; MI_M = FunctionCReturn; MI_NC = JS::Tuple<StringLiteral<11> >]' at /tmp/json_struct/include/json_struct/json_struct.h:3061:26,
    inlined from 'static void JS::Internal::MemberChecker<T, Members, PAGE, INDEX>::serializeMembers(const T&, const Members&, JS::Token&, JS::Serializer&, const char*) [with T = FunctionCReturn; Members = JS::Tuple<JS::MI<int, FunctionCReturn, JS::Tuple<JS::Internal::StringLiteral<11> > >, JS::MI<int, FunctionCReturn, JS::Tuple<JS::Internal::StringLiteral<13> > >, JS::MI<int, FunctionCReturn, JS::Tuple<JS::Internal::StringLiteral<13> > >, JS::MI<int, FunctionCReturn, JS::Tuple<JS::Internal::StringLiteral<10> > > >; long unsigned int PAGE = 0; long unsigned int INDEX = 3]' at /tmp/json_struct/include/json_struct/json_struct.h:3171:20,
    inlined from 'static void JS::TypeHandler<T, Enable>::from(const T&, JS::Token&, JS::Serializer&) [with T = FunctionCReturn; Enable = void]' at /tmp/json_struct/include/json_struct/json_struct.h:4512:86,
    inlined from 'static JS::Error JS::Internal::FunctionCaller<T, U, Ret, Arg, NAME_COUNT, 1>::callFunctionAndSerializeReturn(T&, JS::FunctionInfo<U, Ret, Arg, NAME_COUNT, 1>&, JS::CallFunctionContext&) [with T = JsonFunctions; U = JsonFunctions; Ret = FunctionCReturn; Arg = const FunctionCArguments&; long unsigned int NAME_COUNT = 1]' at /tmp/json_struct/include/json_struct/json_struct.h:3829:29,
    inlined from 'JS::Error JS::matchAndCallFunction(T&, CallFunctionContext&, FunctionInfo<U, Ret, Arg, NAME_COUNT, TAKES_CONTEXT>&, bool) [with T = JsonFunctions; U = JsonFunctions; Ret = FunctionCReturn; Arg = const FunctionCArguments&; long unsigned int NAME_COUNT = 1; long unsigned int TAKES_CONTEXT = 1]' at /tmp/json_struct/include/json_struct/json_struct.h:4033:111,
    inlined from 'JS::Error JS::matchAndCallFunction(T&, CallFunctionContext&, FunctionInfo<U, Ret, Arg, NAME_COUNT, TAKES_CONTEXT>&, bool) [with T = JsonFunctions; U = JsonFunctions; Ret = FunctionCReturn; Arg = const FunctionCArguments&; long unsigned int NAME_COUNT = 1; long unsigned int TAKES_CONTEXT = 1]' at /tmp/json_struct/include/json_struct/json_struct.h:4027:7,
    inlined from 'static JS::Error JS::Internal::FunctionObjectTraverser<T, Functions, INDEX>::call(T&, JS::CallFunctionContext&, Functions&, bool) [with T = JsonFunctions; Functions = const JS::Tuple<JS::FunctionInfo<JsonFunctions, void, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, 1, 0>, JS::FunctionInfo<JsonFunctions, FunctionBReturn, const FunctionBArguments&, 1, 0>, JS::FunctionInfo<JsonFunctions, FunctionCReturn, const FunctionCArguments&, 1, 1>, JS::FunctionInfo<JsonFunctions, bool, int, 1, 0> >; long unsigned int INDEX = 2]' at /tmp/json_struct/include/json_struct/json_struct.h:4085:39,
    inlined from 'static JS::Error JS::Internal::FunctionObjectTraverser<T, Functions, INDEX>::call(T&, JS::CallFunctionContext&, Functions&, bool) [with T = JsonFunctions; Functions = const JS::Tuple<JS::FunctionInfo<JsonFunctions, void, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, 1, 0>, JS::FunctionInfo<JsonFunctions, FunctionBReturn, const FunctionBArguments&, 1, 0>, JS::FunctionInfo<JsonFunctions, FunctionCReturn, const FunctionCArguments&, 1, 1>, JS::FunctionInfo<JsonFunctions, bool, int, 1, 0> >; long unsigned int INDEX = 3]' at /tmp/json_struct/include/json_struct/json_struct.h:4090:66:
/tmp/json_struct/include/json_struct/json_struct.h:6836:43: error: writing 1 byte into a region of size 0 [-Werror=stringop-overflow=]
 6836 |     target_buffer[chars_to_write - 1 - i] = '0' + char(remainder);
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
/tmp/json_struct/include/json_struct/json_struct.h: In static member function 'static JS::Error JS::Internal::FunctionObjectTraverser<T, Functions, INDEX>::call(T&, JS::CallFunctionContext&, Functions&, bool) [with T = JsonFunctions; Functions = const JS::Tuple<JS::FunctionInfo<JsonFunctions, void, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, 1, 0>, JS::FunctionInfo<JsonFunctions, FunctionBReturn, const FunctionBArguments&, 1, 0>, JS::FunctionInfo<JsonFunctions, FunctionCReturn, const FunctionCArguments&, 1, 1>, JS::FunctionInfo<JsonFunctions, bool, int, 1, 0> >; long unsigned int INDEX = 3]':
/tmp/json_struct/include/json_struct/json_struct.h:7025:10: note: at offset -1 into destination object 'buf' of size 40
 7025 |     char buf[40];
      |          ^~~

build.sh script fails on Ubuntu 22.04

Hi - I'm getting compiler errors running the build.sh script on Linux Ubuntu 22.04. Are these expected?

Thanks.

  • git commit hash
$ git log -n 1
commit 9b719771470536763430703b7f04acc558dafe56 (HEAD -> master, origin/master, origin/HEAD)
Author: Jørgen Lind <[email protected]>
Date:   Thu Jan 19 12:12:19 2023 +0100

    Fix some warnings
$
  • OS
$ uname -a
Linux demo 5.15.0-70-generic #77-Ubuntu SMP Tue Mar 21 14:02:37 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04
Codename:       jammy
$
  • compiler
$ g++ --version
g++ (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Command sequence:

git clone [email protected]:jorgen/json_struct.git
cd json_struct/
./build.sh

Shell output:

.
.
.
[ 73%] Building CXX object tests/CMakeFiles/unit-tests-cxx17.dir/json-tokenizer-fail-test.cpp.o
[ 73%] Building CXX object tests/CMakeFiles/unit-tests-cxx17.dir/json-tokenizer-partial-test.cpp.o
[ 74%] Building CXX object tests/CMakeFiles/unit-tests-cxx17.dir/json-tokenizer-test.cpp.o
[ 75%] Building CXX object tests/CMakeFiles/unit-tests-cxx17.dir/json-function-test.cpp.o
In file included from /home/sscott/json_struct/tests/json-function-test.cpp:23:
/home/sscott/json_struct/include/json_struct/json_struct.h: In function ‘void {anonymous}::____C_A_T_C_H____T_E_S_T____10()’:
/home/sscott/json_struct/include/json_struct/json_struct.h:4113:39: error: array subscript ‘int (**)(...)[0]’ is partly outside array bounds of ‘{anonymous}::CallErrorCheck [1]’ [-Werror=array-bounds]
 4113 |     (container.*functionInfo.function)();
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
/home/sscott/json_struct/tests/json-function-test.cpp:372:18: note: while referencing ‘errorCheck’
  372 |   CallErrorCheck errorCheck;
      |                  ^~~~~~~~~~
In file included from /home/sscott/json_struct/tests/json-function-test.cpp:23:
/home/sscott/json_struct/include/json_struct/json_struct.h:4014:39: error: array subscript ‘int (**)(...)[0]’ is partly outside array bounds of ‘{anonymous}::CallErrorCheck [1]’ [-Werror=array-bounds]
 4014 |     (container.*functionInfo.function)(arg, context.error_context);
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/sscott/json_struct/tests/json-function-test.cpp:372:18: note: while referencing ‘errorCheck’
  372 |   CallErrorCheck errorCheck;
      |                  ^~~~~~~~~~
In file included from /home/sscott/json_struct/tests/json-function-test.cpp:23:
/home/sscott/json_struct/include/json_struct/json_struct.h: In function ‘void {anonymous}::____C_A_T_C_H____T_E_S_T____2()’:
/home/sscott/json_struct/include/json_struct/json_struct.h:3935:62: error: array subscript ‘int (**)(...)[0]’ is partly outside array bounds of ‘{anonymous}::CallFunctionSub [1]’ [-Werror=array-bounds]
 3935 |     TypeHandler<Ret>::from((container.*functionInfo.function)(arg), token, context.return_serializer);
      |                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
/home/sscott/json_struct/tests/json-function-test.cpp:143:19: note: while referencing ‘cont’
  143 |   CallFunctionSub cont;
      |                   ^~~~
In file included from /home/sscott/json_struct/tests/json-function-test.cpp:23:
/home/sscott/json_struct/include/json_struct/json_struct.h: In function ‘void {anonymous}::____C_A_T_C_H____T_E_S_T____6()’:
/home/sscott/json_struct/include/json_struct/json_struct.h:3996:39: error: array subscript ‘int (**)(...)[0]’ is partly outside array bounds of ‘{anonymous}::SuperParamCallable [1]’ [-Werror=array-bounds]
 3996 |     (container.*functionInfo.function)(arg);
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
/home/sscott/json_struct/tests/json-function-test.cpp:232:22: note: while referencing ‘cont’
  232 |   SuperParamCallable cont;
      |                      ^~~~
cc1plus: all warnings being treated as errors
gmake[2]: *** [tests/CMakeFiles/unit-tests-cxx17.dir/build.make:328: tests/CMakeFiles/unit-tests-cxx17.dir/json-function-test.cpp.o] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:696: tests/CMakeFiles/unit-tests-cxx17.dir/all] Error 2
gmake: *** [Makefile:166: all] Error 2
$

Cannot parse std::tuple<bool>

On MSVC, I'm unable parse fields like "member": [true] to a type std::tuple<bool> member. A minimal example is below

#include <iostream>
#include "json_struct.h"

struct TestInt {
  std::tuple<int32_t> member;
  JS_OBJ(member);
};

struct TestBool {
  std::tuple<bool> member;
  JS_OBJ(member);
};

int main(int argc, char** argv) {
  TestInt tiStruct;
  JS::ParseContext intContext(R"({ "member": [5] })");
  if (intContext.parseTo(tiStruct) == JS::Error::NoError) {
    std::cout << "Success, member is: " << std::get<0>(tiStruct.member) << std::endl;
  }
  else {
    std::cout << intContext.makeErrorString();
  }

  TestBool tbStruct;
  JS::ParseContext boolContext(R"({ "member": [true] })");
  if (boolContext.parseTo(tbStruct) == JS::Error::NoError) {
    std::cout << "Success, member is: " << std::get<0>(tbStruct.member) << std::endl;
  }
  else {
    std::cout << boolContext.makeErrorString();
  }
}

Output:

Success, member is: 5
Error ExpectedDelimiter:
{ "member": [true] }
                  ^

I'm not sure what the cause here is, all I can say from current investigation is that single item tuples of other basic types (number/string) still work, and tuples involving bools + other types work, e.g. std::tuple<bool, int> will parse fine if given [true, 2]

float deserial error

struct Json1
{
float num1;
double num2;

JS_OBJECT(JS_MEMBER(num1), JS_MEMBER(num2));
};
const char json_data1[] = R"json(
{
"num1": 32587.403333333333333333,
"num2": 32587.403333333333333333
}
)json";

Typo in json_struct.h

Hello,
I just wanted to point out a small typo at line 155 of file json_struct.h.
It's:
#if __cpp_if_consexpr

It probably should be:
#if __cpp_if_constexpr

JS_ENUM Defaults

how can I setenum defaults like this
JS_ENUM(Color, Red = 0x01, Green = 0x02, Blue = 0x03, Yellow4 = 0x04, Purple = 0x05)
struct ColorData {
Color color;

JS_OBJ(color);

};
JS_ENUM_DECLARE_STRING_PARSER(Color)

Compilation can pass, but running or have assert in source code 4438 lines.

I looked at the code and probably understood that this is looking for the name of the enumeration member, but there is no related '=' processing。

Does this library support enumerations with initial values?

Compilation fails if std::tuple member in struct

I'm running on Linux Ubuntu 22.04. I ported the TEST_CASE("test_serialize_simple", "[json_struct][serialize]") in tests/json-struct-serialize-test.cpp to google test, and added members to the Simple struct. If I include the std::tuple member in the JS_OBJ list, the compiler generates reams of failure messages.

struct Simple
{
    std::string A;
    bool b;
    int some_longer_name;
    std::vector<int> vector;
    std::string string;
    std::tuple<std::string, float, int> tup;
    std::unordered_map<std::string, double> uo_map;
    std::optional<std::string> optional;

    //JS_OBJECT(JS_MEMBER(A), JS_MEMBER(b), JS_MEMBER(some_longer_name));
    //JS_OBJ(A, b, some_longer_name, vector, string, tup, uo_map, optional); /// <<<< this fails
    JS_OBJ(A, b, some_longer_name, vector, string, uo_map, optional);        /// <<<< this succeeds

};

TEST_F(EsiJsonSerdesGTest, GenerateSimple)
{
    Simple simple;
    simple.A = "TestString";
    simple.b = false;
    simple.some_longer_name = 456;
    simple.vector = {1,2,3,4,5,6,7,8,9};
    simple.unordered_map = { {"foo", 100.0}, {"bar", 101.1}};
    simple.optional = "hello";

    std::string output = JS::serializeStruct(simple, JS::SerializerOptions(JS::SerializerOptions::Compact));
    EXPECT_STREQ(output.c_str(), expected1_compact);
}
  • git commit hash
$ git log -n 1
commit 9b719771470536763430703b7f04acc558dafe56 (HEAD -> master, origin/master, origin/HEAD)
Author: Jørgen Lind <[email protected]>
Date:   Thu Jan 19 12:12:19 2023 +0100

    Fix some warnings
$
  • OS
$ uname -a
Linux demo 5.15.0-70-generic #77-Ubuntu SMP Tue Mar 21 14:02:37 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04
Codename:       jammy
$
  • compiler
$ g++ --version
g++ (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

how to serialize std::map and std::set ?

can I use this to serialize std::map and std::set ?
for example i define a set A,
I use JS::serializeStruct(A) give me a error.
expect the output is ["1" , "2", "3" ]...

Invalid memory access when parsing message

This bug was discovered when we accidentally parsed a gateway error page as a JSON response from an API. I wasn't expecting the parsing to succeed, but the parsing process actually caused a crash.

I created a minimal reproducible example, below:

#include <iostream>
#include "json_struct.h"

static const char* const MESSAGE =
	"<html>\r\n"
	"<head><title>400 Bad Request</title></head>\r\n"
	"<body>\r\n"
	"<center><h1>400 Bad Request</h1></center>\r\n"
	"<hr><center>cloudflare</center>\r\n"
	"</body>\r\n"
	"</html>\r\n";

int main(int*, char**)
{
	std::cout << "Parsing message" << std::endl;

	JS::ParseContext context(MESSAGE);
	JS::Map outMap;
	JS::Error errror = context.parseTo(outMap);

	std::cout << "Error: " << context.makeErrorString() << std::endl;

	return 0;
}

When attempting to parse this, I get output that begins:

Parsing message
Error: Error EncounteredIllegalChar:
HHt�H�H�;r�HHtgH�H�#r�HHtOH�H��r�LHt7HD$HA...

After inspection in the debugger, the issue appears to be an unsigned underflow on line 1740:

for (cursor_back = real_cursor_index - 1; cursor_back > stop_back; cursor_back--)

Here, because the first character of the input was not valid, real_cursor_index is 0, so subtracting 1 underflows the index.

problem on unordered_map<string,vector<T>>

seems that unordered_map<string,vector<T>> is not suupported
the key of unordered_map is empty string

test case

#include <string>
#include <vector>
#include <map>
#include <json_struct.h>
#include <cinttypes>
//these two have to be ifdef guarded becuase JS support compiler versions where
//they are not implemented
#ifdef JS_STD_UNORDERED_MAP
#include <unordered_map>
#endif
#ifdef JS_STD_OPTIONAL
#include <optional>
#endif


const char json[] = R"json(
{
    "unordered_map" : {
        "foo" : [1],
        "bar" : [2]
    }
}
)json";

struct JsonData
{
#ifdef JS_STD_UNORDERED_MAP
    std::unordered_map<std::string, std::vector<double>> unordered_map;
#else
    JS::JsonObject unordered_map;
#endif
  JS_OBJ(unordered_map);

};

int main()
{
    JsonData dataStruct;
    JS::ParseContext parseContext(json);
    if (parseContext.parseTo(dataStruct) != JS::Error::NoError)
    {
        std::string errorStr = parseContext.makeErrorString();
        fprintf(stderr, "Error parsing struct %s\n", errorStr.c_str());
        return -1;
    }

#ifdef JS_STD_UNORDERED_MAP
    fprintf(stderr, "unordered_map:\n");
    for (auto it : dataStruct.unordered_map)
        fprintf(stderr, "\t%s", it.first.c_str());  // empty string here
#endif
    return 0;
}

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.

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 ?

Not compile at armv7

Hello! I try compile your library at orange pi (arm v7). Use gcc/g++ version 7 and 8. But this error on compile:

 error: static assertion failed: Missing JS_OBJECT JS_OBJECT_EXTERNAL or TypeHandler specialisation

   static_assert(sizeof(HasJsonStructBase<JS_OBJECT_T>::template test_in_base<JS_OBJECT_T>(nullptr)) ==
                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
                   sizeof(typename HasJsonStructBase<JS_OBJECT_T>::yes),
                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                               
/home/user/socket/json_struct.h:2778:98: error: ‘long int’ is not a class, struct, or union type
   using TT = decltype(JS_OBJECT_T::template JsonStructBase<JS_OBJECT_T>::js_static_meta_data_info());
                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
/home/user/socket/json_struct.h:2779:99: error: ‘long int’ is not a class, struct, or union type
   using ST = decltype(JS_OBJECT_T::template JsonStructBase<JS_OBJECT_T>::js_static_meta_super_info());

error: ‘js_static_meta_data_info’ is not a member of ‘JS::Internal::JsonStructBaseDummy<long int, long int>’
   auto members = Internal::JsonStructBaseDummy<T, T>::js_static_meta_data_info();
error: ‘js_static_meta_data_info’ is not a member of ‘JS::Internal::JsonStructBaseDummy<long int, long int>’
   using Members = decltype(Internal::template JsonStructBaseDummy<T, T>::js_static_meta_data_info());
                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
/home/user/socket/json_struct.h:3108:101: error: ‘js_static_meta_super_info’ is not a member of ‘JS::Internal::JsonStructBaseDummy<long int, long int>’
   using SuperMeta = decltype(Internal::template JsonStructBaseDummy<T, T>::js_static_meta_super_info());
                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
/home/user/socket/json_struct.h:3107:98: error: ‘js_static_meta_data_info’ is not a member of ‘JS::Internal::JsonStructBaseDummy<long int, long int>’
   using Members = decltype(Internal::template JsonStructBaseDummy<T, T>::js_static_meta_data_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

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

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.

Adding js member listeners

Hi, I really like your library. I think it provides a really elegant interface for serializing and deserializing json.

I would like to propose adding a feature that I think could be useful to the users of json_struct. The feature I would like to propose is having the ability to register listeners to struct members that will be called during deserialization.
For example, deserializing a Person object with the fields name and age could trigger methods registered as property listeners like onNameChanged(std::string) and onAgeChanged(int).
The syntax for adding these property listeners could be something similar to the existing workflow. Something along the lines of:

struct Person
{
    std::string name;
    unsigned age;
    void onNameChanged(std::string);
    void onAgeChanged(unsigned);

    JS_OBJECT(JS_MEMBER_WITH_LISTENER(name, &Person::onNameChanged), 
              JS_MEMBER_WITH_LISTENER(age, &Person::onAgeChanged));
};

The outcome of this would be that upon deserializing, the registered listeners would be triggered, which could enable some additional custom logic to run for every deserialized field.

I understand this feature might not exactly be part of the original scope for json_struct, but I think it might add real value to the library and increase its utility by adding something more than serializing and deserializing.

I would like to volunteer to write the implementation of this. Even if you think this feature doesn't belong in json_struct, I would still appreciate some pointers on how you would recommend integrating this in json_stuct, as I would like to give it a whirl anyway.

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

Parsing std::vector<MyStruct*>

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.

Enhancement in EnumHandler

Hello.

First of all, thanks you your library, which saved pretty much time for me!

While using Enum serializer, I have noticed that it is only possible to serialize string bash to enum:
https://github.com/jorgen/json_struct/blob/master/include/json_struct.h#L4261

static inline Error to(T &to_type, ParseContext &context)
  {
    if (context.token.value_type == Type::String)
    {
      auto &strings = F::strings();
      for (size_t i = 0; i < strings.size(); i++)
      {
        const DataRef &ref = strings[i];
        if (ref.size == context.token.value.size)
        {
          if (memcmp(ref.data, context.token.value.data, ref.size) == 0)
          {
            to_type = static_cast<T>(i);
            return Error::NoError;
          }
        }
      }
    }
    return Error::IllegalDataValue;
  }

It would be very helpful if it become possible to serialize enum from int value, which is the enum value actually.

EnumHandler::to can be adjusted with another clause to parse number, something like this:

     else if (context.token.value_type == Type::Number)
     {
       int32_t tmp;
       const char *pointer;
       auto parse_error = Internal::ft::integer::to_integer(context.token.value.data, context.token.value.size, tmp, pointer);
       if (parse_error != Internal::ft::parse_string_error::ok || context.token.value.data == pointer)
         return Error::FailedToParseInt;

       to_type = static_cast<T>(tmp);
       return Error::NoError;
     }

Enum class support

Is there a way to serialize enum class props without a custom TypeHandler?

Example:

enum class MyEnum : int {

}

struct MyObject {
   MyEnum myEnum;

   JS_OBJ(myEnum);
}

Brilliant!

Can we now get rid of std:: lib, please?

std::optional<std::vector<my_struct>> will cause program exit

change from:
#ifdef JS_STD_OPTIONAL
/// \private
template
struct TypeHandler<std::optional>
{
public:
static inline Error to(std::optional &to_type, ParseContext &context)
{
to_type = {};
return TypeHandler::to(to_type.value(), context);
}

static inline void from(const std::optional &opt, Token &token, Serializer &serializer)
{
if (opt.has_value())
TypeHandler::from(opt.value(), token, serializer);
}
};
#endif

to:
#ifdef JS_STD_OPTIONAL
/// \private
template
struct TypeHandler<std::optional>
{
public:
static inline Error to(std::optional &to_type, ParseContext &context)
{
to_type = T();
return TypeHandler::to(to_type.value(), context);
}

static inline void from(const std::optional &opt, Token &token, Serializer &serializer)
{
if (opt.has_value())
TypeHandler::from(opt.value(), token, serializer);
}
};
#endif

will fix the bug

makeErrorString() fails with exception in basic_string::_M_create

#include "json_struct.h"

struct AfterburnerConfig 
{
    double beam_hor_variance = 0;
    JS_OBJ(beam_hor_variance);
};

I put some invalid JSon here:

{
    /* dfd */
    "beam_hor_variance" : 6.0,
}

Readout code is nothing fancy:

// Read
{
    std::ifstream reader(file_name);
    std::stringstream buffer;
    buffer << reader.rdbuf();
    auto json = buffer.str();

    AfterburnerConfig config2;
    JS::ParseContext parseContext(json);
    if (parseContext.parseTo(config2) != JS::Error::NoError)
    {
        std::string errorStr = parseContext.makeErrorString();   // <=== fails here
        throw std::runtime_error(errorStr);
    }
    REQUIRE(config2.beam_hor_variance == Catch::Approx( 6 ));
}

Instead of getting an error with message about the wrong character it fails in makeErrorString():

due to unexpected exception with message:
  basic_string::_M_create

It fails exactly here:

inline std::string Tokenizer::makeErrorString() const
{
  static_assert(sizeof(Internal::error_strings) / sizeof *Internal::error_strings == size_t(Error::UserDefinedErrors),
                "Please add missing error message");

  std::string retString("Error");
  if (error_context.error < Error::UserDefinedErrors)
    retString += std::string(" ") + Internal::error_strings[int(error_context.error)];
  if (error_context.custom_message.size())
    retString += " " + error_context.custom_message;
  retString += std::string(":\n");
  for (size_t i = 0; i < error_context.lines.size(); i++)
  {
    retString += error_context.lines[i] + "\n";
    if (i == error_context.line)
    {
      std::string pointing(error_context.character + 1, ' ');
      pointing[error_context.character - 1] = '^';
      pointing[error_context.character] = '\n';
      retString += pointing;   // <========= HERE 
    }
  }
  return retString;
}

Error triggered but not surfaced when setting allow_missing_members or allow_unassigned_required_members

I believe I found a bug when setting allow_missing_members or allow_unassigned_required_members. In the example below, parseTo() does indeed return an error (in one of my tests, a JS::Error::MissingPropertyMember), but when makeErrorString() is called it returns "Error NoError:".

#include "json_struct.h"
#include <iostream>

struct MyStruct {
  int a;
  int b;

  JS_OBJ(a, b);
};

int main() {
  std::string json = "{\"a\":1, \"c\": 3}";

  MyStruct my_struct;

  JS::ParseContext parse_ctx(json);

  parse_ctx.allow_missing_members = false;
  parse_ctx.allow_unnasigned_required_members = false;

  if (parse_ctx.parseTo(my_struct) != JS::Error::NoError) {
    std::cout << "Error parsing struct: " << parse_ctx.makeErrorString() << "\n";
  }

}

Output: Error parsing struct: Error NoError:

Expected: either a MissingPropertyMember or UnassignedRequiredMember error string, depending on the circumstances.

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?

this doest NOT support serialize a std::map with an int as its key

Hi,
I have been putting off making these TypeHandlers since then we have to include <map>, <set>, <unordered_set> in json_struct.h and suddenly everyone has has these includes just by including json_struct. I regret most of the includes in there already, but changing this can break user code, (I still have some members with spelling mistakes because chaning it will break code).

I have come up with this scheme where the TypeHandlers are guarded by their own include guards. So if you want the std::map TypeHandler then you will have to define JS_STL_MAP before including json_struct.h. The unit test should give you the idea: https://github.com/jorgen/json_struct/blob/master/tests/json-unordered-map.cpp#L4.

This is available in 3ddf92e.

Originally posted by @jorgen in #27 (comment)

for example;

std::map<int,std::map<int,std::string>> a;

JS::serializeStruct(a);

will give me an errer

invalid conversion from 'int' to 'const char*'

it seems that

/*!
 *  \brief Pointer to data
 *
 *  DataRef is used to refere to some data inside a json string. It holds the
 *  start posisition of the data, and its size.
 */
struct DataRef
{
  /*!
   * Constructs a null Dataref pointing to "" with size 0.
   */
  constexpr explicit DataRef()
    : data("")
    , size(0)
  {
  }

  /*!
   * Constructs a DataRef pointing to data and size.
   * \param data points to start of data.
   * \param size size of data.
   */
  constexpr explicit DataRef(const char *data, size_t size)
    : data(data)
    , size(size)
  {
  }

  /*!  Cobstructs a DataRef pointing to an array. This will \b NOT look for
   * the null terminator, but just initialize the DataRef to the size of the
   * array - 1. This function is intended to be used with string literals.
   * \param data  start of the data.
   */
  template <size_t N>
  constexpr explicit DataRef(const char (&data)[N])
    : data(data)
    , size(N - 1)
  {
  }

  explicit DataRef(const std::string &str)
    : data(&str[0])
    , size(str.size())
  {
  }

  explicit DataRef(const char *data)
    : data(data)
    , size(strlen(data))
  {
  }

  const char *data;
  size_t size;
};

does NOT privide an constructor which treat int as an input.

Serialize runtime exceptions when debug enabled and struct contains vector

serializeStruct throws various errors (sometimes a "bad alloc" exception, sometimes segv, sometimes invalid pointer) if a struct contains a vector and the compiler debug options are enabled.

Compiler:

$ g++ --version
g++ (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0

Compiler command line options:

-g -std=c++20     <<<<<< with -DCMAKE_BUILD_TYPE=Debug (this fails at runtime)

-O3 -DNDEBUG -std=c++20   <<<<<  with -DCMAKE_BUILD_TYPE=Release (this succeeds at runtime)

Google Test version of test code:

struct Simple
{
    std::vector<int> v;
    JS_OBJECT(JS_MEMBER(v));
};

const char expected1_compact[] = R"json({"v":[1,2,3,4,5,6,7,8,9]})json";

TEST_F(EsiJsonSerdesGTest, Struct2Json)
{
    std::vector<int> temp_vect{1,2,3,4,5,6,7,8,9};

    Simple simple;
    simple.v = temp_vect;

    std::string output = JS::serializeStruct(simple, JS::SerializerOptions(JS::SerializerOptions::Compact));
    EXPECT_STREQ(output.c_str(), expected1_compact);
}

Build/run commands:

 rm -rf build
 mkdir build
 cd build
 cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=./install_dir ..
 ninja
./gtest_sim --gtest_filter=EsiJsonSerdesGTest.Struct2Json
Running main() from /home/sscott/eg_link/eg_esi/apps/build/_deps/googletest-src/googletest/src/gtest_main.cc
Note: Google Test filter = EsiJsonSerdesGTest.Struct2Json
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EsiJsonSerdesGTest
[ RUN      ] EsiJsonSerdesGTest.Struct2Json
[       OK ] EsiJsonSerdesGTest.Struct2Json (0 ms)
[----------] 1 test from EsiJsonSerdesGTest (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

cd ..
rm -rf build
mkdir build
cd build
cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=./install_dir ..
ninja
./gtest_sim --gtest_filter=EsiJsonSerdesGTest.Struct2Json
Running main() from /home/sscott/eg_link/eg_esi/apps/build/_deps/googletest-src/googletest/src/gtest_main.cc
Note: Google Test filter = EsiJsonSerdesGTest.Struct2Json
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EsiJsonSerdesGTest
[ RUN      ] EsiJsonSerdesGTest.Struct2Json
free(): invalid pointer
Aborted (core dumped)

serial a class' private member

i try to serial a class like this,

class a{
public:
  std::string b;
}

JS_OBJ_EXT(a,b);
a my_a;
auto json_string_a = JS::serializeStruct(my_a);

it works fine.

but when i add a private member c

class a{
public:
  std::string b;
private:
  std::string c;
}

JS_OBJ_EXT(a,b,c);

the complier says it can NOT access private member.
so, how can i make it work again ?

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).

Serializer::writeAsString will not write empty string

the Serializer::writeAsString will write nothing when given "" as data, most likely resulting in a bad JSON.

to reproduce give it something like ... {"key1": "value1", "key2" : "", "key3":"value3"}... the output will be:

 {
 	"key1": "value1",
 	"key2": ,
 	"key3": "value3"
 }

the problem might be in the tokenizer giving the token size = 0.

or is the code wrong?

std::string prettify(const std::string& json) const
{
	std::string prettyJson;

	JS::Tokenizer tokenizer;
	tokenizer.addData(json.c_str(), json.size());
	JS::Token token;
	JS::Error e = JS::Error::NoError;
	JS::JsonTokens jsonTokens;

	while (e == JS::Error::NoError)
	{
		e = tokenizer.nextToken(token);
		if (e == JS::Error::NoError)
			jsonTokens.data.emplace_back(token);
	}
	assert(e == JS::Error::NeedMoreData);

	JS::SerializerContext context(prettyJson);
	context.serialize(jsonTokens.data);

	return prettyJson;
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.