This is a program that parses C++ structures from a header file and automatically generates C++ code capable of serializing said structures into the JSON format and parsing it back.
For example, if provided with the following structure as input,
struct User {
int id;
std::string name;
std::vector<int> friendIds;
};
JSON-CPP-gen can generate a UserParser
class that can be used as
User user;
auto error = UserParser::parse(user, jsonString);
to automatically parse a JSON string that matches the stucture, e.g.
{
"id": 137,
"name": "John Smith",
"friendIds": [ 63, 51, 206 ]
}
And analogously, it can generate a UserSerializer
class for
the inverse operation.
Of course, the input may be more complex - see Configuration
and the generated ConfigurationParser
for a more complex example.
The generated parsers and serializers are highly efficient because no intermediate DOM or other auxiliary data structures are constructed - data is read directly from / to the input structures.
To build the program, simply use the provided CMake file. JSON-CPP-gen has no dependencies besides the C++ standard library.
To run the program, you must provide a JSON configuration file
that corresponds to the Configuration
structure
as its command line argument.
For example, to generate the UserParser
and UserSerializer
classes
described above, you could use a configuration file containing:
{
"inputs": [ "User.h" ],
"includes": [ ],
"settings": { },
"parsers": [ {
"name": "UserParser",
"types": [ "User" ],
"headerOutput": "UserParser.h",
"sourceOutput": "UserParser.cpp"
} ],
"serializers": [ {
"name": "UserSerializer",
"types": [ "User" ],
"headerOutput": "UserSerializer.h",
"sourceOutput": "UserSerializer.cpp"
} ],
"stringType": "std::string"
}
All (non-absolute) file names are relative to the configuration file.
includes
may contain additional include files that should be present
in the output files and settings
may contain code generator
settings.
The includes
, settings
, and stringType
fields could be omitted in this case
since they are empty or equal to the default value.
Even parsers
or serializers
could be omitted if you only needed one of the two.
Additionally, custom string and container data types can be defined
in the configuration file - see below.
Another example is the configuration file
for the ConfigurationParser
class.
- Supported C++ types:
- All fundamental integer and floating-point types,
bool
, andsize_t
- Structures (
struct
) - Enumerations (
enum
andenum class
) - serialized as strings - Static arrays, including multi-dimensional
- Standard library types:
std::string
for stringsstd::vector
,std::deque
,std::list
for dynamic-sized arraysstd::array
for static-sized arraysstd::map
for objects with arbitrary keys and homogeneous valuesstd::optional
as well asstd::auto_ptr
,std::shared_ptr
, andstd::unique_ptr
for optional values
- Custom string, array, object, and optional types if defined in the configuration file, see below
- All fundamental integer and floating-point types,
- Namespaces
- Structure inheritance (basic support)
- Full UTF-8 & UTF-16 support in JSON
Currently NOT supported but planned features:
- Omitting specific member variables from parsing and serialization - planned via annotations
- Structure members under different name in JSON - planned via annotations
- Classes - currently ignored due to their data being typically private but explicit enablement via annotations is planned - access to private members must be ensured by the user
#define
,typedef
,using
aliases - basic support planned
What will (probably) never be supported:
- Heterogeneous JSON objects - not really representable by static stuctures
- Unions - impossible to serialize without more advanced logic
- Raw pointers - no point in serializing memory addresses and unclear memory management otherwise
- Template structures
- More complex expression / macro evaluation (e.g. array of length
20*sizeof(User)
)
Even though the standard library string and container types are supported by default, you are not locked into using these exclusively in order for the automatic parser and serializer generation to work. In fact, you may configure JSON-CPP-gen even to produce code where none of the C++ standard library is used.
However, in order to do that, you must provide a set of replacement classes, and for each an API that dictates how it should be used. The API consists of code snippets for specific operations with placeholders. See comments in Configuration.h for a full list of the placeholders. The following are examples for each category of definable custom types and how they should be written in the configuration file. You can specify multiple types for each category.
These are mostly demonstrated on types from the standard library, which however should not be put into the configuration file as they are present by default.
"stringTypes": [ {
"name": "std::string",
"api": {
"clear": "$S.clear()",
"getLength": "$S.size()",
"getCharAt": "$S[$I]",
"appendChar": "$S.push_back($X)",
"appendCStr": "$S += $X",
"appendStringLiteral": "$S += $X",
"equalsStringLiteral": "$S == $X",
"iterateChars": "for (char $E : $S) { $F }"
}
} ]
Represents a string type that cannot be constructed incrementally.
Does not have an analogue in the standard library.
An intermediate (dynamic) string type must be specified for the parser (can be std::string
).
"constStringTypes": [ {
"name": "ConstString",
"stringType": "std::string",
"api": {
"copyFromString": "$S = $X",
"moveFromString": "$S = std::move($X)",
"iterateChars": "for (char $E : $S) { $F }"
}
} ]
"arrayContainerTypes": [ {
"name": "std::vector<$T>",
"api": {
"clear": "$S.clear()",
"refAppended": "($S.emplace_back(), $S.back())",
"iterateElements": "for ($T const &$E : $S) { $F }"
}
} ]
Note: The refAppended
operation must add an empty element at the end of the array
and return a modifiable reference to the new element.
Represents a non-statically fixed-length array that cannot be constructed incrementally.
Does not have an analogue in the standard library.
An intermediate (dynamic) array type must be specified for the parser
(can be std::vector
or another implicit array type).
"fixedArrayContainerTypes": [ {
"name": "FixedArray<$T>",
"arrayContainerType": "std::vector",
"api": {
"copyFromArrayContainer": "$S = $X",
"moveFromArrayContainer": "$S = std::move($X)",
"iterateElements": "for ($T const &$E : $S) { $F }"
}
} ]
"staticArrayContainerTypes": [ {
"name": "std::array<$T, $N>",
"api": {
"refByIndex": "$S[$I]"
}
} ]
Note: $N
is array length.
"objectContainerTypes": [ {
"name": "std::map<std::string, $T>",
"keyType": "std::string",
"api": {
"clear": "$S.clear()",
"refByKey": "$S[$K]",
"iterateElements": "for (const std::pair<$U, $T> &$I : $S) { $U const &$K = $I.first; $T const &$V = $I.second; $F }"
}
} ]
Notes: A key type must be specified as keyType
(can be std::string
).
The refByKey
operation must create the element if it doesn't already exist
and return a modifiable reference to its value.
The iterateElements
operation must provide keys and values separately as $K
and $V
.
"objectMapContainerTypes": [ {
"name": "std::map<$K, $T>",
"api": {
"clear": "$S.clear()",
"refByKey": "$S[$K]",
"iterateElements": "for (const std::pair<$U, $T> &$I : $S) { $U const &$K = $I.first; $T const &$V = $I.second; $F }"
}
} ]
"optionalContainerTypes": [ {
"name": "std::optional<$T>",
"api": {
"clear": "$S.reset()",
"refInitialized": "($S = $T()).value()",
"hasValue": "$S.has_value()",
"getValue": "$S.value()"
}
} ]
Note: The refInitialized
operation must initialize the container
with a default-constructed value and return a modifiable reference to the value.