-
Notifications
You must be signed in to change notification settings - Fork 3
Home
(with JSON Schema draft04 support)
wjelement-cpp provides a simple, lightweight wrapper around the excellent wjelement C library. The wrapper supports JSON Schema draft-04.
While wjelement is fast and memory-efficiency, this C++ wrapper makes it far more convenient to use and the code is easier to read.
The WJPP::Node
class is to wjelement-cpp what WJElement
(a pointer to a WJElementPublic struct
) is to wjelement. You can pass a WJElement
to a WJPP::Node
constructor and you now have a C++ interface.
Here is a simple example:
#include <wjelement++.h>
#include <iostream>
using namespace WJPP;
using namespace std;
int main(int argc, char** argv)
{
Node people = Node::parseJson("[{\"name\":\"john\",\"male\":true,\"children\":\"mary\",\"anna\",\"josh\"],\"age\":40,\"partner\":\"rosie\"},\"name\":\"rosie\",\"male\":false,\"children\":\"mary\",\"anna\",\"josh\"],\"age\":37,\"partner\":\"john\"}]");
for (Node::iterator i = people.begin(); i != people.end(); i++)
{
Node person = *i;
cout << "===============================================================================" << endl;
cout << "Name: " << person["name"] << endl;
cout << "Age: " << person["age"] << endl;
cout << "Sex: " << (person["male"].getBool() ? "male" : "female") << endl;
cout << "Partner: " << person["partner"] << endl;
cout << "Kids: ";
Node kids = person["children"];
for (Node::iterator j = kids.begin(); j != kids.end(); j++)
cout << *j << " ";
cout << endl;
}
people.discard(); // frees memory
}
The WJPP::Node class simply wraps a WJElement. It is therefore highly efficient to pass a Node by value.
The wjelement library functions often return a WJElement which may be NULL. wjelement-cpp has many methods which return a Node even though the operation may potentially fail. For example, operator[](char*name)
returns the Node of an object with the given name. So what happens if the message is sent to an Node which is not an object or the object doesn't have a property with the given name? The Node returned will wrap a NULL WJElement and therefore, operator!()
will return true.
This allows you to write code like:
Node x = people[2]["children"]["partner"]["age"];
if (x) { /* we'll never get here because x is NULL (or more correctly wraps a NULL WJElement) */}
if (!x) { /* we end up in here */}
This won't throw an exception even though people[2] already returns a Node that wraps a NULL WJElement.
wjelement-cpp does not use or even honour wjelement like selectors:
people["[1].children"]; // returns a NULL Node
people[1]["children"]; // fine
WJPP::Node
implements a simple C++ standard library like non-const forward iterator. It works on all nodes, but unless the Node wraps an array or object, WJPP::Node::begin()
will always return the same as WJPP::Node::end()
. The iterator is the most efficient way to process each element in a collection.
WJPP::Node
rarely throws std::runtime_error
exceptions. Exceptions may be raised when you...
- parse JSON
- attach/detach a node
- add a node to a node that already has that property
- add a Node to a Node that is neither an array nor an object
- request a value that is not compatible with the Node's type (for example Node is string and you send a
getBool()
message) - JSON schema based operations
wjelement-cpp supports the JSON Schema draft 04. In the test folder you'll find a draf4-tests subfolder with all the test cases shipped with draft 04. In the test folder itself, you will find wjelement-test (a wjelement-cpp client application) which simply processes all the test cases in draf4-tests and prints the result to std::cout.
WJPP::Cache
is a singleton class which is responsible for storing schema details. The only instance of the class is created when you first request the singleton via Cache::GetCache()
. During construction of the only singleton class instance, two schemas are created:
- the draft 04 meta schema (accessible via
cache.getMetaSchema()
) - an empty schema (accessible via
cache.getEmptySchema()
)
These schemas are hard coded into wjelement-cpp. The draft 04 meta schema allows you to load and validate draft 04 compliant schemas which can then be used to validate instance data.
Here is a version of the example shown above which uses schema validation:
#include <wjelement++.h>
#include <iostream>
using namespace WJPP;
using namespace std;
int main(int argc, char** argv)
{
/*
We obtain a reference to the global JSON schema cache by calling Cache::GetCache.
The first time this is called the cache is created and the JSON Schema Draft-04
meta schema is loaded. Any schemas you load will be stored in the cache. The cache
is cleaned on exit.
*/
Cache& cache = Cache::GetCache();
/*
A hard coded sample Draft-04 compliant JSON schema
*/
string strSchema=
"{\n"
"\t\"$schema\": \"http://json-schema.org/draft-04/schema#\",\n"
"\t\"type\": \"array\",\n"
"\t\"items\": {\n"
"\t\t\"type\": \"object\",\n"
"\t\t\"properties\": {\n"
"\t\t\t\"name\": { \"type\": \"string\" },\n"
"\t\t\t\"age\": { \"type\": \"integer\" },\n"
"\t\t\t\"male\": { \"type\": \"boolean\" },\n"
"\t\t\t\"partner\": { \"type\": \"string\" },\n"
"\t\t\t\"children\": { \n"
"\t\t\t\t\"type\": \"array\",\n"
"\t\t\t\t\"items\": { \"type\": \"string\" },\n"
"\t\t\t\t\"uniqueItems\": true\n"
"\t\t\t}\n"
"\t\t}\n"
"\t}\n"
"}\n";
/*
Declare an errors node which is NULL initially. Note, a ManagedNode is
just like a Node except on exit it deletes the attached JSON structure.
*/
ManagedNode errors;
/*
Parse the schemas JSON. Since the schema will end up in the schema Cache
we do not make it a ManagedNode.
*/
Node schema = Node::parseJson(strSchema);
/*
Get the meta schema from the cache to validate the parsed schema and store
potential errors in errors node.
*/
cache.getMetaSchema().validateInstance(schema, errors);
if (errors)
{
/*
print errors to cout and stop (note it is our responsibility to discard
the errors to avoid leaks).
*/
cout << "Errors in schema:";
errors.dump(cout);
/*
schema needs to be discarded explicitly because erroneous schemas will
not be cached
*/
schema.discard();
}
else
{
/*
Parse a document. We assign this to a managed node which will automatically
discard the entire document during destruction.
*/
ManagedNode people = Node::parseJson("[{\"name\":\"john\",\"male\":\"yes\",\"children\":[\"mary\",\"anna\",\"josh\"],\"age\":40,\"partner\":\"rosie\"},{\"name\":\"rosie\",\"male\":false,\"children\":[\"mary\",\"anna\",\"josh\"],\"age\":37,\"partner\":\"john\"}]");
/*
Get the schema to validate the data. If errors were found, write these to
cout and stop (note it is our responsibility to discard the errors to
avoid leaks).
*/
schema.validateInstance(people, errors);
if (errors)
{
cout << "Errors in instance:";
errors.dump(cout);
}
else
{
for (Node::iterator i = people.begin(); i != people.end(); i++)
{
Node person = *i;
cout << "===============================================================================" << endl;
cout << "Name: " << person["name"] << endl;
cout << "Age: " << person["age"] << endl;
cout << "Sex: " << (person["male"].getBool() ? "male" : "female") << endl;
cout << "Partner: " << person["partner"] << endl;
cout << "Kids: ";
Node kids = person["children"];
for (Node::iterator j = kids.begin(); j != kids.end(); j++)
cout << *j << " ";
cout << endl;
}
}
}
}
If you run the example above, you will get this output:
Errors in instance:
[
{
"schema":"#/items/properties/male",
"instance":"#/*/male",
"message":"expecting type boolean but found type string"
}
]
The reason is that I made a small change to the instance JSON from the original example: I changed the first persons "male" value from true to "yes". This of course violates the schema which demands that property "male" is a boolean. Errors always have these properties:
- schema A JSON pointer to the schema element that was used to validate the instance data
-
instance A JSON pointer to the instance data that was found to be faulty (please note that array elements are denoted with an asterix so in the error example above, the JSON pointer
#/*/male
could refer to#/0/male
and/or#/1/male
; using an asterix simply ensures that you don't get flooded with errors if the same error applies to each element in a very large array) - message The problem that was found.
You can find code documentation here: http://www.d3.org.nz/wjelement-cpp/