Newer
Older
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2007 ISIS Rutherford Appleton Laboratory UKRI,
// NScD Oak Ridge National Laboratory, European Spallation Source
// & Institut Laue - Langevin
// SPDX - License - Identifier: GPL - 3.0 +
#include "MantidKernel/PropertyWithValueJSONDecoder.h"
#include "MantidKernel/ArrayProperty.h"
#include <map>
using Json::StreamWriterBuilder;
using Json::Value;
using Json::writeString;
namespace Mantid {
namespace Kernel {
namespace {
// A pointer to a member function for doing the Json::Value->C++ type conversion
template <typename T> using ValueAsTypeMemFn = T (Json::Value::*)() const;
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// A struct mapping a Json::Value to a property. Follows the
// Runtime concept idiom to store a templated type internally
struct FromJson {
template <typename T>
FromJson(ValueAsTypeMemFn<T> asFn) noexcept
: m_self{std::make_unique<ModelT<T>>(std::move(asFn))} {}
std::unique_ptr<Property> createProperty(const std::string &name,
const Json::Value &value,
bool createArray) const {
if (createArray)
return m_self->arrayValueProperty(name, value);
else
return m_self->singleValueProperty(name, value);
}
private:
struct ConceptT {
virtual ~ConceptT() = default;
virtual std::unique_ptr<Property>
singleValueProperty(const std::string &name,
const Json::Value &value) const = 0;
virtual std::unique_ptr<Property>
arrayValueProperty(const std::string &name,
const Json::Value &value) const = 0;
};
template <typename T> struct ModelT : ConceptT {
ModelT(ValueAsTypeMemFn<T> asFn) noexcept : m_asFn{std::move(asFn)} {}
std::unique_ptr<Property>
singleValueProperty(const std::string &name,
const Json::Value &value) const override {
return std::make_unique<PropertyWithValue<T>>(name, (value.*m_asFn)());
}
std::unique_ptr<Property>
arrayValueProperty(const std::string &name,
const Json::Value &value) const override {
std::vector<T> arrayValue;
arrayValue.reserve(value.size());
for (const auto &elem : value) {
try {
arrayValue.emplace_back((elem.*m_asFn)());
} catch (Json::Exception &exc) {
throw std::invalid_argument(
"Mixed-type JSON array values not supported:" +
std::string(exc.what()));
}
}
return std::make_unique<ArrayProperty<T>>(name, std::move(arrayValue));
}
private:
ValueAsTypeMemFn<T> m_asFn;
};
std::unique_ptr<ConceptT> m_self;
};
// Define a lookup mapping Json::ValueTypes to a FromJson able to
// create a PropertyWithValue object from that type
using FromJsonConverters = std::map<Json::ValueType, FromJson>;
// Returns (and creates on first call) the map of Json::ValueType to
// FromJson for converting a JsonValue to a Property object
const FromJsonConverters &converters() {
static FromJsonConverters converters;
if (converters.empty()) {
using namespace std::placeholders;
// Build a map of Json types to fromJson functions of the appropriate type
converters.insert(
std::make_pair(Json::booleanValue, FromJson(&Json::Value::asBool)));
converters.insert(
std::make_pair(Json::intValue, FromJson(&Json::Value::asInt)));
converters.insert(
std::make_pair(Json::realValue, FromJson(&Json::Value::asDouble)));
converters.insert(
std::make_pair(Json::stringValue, FromJson(&Json::Value::asString)));
}
return converters;
}
/**
* @brief Create a PropertyWithValue object from the given Json::Value
* @param name The name of the new property
* @param value The value as Json::Value. For an array is guaranteed to have at
* least 1 element
* @param createArray If true creates an ArrayProperty
* @return A pointer to a new Property object
* @throws std::invalid_argument if the type of the Json::Value is not known
*/
std::unique_ptr<Property> createProperty(const std::string &name,
const Json::Value &value) {
const auto isArray{value.isArray()};
FromJsonConverters::const_iterator conversionFnIter;
// For an array use the first element as the type checker and the rest must
// be convertible
if (isArray)
conversionFnIter = converters().find(value[0].type());
else
conversionFnIter = converters().find(value.type());
if (conversionFnIter == converters().end()) {
throw std::invalid_argument("Cannot create property with name " + name +
". Unable to find converter "
"for Json::ValueType to C++ "
"type");
return conversionFnIter->second.createProperty(name, value, value.isArray());
}
} // namespace
/**
* @param value A value as a Json serialized quantity
* @return A pointer to a new Property if the underlying value can
* be converted to a known C++ type
* @throws std::invalid_argument If the value cannot be transformed to
* a Property object
*/
std::unique_ptr<Property> decode(const Json::Value &value) {
if (!value.isObject() || value.size() != 1) {
StreamWriterBuilder wbuilder;
throw std::invalid_argument(
"Expected Json::Value with a single member. Found " +
writeString(wbuilder, value));
}
Value::Members members(value.getMemberNames());
const auto &name = members.front();
return createProperty(name, value[name]);
}
} // namespace Kernel
} // namespace Mantid