Mercurial > code
changeset 635:002c13ee0021
json_util: create parser class
author | David Demelier <markand@malikania.fr> |
---|---|
date | Mon, 26 Mar 2018 13:26:31 +0200 |
parents | 005794d9cf77 |
children | f36b978962fb |
files | cpp/json_util/json_util.hpp cpp/json_util/test/main.cpp |
diffstat | 2 files changed, 272 insertions(+), 200 deletions(-) [+] |
line wrap: on
line diff
--- a/cpp/json_util/json_util.hpp Fri Mar 23 12:56:47 2018 +0100 +++ b/cpp/json_util/json_util.hpp Mon Mar 26 13:26:31 2018 +0200 @@ -25,7 +25,9 @@ */ #include <cstdint> +#include <limits> #include <string> +#include <type_traits> #include <boost/optional.hpp> @@ -37,214 +39,282 @@ namespace json_util { /** - * Get a JSON value from the given object or array. - * - * \param json the JSON object/array - * \param key the pointer to the object - * \return the value or boost::none if not found + * \cond JSON_UTIL_HIDDEN_SYMBOLS */ -inline boost::optional<nlohmann::json> get(const nlohmann::json& json, - const nlohmann::json::json_pointer& key) noexcept -{ - // Unfortunately, there is no find using pointer yet. - try { - return json.at(key); - } catch (...) { - return boost::none; + +namespace detail { + +template <typename Int> +class parser_type_traits_uint : public std::true_type { +public: + static boost::optional<Int> get(const nlohmann::json& value) noexcept + { + if (!value.is_number_unsigned()) + return boost::none; + + const auto ret = value.get<std::uint64_t>(); + + if (ret > std::numeric_limits<Int>::max()) + return boost::none; + + return static_cast<Int>(ret); } -} +}; + +template <typename Int> +class parser_type_traits_int : public std::true_type { +public: + static boost::optional<Int> get(const nlohmann::json& value) noexcept + { + if (!value.is_number_integer()) + return boost::none; + + const auto ret = value.get<std::int64_t>(); + + if (ret < std::numeric_limits<Int>::min() || ret > std::numeric_limits<Int>::max()) + return boost::none; + + return static_cast<Int>(ret); + } +}; + +} // !detail /** - * Convenient overload with simple key. - * - * \param json the JSON object/array - * \param key the pointer to the object - * \return the value or boost::none if not found + * \endcond */ -inline boost::optional<nlohmann::json> get(const nlohmann::json& json, - const std::string& key) noexcept -{ - const auto it = json.find(key); - - if (it == json.end()) - return boost::none; - - return *it; -} /** - * Get a bool or null if not found or invalid. + * \brief Describe how to convert a JSON value. + * + * This class must be specialized for every type you want to convert from JSON + * to its native type. + * + * You only need to implement the get function with the following signature: * - * \param json the JSON object/array - * \param key the pointer or property key - * \return the value or boost::none if not found or invalid + * ```cpp + * static boost::optional<T> get(const nlohmann::json& value); + * ``` + * + * The implementation should not throw an exception but return a null optional + * instead. + * + * This class is already specialized for the given types: + * + * - bool + * - double + * - std::uint(8, 16, 32, 64) + * - std::string */ -template <typename Key> -inline boost::optional<bool> get_bool(const nlohmann::json& json, const Key& key) noexcept -{ - const auto v = get(json, key); - - if (!v || !v->is_boolean()) - return boost::none; - - return v->template get<bool>(); -} +template <typename T> +class parser_type_traits : public std::false_type { +}; /** - * Get a 64 bit signed integer or null if not found or invalid. - * - * \param json the JSON object/array - * \param key the pointer or property key - * \return the value or boost::none if not found or invalid + * \brief Specialization for `bool`. */ -template <typename Key> -inline boost::optional<std::int64_t> get_int(const nlohmann::json& json, const Key& key) noexcept -{ - const auto v = get(json, key); +template <> +class parser_type_traits<bool> : public std::true_type { +public: + /** + * Convert the JSON value to bool. + * + * \return the bool or none if not a boolean type + */ + static boost::optional<bool> get(const nlohmann::json& value) noexcept + { + if (!value.is_boolean()) + return boost::none; - if (!v || !v->is_number_integer()) - return boost::none; - - return v->template get<std::int64_t>(); -} + return value.get<bool>(); + } +}; /** - * Get a 64 bit unsigned integer or null if not found or invalid. - * - * \param json the JSON object/array - * \param key the pointer or property key - * \return the value or boost::none if not found or invalid + * \brief Specialization for `double`. */ -template <typename Key> -inline boost::optional<std::uint64_t> get_uint(const nlohmann::json& json, const Key& key) noexcept -{ - const auto v = get(json, key); +template <> +class parser_type_traits<double> : public std::true_type { +public: + /** + * Convert the JSON value to bool. + * + * \return the double or none if not a double type + */ + static boost::optional<double> get(const nlohmann::json& value) noexcept + { + if (!value.is_number_float()) + return boost::none; + + return value.get<double>(); + } +}; - if (!v || !v->is_number_unsigned()) - return boost::none; +/** + * \brief Specialization for `std::string`. + */ +template <> +class parser_type_traits<std::string> : public std::true_type { +public: + /** + * Convert the JSON value to bool. + * + * \return the string or none if not a string type + */ + static boost::optional<std::string> get(const nlohmann::json& value) + { + if (!value.is_string()) + return boost::none; - return v->template get<std::uint64_t>(); -} + return value.get<std::string>(); + } +}; /** - * Get a string or null if not found or invalid. - * - * \param json the JSON object/array - * \param key the pointer or property key - * \return the value or boost::none if not found or invalid + * \brief Specialization for `std::int8_t`. + */ +template <> +class parser_type_traits<std::int8_t> : public detail::parser_type_traits_int<std::int8_t> { +}; + +/** + * \brief Specialization for `std::int16_t`. */ -template <typename Key> -inline boost::optional<std::string> get_string(const nlohmann::json& json, const Key& key) noexcept -{ - const auto v = get(json, key); +template <> +class parser_type_traits<std::int16_t> : public detail::parser_type_traits_int<std::int16_t> { +}; - if (!v || !v->is_string()) - return boost::none; - - return v->template get<std::string>(); -} +/** + * \brief Specialization for `std::int32_t`. + */ +template <> +class parser_type_traits<std::int32_t> : public detail::parser_type_traits_int<std::int32_t> { +}; /** - * Get an optional bool. - * - * If the property is not found, return default value. If the property is not - * a bool, return boost::none, otherwise return the value. - * - * \param json the JSON object/array - * \param key the pointer or property key - * \param def the default value - * \return the value, boost::none or def + * \brief Specialization for `std::int64_t`. */ -template <typename Key> -inline boost::optional<bool> optional_bool(const nlohmann::json& json, const Key& key, bool def = false) noexcept -{ - const auto v = get(json, key); +template <> +class parser_type_traits<std::int64_t> : public std::true_type { +public: + /** + * Convert the JSON value to std::int64_t. + * + * \return the int or none if not a int type + */ + static boost::optional<std::int64_t> get(const nlohmann::json& value) noexcept + { + if (!value.is_number_integer()) + return boost::none; + + return value.get<std::int64_t>(); + } +}; - if (!v) - return def; - if (!v->is_boolean()) - return boost::none; +/** + * \brief Specialization for `std::int8_t`. + */ +template <> +class parser_type_traits<std::uint8_t> : public detail::parser_type_traits_uint<std::uint8_t> { +}; - return v->template get<bool>(); -} +/** + * \brief Specialization for `std::int16_t`. + */ +template <> +class parser_type_traits<std::uint16_t> : public detail::parser_type_traits_uint<std::uint16_t> { +}; + +/** + * \brief Specialization for `std::int32_t`. + */ +template <> +class parser_type_traits<std::uint32_t> : public detail::parser_type_traits_uint<std::uint32_t> { +}; /** - * Get an optional integer. - * - * If the property is not found, return default value. If the property is not - * an integer, return boost::none, otherwise return the value. - * - * \param json the JSON object/array - * \param key the pointer or property key - * \param def the default value - * \return the value, boost::none or def + * \brief Specialization for `std::int64_t`. */ -template <typename Key> -inline boost::optional<std::int64_t> optional_int(const nlohmann::json& json, - const Key& key, - std::int64_t def = 0) noexcept -{ - const auto v = get(json, key); +template <> +class parser_type_traits<std::uint64_t> : public std::true_type { +public: + /** + * Convert the JSON value to std::uint64_t. + * + * \return the int or none if not a int type + */ + static boost::optional<std::uint64_t> get(const nlohmann::json& value) noexcept + { + if (!value.is_number_unsigned()) + return boost::none; - if (!v) - return def; - if (!v->is_number_integer()) - return boost::none; - - return v->template get<std::int64_t>(); -} + return value.get<std::uint64_t>(); + } +}; /** - * Get an optional unsigned integer. - * - * If the property is not found, return default value. If the property is not - * an unsigned integer, return boost::none, otherwise return the value. + * \brief Convenient JSON object parser * - * \param json the JSON object/array - * \param key the pointer or property key - * \param def the default value - * \return the value, boost::none or def + * This class helps destructuring insecure JSON input by returning optional + * values if they are not present or invalid. */ -template <typename Key> -inline boost::optional<std::uint64_t> optional_uint(const nlohmann::json& json, - const Key& key, - std::uint64_t def = 0) noexcept -{ - const auto v = get(json, key); +class parser { +private: + nlohmann::json& json_; - if (!v) - return def; - if (!v->is_number_unsigned()) - return boost::none; +public: + /** + * Construct the parser. + * + * \param document the document + */ + inline parser(nlohmann::json& document) noexcept + : json_(document) + { + } - return v->template get<std::uint64_t>(); -} + /** + * Get a value from the document object. + * + * \param key the property key + * \return the value or boost::none if not found + */ + template <typename Type> + inline boost::optional<Type> get(const std::string& key) const noexcept + { + static_assert(parser_type_traits<Type>::value, "type not supported"); -/** - * Get an optional string. - * - * If the property is not found, return default value. If the property is not - * a string, return boost::none, otherwise return the value. - * - * \param json the JSON object/array - * \param key the pointer or property key - * \param def the default value - * \return the value, boost::none or def - */ -template <typename Key> -inline boost::optional<std::string> optional_string(const nlohmann::json& json, - const Key& key, - const std::string& def = "") noexcept -{ - const auto v = get(json, key); + const auto it = json_.find(key); + + if (it == json_.end()) + return boost::none; + + return parser_type_traits<Type>::get(*it); + } - if (!v) - return def; - if (!v->is_string()) - return boost::none; + /** + * Get an optional value from the document object. + * + * If the value is undefined, the default value is returned. Otherwise, if + * the value is not in the given type, boost::none is returned. + * + * \param key the property key + * \param def the default value if property is undefined + * \return the value, boost::none or def + */ + template <typename Type> + inline boost::optional<Type> optional(const std::string& key, Type&& def) const noexcept + { + static_assert(parser_type_traits<Type>::value, "type not supported"); - return v->template get<std::string>(); -} + const auto it = json_.find(key); + + if (it == json_.end()) + return def; + + return parser_type_traits<Type>::get(*it); + } +}; /** * Print the value as human readable.
--- a/cpp/json_util/test/main.cpp Fri Mar 23 12:56:47 2018 +0100 +++ b/cpp/json_util/test/main.cpp Mon Mar 26 13:26:31 2018 +0200 @@ -32,6 +32,8 @@ { "uint", 1234U }, { "string", "str" } }; + + parser parser_{json_}; }; BOOST_FIXTURE_TEST_SUITE(test_fixture_suite, test_fixture) @@ -40,46 +42,46 @@ BOOST_AUTO_TEST_CASE(boolean) { - const auto v = get_bool(json_, "/boolean"_json_pointer); + const auto v = parser_.get<bool>("boolean"); BOOST_TEST(v); BOOST_TEST(*v); - BOOST_TEST(!get_bool(json_, "/int"_json_pointer)); - BOOST_TEST(!get_bool(json_, "/int"_json_pointer)); - BOOST_TEST(!get_bool(json_, "/uint"_json_pointer)); - BOOST_TEST(!get_bool(json_, "/string"_json_pointer)); + BOOST_TEST(!parser_.get<bool>("int")); + BOOST_TEST(!parser_.get<bool>("int")); + BOOST_TEST(!parser_.get<bool>("uint")); + BOOST_TEST(!parser_.get<bool>("string")); } BOOST_AUTO_TEST_CASE(integer) { - const auto v = get_int(json_, "/int"_json_pointer); + const auto v = parser_.get<int>("int"); BOOST_TEST(v); BOOST_TEST(*v == -1234); - BOOST_TEST(!get_int(json_, "/bool"_json_pointer)); - BOOST_TEST(!get_int(json_, "/string"_json_pointer)); + BOOST_TEST(!parser_.get<int>("bool")); + BOOST_TEST(!parser_.get<int>("string")); } BOOST_AUTO_TEST_CASE(unsigned_integer) { - const auto v = get_uint(json_, "/uint"_json_pointer); + const auto v = parser_.get<unsigned>("uint"); BOOST_TEST(v); BOOST_TEST(*v == 1234U); - BOOST_TEST(!get_uint(json_, "/bool"_json_pointer)); - BOOST_TEST(!get_uint(json_, "/int"_json_pointer)); - BOOST_TEST(!get_uint(json_, "/string"_json_pointer)); + BOOST_TEST(!parser_.get<unsigned>("bool")); + BOOST_TEST(!parser_.get<unsigned>("int")); + BOOST_TEST(!parser_.get<unsigned>("string")); } BOOST_AUTO_TEST_CASE(string) { - const auto v = get_string(json_, "/string"_json_pointer); + const auto v = parser_.get<std::string>("string"); BOOST_TEST(v); BOOST_TEST(*v == "str"); - BOOST_TEST(!get_string(json_, "/bool"_json_pointer)); - BOOST_TEST(!get_string(json_, "/int"_json_pointer)); - BOOST_TEST(!get_string(json_, "/uint"_json_pointer)); + BOOST_TEST(!parser_.get<std::string>("bool")); + BOOST_TEST(!parser_.get<std::string>("int")); + BOOST_TEST(!parser_.get<std::string>("uint")); } BOOST_AUTO_TEST_SUITE_END() @@ -88,23 +90,23 @@ BOOST_AUTO_TEST_CASE(boolean) { - BOOST_TEST(*optional_bool(json_, "/undefined"_json_pointer, true)); - BOOST_TEST(!*optional_bool(json_, "/undefined"_json_pointer, false)); + BOOST_TEST(*parser_.optional<bool>("undefined", true)); + BOOST_TEST(!*parser_.optional<bool>("undefined", false)); } BOOST_AUTO_TEST_CASE(integer) { - BOOST_TEST(*optional_int(json_, "/undefined"_json_pointer, 1234) == 1234); + BOOST_TEST(*parser_.optional<int>("undefined", 1234) == 1234); } BOOST_AUTO_TEST_CASE(unsigned_integer) { - BOOST_TEST(*optional_uint(json_, "/undefined"_json_pointer, 1234U) == 1234U); + BOOST_TEST(*parser_.optional<unsigned>("undefined", 1234U) == 1234U); } BOOST_AUTO_TEST_CASE(string) { - BOOST_TEST(*optional_string(json_, "/undefined"_json_pointer, "foo") == "foo"); + BOOST_TEST(*parser_.optional<std::string>("undefined", "foo") == "foo"); } BOOST_AUTO_TEST_SUITE_END() @@ -113,29 +115,29 @@ BOOST_AUTO_TEST_CASE(boolean) { - BOOST_TEST(!optional_bool(json_, "/int"_json_pointer, true)); - BOOST_TEST(!optional_bool(json_, "/uint"_json_pointer, true)); - BOOST_TEST(!optional_bool(json_, "/string"_json_pointer, true)); + BOOST_TEST(!parser_.optional<bool>("int", true)); + BOOST_TEST(!parser_.optional<bool>("uint", true)); + BOOST_TEST(!parser_.optional<bool>("string", true)); } BOOST_AUTO_TEST_CASE(integer) { - BOOST_TEST(!optional_int(json_, "/boolean"_json_pointer, 1234)); - BOOST_TEST(!optional_int(json_, "/string"_json_pointer, 1234)); + BOOST_TEST(!parser_.optional<int>("boolean", 1234)); + BOOST_TEST(!parser_.optional<int>("string", 1234)); } BOOST_AUTO_TEST_CASE(unsigned_integer) { - BOOST_TEST(!optional_uint(json_, "/boolean"_json_pointer, 1234)); - BOOST_TEST(!optional_uint(json_, "/int"_json_pointer, 1234)); - BOOST_TEST(!optional_uint(json_, "/string"_json_pointer, 1234)); + BOOST_TEST(!parser_.optional<unsigned>("boolean", 1234)); + BOOST_TEST(!parser_.optional<unsigned>("int", 1234)); + BOOST_TEST(!parser_.optional<unsigned>("string", 1234)); } BOOST_AUTO_TEST_CASE(string) { - BOOST_TEST(!optional_string(json_, "/boolean"_json_pointer, "foo")); - BOOST_TEST(!optional_string(json_, "/int"_json_pointer, "foo")); - BOOST_TEST(!optional_string(json_, "/uint"_json_pointer, "foo")); + BOOST_TEST(!parser_.optional<std::string>("boolean", "foo")); + BOOST_TEST(!parser_.optional<std::string>("int", "foo")); + BOOST_TEST(!parser_.optional<std::string>("uint", "foo")); } BOOST_AUTO_TEST_SUITE_END()