diff --git a/include/valijson/adapters/poco_json_adapter.hpp b/include/valijson/adapters/poco_json_adapter.hpp new file mode 100644 index 0000000..915befc --- /dev/null +++ b/include/valijson/adapters/poco_json_adapter.hpp @@ -0,0 +1,728 @@ +/** +* @file +* +* @brief Adapter implementation for the Poco json parser library. +* +* Include this file in your program to enable support for Poco json. +* +* This file defines the following classes (not in this order): +* - PocoJsonAdapter +* - PocoJsonArray +* - PocoJsonValueIterator +* - PocoJsonFrozenValue +* - PocoJsonObject +* - PocoJsonObjectMember +* - PocoJsonObjectMemberIterator +* - PocoJsonValue +* +* Due to the dependencies that exist between these classes, the ordering of +* class declarations and definitions may be a bit confusing. The best place to +* start is PocoJsonAdapter. This class definition is actually very small, +* since most of the functionality is inherited from the BasicAdapter class. +* Most of the classes in this file are provided as template arguments to the +* inherited BasicAdapter class. +*/ + +#pragma once +#ifndef __VALIJSON_ADAPTERS_POCO_JSON_ADAPTER_HPP +#define __VALIJSON_ADAPTERS_POCO_JSON_ADAPTER_HPP + +#include +#include + +#include +#include +#include + +namespace valijson +{ + namespace adapters + { + + class PocoJsonAdapter; + class PocoJsonArrayValueIterator; + class PocoJsonObjectMemberIterator; + + typedef std::pair PocoJsonObjectMember; + + /** + * @brief Light weight wrapper for a PocoJson array value. + * + * This class is light weight wrapper for a PocoJson array. It provides a + * minimum set of container functions and typedefs that allow it to be used as + * an iterable container. + * + * An instance of this class contains a single reference to the underlying + * PocoJson value, assumed to be an array, so there is very little overhead + * associated with copy construction and passing by value. + */ + class PocoJsonArray + { + public: + + typedef PocoJsonArrayValueIterator const_iterator; + typedef PocoJsonArrayValueIterator iterator; + + /// Construct a PocoJsonArray referencing an empty array. + PocoJsonArray() + : value(emptyArray()) + { } + + /** + * @brief Construct a PocoJsonArray referencing a specific PocoJson + * value. + * + * @param value reference to a PocoJson value + * + * Note that this constructor will throw an exception if the value is not + * an array. + */ + PocoJsonArray(const Poco::Dynamic::Var &value) + : value(value) + { + if (value.type() != typeid(Poco::JSON::Array::Ptr)) { + throw std::runtime_error("Value is not an array."); + } + } + + /** + * @brief Return an iterator for the first element of the array. + * + * The iterator return by this function is effectively the iterator + * returned by the underlying PocoJson implementation. + */ + PocoJsonArrayValueIterator begin() const; + + /** + * @brief Return an iterator for one-past the last element of the array. + * + * The iterator return by this function is effectively the iterator + * returned by the underlying PocoJson implementation. + */ + PocoJsonArrayValueIterator end() const; + + /// Return the number of elements in the array + size_t size() const + { + return value.extract()->size(); + } + + private: + + /** + * @brief Return a PocoJson value that is an empty array. + */ + static Poco::Dynamic::Var emptyArray() + { + Poco::Dynamic::Var array = Poco::JSON::Array::Ptr(); + return array; + } + + /// Contained value + Poco::Dynamic::Var value; + }; + + /** + * @brief Light weight wrapper for a PocoJson object. + * + * This class is light weight wrapper for a PocoJson object. It provides a + * minimum set of container functions and typedefs that allow it to be used as + * an iterable container. + * + * An instance of this class contains a single reference to the underlying + * PocoJson value, assumed to be an object, so there is very little overhead + * associated with copy construction and passing by value. + */ + class PocoJsonObject + { + public: + + typedef PocoJsonObjectMemberIterator const_iterator; + typedef PocoJsonObjectMemberIterator iterator; + + /// Construct a PocoJsonObject an empty object. + PocoJsonObject() + : value(emptyObject()) + { } + + /** + * @brief Construct a PocoJsonObject referencing a specific PocoJson + * value. + * + * @param value reference to a PocoJson value + * + * Note that this constructor will throw an exception if the value is not + * an object. + */ + PocoJsonObject(const Poco::Dynamic::Var &value) + : value(value) + { + if (value.type() != typeid(Poco::JSON::Object::Ptr)) { + throw std::runtime_error("Value is not an object."); + } + } + + /** + * @brief Return an iterator for this first object member + * + * The iterator return by this function is effectively a wrapper around + * the iterator value returned by the underlying PocoJson implementation. + */ + PocoJsonObjectMemberIterator begin() const; + + /** + * @brief Return an iterator for an invalid object member that indicates + * the end of the collection. + * + * The iterator return by this function is effectively a wrapper around + * the iterator value returned by the underlying PocoJson implementation. + */ + PocoJsonObjectMemberIterator end() const; + + /** + * @brief Return an iterator for the object member with the specified + * property name. + * + * If an object member with the specified name does not exist, the iterator + * returned will be the same as the iterator returned by the end() function. + * + * @param propertyName property name to search for + */ + PocoJsonObjectMemberIterator find(const std::string &propertyName) const; + + /// Returns the number of members belonging to this object. + size_t size() const + { + return value.extract()->size(); + } + + private: + + /** + * @brief Return a PocoJson value that is empty object. + */ + static Poco::Dynamic::Var emptyObject() + { + Poco::Dynamic::Var object = Poco::JSON::Object::Ptr(); + return object; + } + + /// Contained value + Poco::Dynamic::Var value; + }; + + /** + * @brief Stores an independent copy of a PocoJson value. + * + * This class allows a PocoJson value to be stored independent of its original + * document. PocoJson makes this easy to do, as it does not perform any + * custom memory management. + * + * @see FrozenValue + */ + class PocoJsonFrozenValue : public FrozenValue + { + public: + + /** + * @brief Make a copy of a PocoJson value + * + * @param source the PocoJson value to be copied + */ + explicit PocoJsonFrozenValue(const Poco::Dynamic::Var &source) + : value(source) + { } + + virtual FrozenValue * clone() const + { + return new PocoJsonFrozenValue(value); + } + + virtual bool equalTo(const Adapter &other, bool strict) const; + + private: + + /// Stored PocoJson value + Poco::Dynamic::Var value; + }; + + + /** + * @brief Light weight wrapper for a PocoJson value. + * + * This class is passed as an argument to the BasicAdapter template class, + * and is used to provide access to a PocoJson value. This class is responsible + * for the mechanics of actually reading a PocoJson value, whereas the + * BasicAdapter class is responsible for the semantics of type comparisons + * and conversions. + * + * The functions that need to be provided by this class are defined implicitly + * by the implementation of the BasicAdapter template class. + * + * @see BasicAdapter + */ + class PocoJsonValue + { + public: + + /// Construct a wrapper for the empty object + PocoJsonValue() + : value(emptyObject()) + { } + + /// Construct a wrapper for a specific PocoJson value + PocoJsonValue(const Poco::Dynamic::Var& value) + : value(value) + { } + + /** + * @brief Create a new PocoJsonFrozenValue instance that contains the + * value referenced by this PocoJsonValue instance. + * + * @returns pointer to a new PocoJsonFrozenValue instance, belonging to the + * caller. + */ + FrozenValue * freeze() const + { + return new PocoJsonFrozenValue(value); + } + + /** + * @brief Optionally return a PocoJsonArray instance. + * + * If the referenced PocoJson value is an array, this function will return + * a std::optional containing a PocoJsonArray instance referencing the + * array. + * + * Otherwise it will return an empty optional. + */ + opt::optional getArrayOptional() const + { + if (value.type() == typeid(Poco::JSON::Array::Ptr)) { + return opt::make_optional(PocoJsonArray(value)); + } + + return opt::optional(); + } + + /** + * @brief Retrieve the number of elements in the array + * + * If the referenced PocoJson value is an array, this function will + * retrieve the number of elements in the array and store it in the output + * variable provided. + * + * @param result reference to size_t to set with result + * + * @returns true if the number of elements was retrieved, false otherwise. + */ + bool getArraySize(size_t &result) const + { + if (value.type() == typeid(Poco::JSON::Array::Ptr)) { + result = value.extract()->size(); + return true; + } + + return false; + } + + bool getBool(bool &result) const + { + if (value.isBoolean()) { + result = value.convert(); + return true; + } + + return false; + } + + bool getDouble(double &result) const + { + if (value.isNumeric() && !value.isInteger()) { + result = value.convert(); + return true; + } + + return false; + } + + bool getInteger(int64_t &result) const + { + if (value.isInteger()) { + result = value.convert(); + return true; + } + return false; + } + + /** + * @brief Optionally return a PocoJsonObject instance. + * + * If the referenced PocoJson value is an object, this function will return a + * std::optional containing a PocoJsonObject instance referencing the + * object. + * + * Otherwise it will return an empty optional. + */ + opt::optional getObjectOptional() const + { + if (value.type() == typeid(Poco::JSON::Object::Ptr)) { + return opt::make_optional(PocoJsonObject(value)); + } + + return opt::optional(); + } + + /** + * @brief Retrieve the number of members in the object + * + * If the referenced PocoJson value is an object, this function will + * retrieve the number of members in the object and store it in the output + * variable provided. + * + * @param result reference to size_t to set with result + * + * @returns true if the number of members was retrieved, false otherwise. + */ + bool getObjectSize(size_t &result) const + { + if (value.type() == typeid(Poco::JSON::Object::Ptr)) { + result = value.extract()->size(); + return true; + } + + return false; + } + + bool getString(std::string &result) const + { + if (value.isString()) { + result = value.convert(); + return true; + } + + return false; + } + + static bool hasStrictTypes() + { + return true; + } + + bool isArray() const + { + return value.type() == typeid(Poco::JSON::Array::Ptr); + } + + bool isBool() const + { + return value.isBoolean(); + } + + bool isDouble() const + { + return value.isNumeric() && !value.isInteger(); + } + + bool isInteger() const + { + return !isBool() && value.isInteger(); + } + + bool isNull() const + { + return value.isEmpty(); + } + + bool isNumber() const + { + return value.isNumeric(); + } + + bool isObject() const + { + return value.type() == typeid(Poco::JSON::Object::Ptr); + } + + bool isString() const + { + return value.isString(); + } + + private: + + /// Return an empty object + static Poco::Dynamic::Var emptyObject() + { + Poco::Dynamic::Var object = Poco::JSON::Object::Ptr(); + return object; + } + + /// Contained value + Poco::Dynamic::Var value; + }; + + /** + * @brief An implementation of the Adapter interface supporting PocoJson. + * + * This class is defined in terms of the BasicAdapter template class, which + * helps to ensure that all of the Adapter implementations behave consistently. + * + * @see Adapter + * @see BasicAdapter + */ + class PocoJsonAdapter : + public BasicAdapter + { + public: + /// Construct a PocoJsonAdapter that contains an empty object + PocoJsonAdapter() + : BasicAdapter() + { } + + /// Construct a PocoJsonAdapter containing a specific Poco Json object + PocoJsonAdapter(const Poco::Dynamic::Var &value) + : BasicAdapter(PocoJsonValue {value}) + { } + }; + + /** + * @brief Class for iterating over values held in a JSON array. + * + * This class provides a JSON array iterator that dereferences as an instance of + * PocoJsonAdapter representing a value stored in the array. It has been + * implemented using the boost iterator_facade template. + * + * @see PocoJsonArray + */ + class PocoJsonArrayValueIterator : + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + PocoJsonAdapter> // value type + { + public: + + /** + * @brief Construct a new PocoJsonArrayValueIterator using an existing + * PocoJson iterator. + * + * @param itr PocoJson iterator to store + */ + PocoJsonArrayValueIterator(const Poco::JSON::Array::ConstIterator &itr) + : itr(itr) + { } + + /// Returns a PocoJsonAdapter that contains the value of the current + /// element. + PocoJsonAdapter operator*() const + { + return PocoJsonAdapter(*itr); + } + + DerefProxy operator->() const + { + return DerefProxy(**this); + } + + /** + * @brief Compare this iterator against another iterator. + * + * Note that this directly compares the iterators, not the underlying + * values, and assumes that two identical iterators will point to the same + * underlying object. + * + * @param other iterator to compare against + * + * @returns true if the iterators are equal, false otherwise. + */ + bool operator==(const PocoJsonArrayValueIterator &other) const + { + return itr == other.itr; + } + + bool operator!=(const PocoJsonArrayValueIterator &other) const + { + return !(itr == other.itr); + } + + const PocoJsonArrayValueIterator& operator++() + { + itr++; + + return *this; + } + + PocoJsonArrayValueIterator operator++(int) + { + PocoJsonArrayValueIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + const PocoJsonArrayValueIterator& operator--() + { + itr--; + + return *this; + } + + void advance(std::ptrdiff_t n) + { + itr += n; + } + + private: + Poco::JSON::Array::ConstIterator itr; + }; + + + /** + * @brief Class for iterating over the members belonging to a JSON object. + * + * This class provides a JSON object iterator that dereferences as an instance + * of PocoJsonObjectMember representing one of the members of the object. It + * has been implemented using the boost iterator_facade template. + * + * @see PocoJsonObject + * @see PocoJsonObjectMember + */ + class PocoJsonObjectMemberIterator : + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + PocoJsonObjectMember> // value type + { + public: + + /** + * @brief Construct an iterator from a PocoJson iterator. + * + * @param itr PocoJson iterator to store + */ + PocoJsonObjectMemberIterator(const Poco::JSON::Object::ConstIterator &itr) + : itr(itr) + { } + + /** + * @brief Returns a PocoJsonObjectMember that contains the key and value + * belonging to the object member identified by the iterator. + */ + PocoJsonObjectMember operator*() const + { + return PocoJsonObjectMember(itr->first, itr->second); + } + + DerefProxy operator->() const + { + return DerefProxy(**this); + } + + /** + * @brief Compare this iterator with another iterator. + * + * Note that this directly compares the iterators, not the underlying + * values, and assumes that two identical iterators will point to the same + * underlying object. + * + * @param other Iterator to compare with + * + * @returns true if the underlying iterators are equal, false otherwise + */ + bool operator==(const PocoJsonObjectMemberIterator &other) const + { + return itr == other.itr; + } + + bool operator!=(const PocoJsonObjectMemberIterator &other) const + { + return !(itr == other.itr); + } + + const PocoJsonObjectMemberIterator& operator++() + { + itr++; + + return *this; + } + + PocoJsonObjectMemberIterator operator++(int) + { + PocoJsonObjectMemberIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + const PocoJsonObjectMemberIterator& operator--() + { + itr--; + + return *this; + } + + private: + + /// Iternal copy of the original PocoJson iterator + Poco::JSON::Object::ConstIterator itr; + }; + + /// Specialisation of the AdapterTraits template struct for PocoJsonAdapter. + template<> + struct AdapterTraits + { + typedef Poco::Dynamic::Var DocumentType; + + static std::string adapterName() + { + return "PocoJsonAdapter"; + } + }; + + inline PocoJsonArrayValueIterator PocoJsonArray::begin() const + { + return value.extract()->begin(); + } + + inline PocoJsonArrayValueIterator PocoJsonArray::end() const + { + return value.extract()->end(); + } + + inline PocoJsonObjectMemberIterator PocoJsonObject::begin() const + { + return value.extract()->begin(); + } + + inline PocoJsonObjectMemberIterator PocoJsonObject::end() const + { + return value.extract()->end(); + } + + inline PocoJsonObjectMemberIterator PocoJsonObject::find( + const std::string &propertyName) const + { + auto& ptr = value.extract(); + + auto it = std::find_if(ptr->begin(), ptr->end(), + [&propertyName](const Poco::JSON::Object::ValueType& p) + { + return p.first == propertyName; + }); + return it; + } + + inline bool PocoJsonFrozenValue::equalTo(const Adapter &other, bool strict) const + { + return PocoJsonAdapter(value).equalTo(other, strict); + } + + + + + } // namespace adapters +} // namespace valijson + +#endif // __VALIJSON_ADAPTERS_POCO_JSON_ADAPTER_HPP + diff --git a/include/valijson/utils/poco_json_utils.hpp b/include/valijson/utils/poco_json_utils.hpp new file mode 100644 index 0000000..c2be797 --- /dev/null +++ b/include/valijson/utils/poco_json_utils.hpp @@ -0,0 +1,38 @@ +#pragma once +#ifndef VALIJSON_POCO_JSON_UTILS_HPP +#define VALIJSON_POCO_JSON_UTILS_HPP + +#include + +#include +#include +#include + +namespace valijson { +namespace utils { + +inline bool loadDocument(const std::string &path, Poco::Dynamic::Var &document) { + // Load schema JSON from file + std::string file; + if (!loadFile(path, file)) { + std::cerr << "Failed to load json from file '" << path << "'." + << std::endl; + return false; + } + + // Parse schema + try { + document = Poco::JSON::Parser().parse(file); + } catch (std::invalid_argument const& exception) { + std::cerr << "nlohmann::json failed to parse the document\n" + << "Parse error:" << exception.what() << "\n"; + return false; + } + + return true; +} + +} // namespace utils +} // namespace valijson + +#endif //VALIJSON_POCO_JSON_UTILS_HPP diff --git a/tests/test_poco_json_adapter.cpp b/tests/test_poco_json_adapter.cpp new file mode 100644 index 0000000..9d2c17d --- /dev/null +++ b/tests/test_poco_json_adapter.cpp @@ -0,0 +1,89 @@ +#ifdef VALIJSON_BUILD_POCO_ADAPTERS + +#include + +#include + +class TestPocoJsonAdapter : public testing::Test +{ + +}; + +TEST_F(TestPocoJsonAdapter, BasicArrayIteration) +{ + const unsigned int numElements = 10; + + // Create a Json document that consists of an array of numbers + Poco::JSON::Array::Ptr documentImpl = new Poco::JSON::Array(); + + for (unsigned int i = 0; i < numElements; i++) { + documentImpl->set(i, static_cast(i)); + } + + Poco::Dynamic::Var document = documentImpl; + + // Ensure that wrapping the document preserves the array and does not allow + // it to be cast to other types + valijson::adapters::PocoJsonAdapter adapter(document); + ASSERT_NO_THROW( adapter.getArray() ); + ASSERT_ANY_THROW( adapter.getBool() ); + ASSERT_ANY_THROW( adapter.getDouble() ); + ASSERT_ANY_THROW( adapter.getObject() ); + ASSERT_ANY_THROW( adapter.getString() ); + + // Ensure that the array contains the expected number of elements + EXPECT_EQ( numElements, adapter.getArray().size() ); + + // Ensure that the elements are returned in the order they were inserted + unsigned int expectedValue = 0; + for (const valijson::adapters::PocoJsonAdapter value : adapter.getArray()) { + ASSERT_TRUE( value.isNumber() ); + EXPECT_EQ( double(expectedValue), value.getDouble() ); + expectedValue++; + } + + // Ensure that the correct number of elements were iterated over + EXPECT_EQ(numElements, expectedValue); +} + +TEST_F(TestPocoJsonAdapter, BasicObjectIteration) +{ + const unsigned int numElements = 10; + + // Create a DropBoxJson document that consists of an object that maps numeric + // strings their corresponding numeric values + Poco::JSON::Object::Ptr documentImpl = new Poco::JSON::Object; + + for (uint32_t i = 0; i < numElements; i++) { + documentImpl->set(std::to_string(i), static_cast(i)); + } + + Poco::Dynamic::Var document = documentImpl; + + // Ensure that wrapping the document preserves the object and does not + // allow it to be cast to other types + valijson::adapters::PocoJsonAdapter adapter(document); + ASSERT_NO_THROW( adapter.getObject() ); + ASSERT_ANY_THROW( adapter.getArray() ); + ASSERT_ANY_THROW( adapter.getBool() ); + ASSERT_ANY_THROW( adapter.getDouble() ); + ASSERT_ANY_THROW( adapter.getString() ); + + // Ensure that the object contains the expected number of members + EXPECT_EQ( numElements, adapter.getObject().size() ); + + // Ensure that the members are returned in the order they were inserted + unsigned int expectedValue = 0; + for (const valijson::adapters::PocoJsonAdapter::ObjectMember member : adapter.getObject()) { + ASSERT_TRUE( member.second.isNumber() ); + EXPECT_EQ( std::to_string(expectedValue), member.first ); + EXPECT_EQ( double(expectedValue), member.second.getDouble() ); + expectedValue++; + } + + // Ensure that the correct number of elements were iterated over + EXPECT_EQ( numElements, expectedValue ); +} + +#endif // VALIJSON_BUILD_POCO_ADAPTERS + diff --git a/tests/test_validator.cpp b/tests/test_validator.cpp index 7fe9bf2..d417923 100644 --- a/tests/test_validator.cpp +++ b/tests/test_validator.cpp @@ -20,6 +20,11 @@ #include #endif // VALIJSON_BUILD_CXX11_ADAPTERS +#ifdef VALIJSON_BUILD_POCO_ADAPTERS +#include +#include +#endif // VALIJSON_BUILD_POCO_ADAPTERS + #define REMOTES_DIR "../thirdparty/JSON-Schema-Test-Suite/remotes/" #define TEST_SUITE_DIR "../thirdparty/JSON-Schema-Test-Suite/tests/" @@ -170,6 +175,10 @@ protected: #ifdef VALIJSON_BUILD_CXX11_ADAPTERS processTestFile(testFile, version); #endif // VALIJSON_BUILD_CXX11_ADAPTERS + +#ifdef VALIJSON_BUILD_POCO_ADAPTERS + processTestFile(testFile, version); +#endif // VALIJSON_BUILD_POCO_ADAPTERS } void processDraft3TestFile(const std::string &testFile)