259 lines
7.8 KiB
Markdown
259 lines
7.8 KiB
Markdown
# New Json Utility API
|
|
|
|
## Raw value conversion (GetValue)
|
|
|
|
`GetValue` is a convenience helper that will either read a value into existing storage (type-deduced) or
|
|
return a JSON value coerced into the specified type.
|
|
|
|
When reading into existing storage, it returns a boolean indicating whether that storage was modified.
|
|
|
|
If the JSON value cannot be converted to the specified type, an exception will be generated.
|
|
For non-nullable type conversions (most POD types), `null` is considered to be an invalid type.
|
|
|
|
```c++
|
|
std::string one;
|
|
std::optional<std::string> two;
|
|
|
|
JsonUtils::GetValue(json, one);
|
|
// one is populated or an exception is thrown.
|
|
|
|
JsonUtils::GetValue(json, two);
|
|
// two is populated, nullopt or an exception is thrown
|
|
|
|
auto three = JsonUtils::GetValue<std::string>(json);
|
|
// three is populated or an exception is thrown
|
|
|
|
auto four = JsonUtils::GetValue<std::optional<std::string>>(json);
|
|
// four is populated or nullopt
|
|
```
|
|
|
|
## Key lookup (GetValueForKey)
|
|
|
|
`GetValueForKey` follows the same rules as `GetValue`, but takes an additional key.
|
|
It is assumed that the JSON value passed to GetValueForKey is of `object` type.
|
|
|
|
```c++
|
|
std::string one;
|
|
std::optional<std::string> two;
|
|
|
|
JsonUtils::GetValueForKey(json, "firstKey", one);
|
|
// one is populated or unchanged.
|
|
|
|
JsonUtils::GetValueForKey(json, "secondKey", two);
|
|
// two is populated, nullopt or unchanged
|
|
|
|
auto three = JsonUtils::GetValueForKey<std::string>(json, "thirdKey");
|
|
// three is populated or zero-initialized
|
|
|
|
auto four = JsonUtils::GetValueForKey<std::optional<std::string>>(json, "fourthKey");
|
|
// four is populated or nullopt
|
|
```
|
|
|
|
## Rationale: Value-Returning Getters
|
|
|
|
JsonUtils provides two types of `GetValue...`: value-returning and reference-filling.
|
|
|
|
The reference-filling fixtures use type deduction so that a developer does not
|
|
need to specify template parameters on every `GetValue` call. It excels at
|
|
populating class members during deserialization.
|
|
|
|
The value-returning fixtures, on the other hand, are very useful for partial
|
|
deserialization and key detection when you do not need to deserialize an entire
|
|
instance of a class or you need to reason about the presence of members.
|
|
|
|
To provide a concrete example of the latter, consider:
|
|
|
|
```c++
|
|
if (const auto guid{ GetValueForKey<std::optional<GUID>>(json, "guid") })
|
|
// This condition is only true if there was a "guid" member in the provided JSON object.
|
|
// It can be accessed through *guid.
|
|
}
|
|
```
|
|
|
|
If you are... | Use
|
|
--------------|-----
|
|
Deserializing | `GetValue(..., storage)`
|
|
Interrogating | `storage = GetValue<T>(...)`
|
|
|
|
## Converting User-Defined Types
|
|
|
|
All conversions are done using specializations of
|
|
`JsonUtils::ConversionTrait<T>`. To implement a converter for a user-defined
|
|
type, you must implement a specialization of `JsonUtils::ConversionTrait<T>`.
|
|
|
|
Every specialization over `T` must implement `static T FromJson(const Json::Value&)`
|
|
and `static bool CanConvert(const Json::Value&)`.
|
|
|
|
```c++
|
|
struct MyCustomType { int val; };
|
|
|
|
template<>
|
|
struct ConversionTrait<MyCustomType>
|
|
{
|
|
// This trait converts a string of the format "[0-9]" to a value of type MyCustomType.
|
|
|
|
static MyCustomType FromJson(const Json::Value& json)
|
|
{
|
|
return MyCustomType{ json.asString()[0] - '0' };
|
|
}
|
|
|
|
static bool CanConvert(const Json::Value& json)
|
|
{
|
|
return json.isString();
|
|
}
|
|
};
|
|
```
|
|
|
|
### Converting User-Defined Enumerations
|
|
|
|
Enumeration types represent a single choice out of multiple options.
|
|
|
|
In a JSON data model, they are typically represented as strings.
|
|
|
|
For parsing enumerations, JsonUtils provides the `JSON_ENUM_MAPPER` macro. It
|
|
can be used to establish a converter that will take a set of known strings and
|
|
convert them to values.
|
|
|
|
```c++
|
|
JSON_ENUM_MAPPER(CursorStyle)
|
|
{
|
|
// pair_type is provided by ENUM_MAPPER.
|
|
JSON_MAPPINGS(5) = {
|
|
pair_type{ "bar", CursorStyle::Bar },
|
|
pair_type{ "vintage", CursorStyle::Vintage },
|
|
pair_type{ "underscore", CursorStyle::Underscore },
|
|
pair_type{ "filledBox", CursorStyle::FilledBox },
|
|
pair_type{ "emptyBox", CursorStyle::EmptyBox }
|
|
};
|
|
};
|
|
```
|
|
|
|
If the enum mapper fails to convert the provided string, it will throw an
|
|
exception.
|
|
|
|
### Converting User-Defined Flag Sets
|
|
|
|
Flags represent a multiple-choice selection. They are typically implemented as
|
|
enums with bitfield values intended to be ORed together.
|
|
|
|
In JSON, a set of flags may be represented by a single string (`"flagName"`) or
|
|
an array of strings (`["flagOne", "flagTwo"]`).
|
|
|
|
JsonUtils provides a `JSON_FLAG_MAPPER` macro that can be used to produce a
|
|
specialization for a set of flags.
|
|
|
|
Given the following flag enum,
|
|
|
|
```c++
|
|
enum class JsonTestFlags : int
|
|
{
|
|
FlagOne = 1 << 0,
|
|
FlagTwo = 1 << 1
|
|
};
|
|
```
|
|
|
|
You can register a flag mapper with the `JSON_FLAG_MAPPER` macro as follows:
|
|
|
|
```c++
|
|
JSON_FLAG_MAPPER(JsonTestFlags)
|
|
{
|
|
JSON_MAPPINGS(2) = {
|
|
pair_type{ "flagOne", JsonTestFlags::FlagOne },
|
|
pair_type{ "flagTwo", JsonTestFlags::FlagTwo },
|
|
};
|
|
};
|
|
```
|
|
|
|
The `FLAG_MAPPER` also provides two convenience definitions, `AllSet` and
|
|
`AllClear`, that can be used to represent "all choices" and "no choices"
|
|
respectively.
|
|
|
|
```c++
|
|
JSON_FLAG_MAPPER(JsonTestFlags)
|
|
{
|
|
JSON_MAPPINGS(4) = {
|
|
pair_type{ "never", AllClear },
|
|
pair_type{ "flagOne", JsonTestFlags::FlagOne },
|
|
pair_type{ "flagTwo", JsonTestFlags::FlagTwo },
|
|
pair_type{ "always", AllSet },
|
|
};
|
|
};
|
|
```
|
|
|
|
Because flag values are additive, `["always", "flagOne"]` will result in the
|
|
same behavior as `"always"`.
|
|
|
|
If the flag mapper encounters an unknown flag, it will throw an exception.
|
|
|
|
If the flag mapper encounters a logical discontinuity such as `["never", "flagOne"]`
|
|
(as in the above example), it will throw an exception.
|
|
|
|
### Advanced Use
|
|
|
|
`GetValue` and `GetValueForKey` can be passed, as their final arguments, any
|
|
value whose type implements the same interface as `ConversionTrait<T>`--that
|
|
is, `FromJson(const Json::Value&)` and `CanConvert(const Json::Value&)`.
|
|
|
|
This allows for one-off conversions without a specialization of
|
|
`ConversionTrait` or even stateful converters.
|
|
|
|
#### Stateful Converter Sample
|
|
|
|
```c++
|
|
struct MultiplyingConverter {
|
|
int BaseValue;
|
|
|
|
bool CanConvert(const Json::Value&) { return true; }
|
|
|
|
int FromJson(const Json::Value& value)
|
|
{
|
|
return value.asInt() * BaseValue;
|
|
}
|
|
};
|
|
|
|
...
|
|
|
|
Json::Value json{ 66 }; // A JSON value containing the number 66
|
|
MultiplyingConverter conv{ 10 };
|
|
|
|
auto v = JsonUtils::GetValue<int>(json, conv);
|
|
// v is equal to 660.
|
|
```
|
|
|
|
## Behavior Chart
|
|
|
|
### GetValue(T&) (type-deducing)
|
|
|
|
-|json type invalid|json null|valid
|
|
-|-|-|-
|
|
`T`|❌ exception|❌ exception|✔ converted
|
|
`std::optional<T>`|❌ exception|🟨 `nullopt`|✔ converted
|
|
|
|
### GetValue<T>() (returning)
|
|
|
|
-|json type invalid|json null|valid
|
|
-|-|-|-
|
|
`T`|❌ exception|❌ exception|✔ converted
|
|
`std::optional<T>`|❌ exception|🟨 `nullopt`|✔ converted
|
|
|
|
### GetValueForKey(T&) (type-deducing)
|
|
|
|
GetValueForKey builds on the behavior set from GetValue by adding
|
|
a "key not found" state. The remaining three cases are the same.
|
|
|
|
val type|key not found|_json type invalid_|_json null_|_valid_
|
|
-|-|-|-|-
|
|
`T`|🔵 unchanged|_❌ exception_|_❌ exception_|_✔ converted_
|
|
`std::optional<T>`|🔵 unchanged|_❌ exception_|_🟨 `nullopt`_|_✔ converted_
|
|
|
|
### GetValueForKey<T>() (return value)
|
|
|
|
val type|key not found|_json type invalid_|_json null_|_valid_
|
|
-|-|-|-|-
|
|
`T`|🟨 `T{}` (zero value)|_❌ exception_|_❌ exception_|_✔ converted_
|
|
`std::optional<T>`|🟨 `nullopt`|_❌ exception_|_🟨 `nullopt`_|_✔ converted_
|
|
|
|
### Future Direction
|
|
|
|
These converters lend themselves very well to automatic _serialization_.
|