Well, it's not actually a schema validator but anyway...
Motivation
I'm writing a library which is a UI to work with some electronics - modules. Each real module is represented by a corresponding class in the library. I've decided to add the ability to program a module at once using the config entity. Also it should be possible to read the current configuration from a module. Also I want a user could save (load) config to (from) file on disk, and that data should be human readable and editable. So I've chosen death JSON as a config entity.
So my classes have the following member functions:
void ReadConfig( json& config );
void WriteConfig( const json& config );
As an advantage I have a polymorphic object here : I don't need many different configs for each class.
As a disadvantage well ... the same. Having such a polymorphic object makes it impossible to check actions with wrong configs at compile time, for example, passing the discriminator config to program an ADC.
Solution
I've decided that any configurable module must have a default config (some kind of schema) which values, if any, are null
s. For example:
{
"name": "V2718",
"settings": {
"inputs": [
{
"led_polarity": null,
"polarity": null
},
{
"led_polarity": null,
"polarity": null
}
],
...
}
So the config is correct if and only if it can be obtained from the default one using only replacements of null
s. How do I check this? The answer is JSON Patch.
Code
Here is the code (see the Validate
member function). Each module-class inherits from the UConfigurable
abstract class. Of course, I could provide code for the Validate
function only, but I think the whole header is more consistent.
UConfigurable.h
#ifndef V_PLUS_CONFIGURABLE_H
#define V_PLUS_CONFIGURABLE_H
#include <nlohmann/json.hpp>
#include <iostream>
namespace vmeplus
{
using json = nlohmann::json;
// Curiously recurring template pattern
template <typename T>
class UConfigurable
{
protected :
static json fDefaultConfig;
public :
UConfigurable() {};
virtual ~UConfigurable() {};
virtual void ReadConfig( nlohmann::json &config ) = 0;
virtual void WriteConfig( const nlohmann::json &config ) = 0;
static json GetDefaultConfig() { return fDefaultConfig; }
static bool Validate( const json& source );
};
template<typename T>
bool UConfigurable<T>::Validate( const json& source )
{
bool verdict = true;
json patch = json::diff( source, fDefaultConfig );
for( auto it = patch.begin(); it != patch.end(); ++it )
{
// key "op" MUST be in any patch according to
// https://datatracker.ietf.org/doc/html/rfc6902
// and its value MUST be one of the following
// "add", "remove", "replace", "move", "copy", or "test"
if( it->at("op") == "replace" )
{
// if "op" is "replace" then there MUST be the "value" key
if( not (it->at("value").is_null()) )
{
verdict = false;
break;
}
}
else
{
verdict = false;
break;
}
}
return verdict;
}
void WriteConfigToFile( const json& j, const std::string& path );
json ReadConfigFromFile( const std::string& path );
}
#endif
JSON Schema
. If you're interested primarily in the end result, maybe it would be easier to use this instead? \$\endgroup\$