From 5199382c91e351c996e2782ec0c884b209f1beb0 Mon Sep 17 00:00:00 2001 From: Niels Date: Sat, 24 Jan 2015 20:33:06 +0100 Subject: [PATCH] worked on issue #8 and #27 --- header_only/json.h | 124 ++++++++++++++++++++++++++++++++++----------- src/json.cc | 108 ++++++++++++++++++++++++++++++--------- src/json.h | 16 ++++-- test/json_unit.cc | 59 ++++++++++++++++++++- 4 files changed, 248 insertions(+), 59 deletions(-) diff --git a/header_only/json.h b/header_only/json.h index 5943cd135..517baea18 100644 --- a/header_only/json.h +++ b/header_only/json.h @@ -140,7 +140,7 @@ class json /// create an object according to given type json(const value_t); /// create a null object - json() = default; + json() noexcept; /// create a null object json(std::nullptr_t) noexcept; /// create a string object from a C++ string @@ -160,7 +160,7 @@ class json /// create an object (move) json(object_t&&); /// create from an initializer list (to an array or object) - json(list_init_t); + json(list_init_t, bool = true, value_t = value_t::array); /*! @brief create a number object (integer) @@ -171,7 +171,7 @@ class json std::numeric_limits::is_integer, T>::type = 0> json(const T n) noexcept - : type_(value_t::number), + : final_type_(0), type_(value_t::number), value_(static_cast(n)) {} @@ -184,7 +184,7 @@ class json std::is_floating_point::value>::type > json(const T n) noexcept - : type_(value_t::number_float), + : final_type_(0), type_(value_t::number_float), value_(static_cast(n)) {} @@ -229,6 +229,11 @@ class json /// destructor ~json() noexcept; + /// explicit keyword to force array creation + static json array(list_init_t = list_init_t()); + /// explicit keyword to force object creation + static json object(list_init_t = list_init_t()); + /// create from string representation static json parse(const std::string&); /// create from string representation @@ -404,9 +409,10 @@ class json const_reverse_iterator crend() const noexcept; private: + /// whether the type is final + unsigned final_type_ : 1; /// the type of this object value_t type_ = value_t::null; - /// the payload value value_ {}; @@ -654,6 +660,9 @@ json::json(const value_t t) } } +json::json() noexcept : final_type_(0), type_(value_t::null) +{} + /*! Construct a null JSON object. */ @@ -666,35 +675,35 @@ Construct a string JSON object. @param s a string to initialize the JSON object with */ json::json(const std::string& s) - : type_(value_t::string), value_(new string_t(s)) + : final_type_(0), type_(value_t::string), value_(new string_t(s)) {} json::json(std::string&& s) - : type_(value_t::string), value_(new string_t(std::move(s))) + : final_type_(0), type_(value_t::string), value_(new string_t(std::move(s))) {} json::json(const char* s) - : type_(value_t::string), value_(new string_t(s)) + : final_type_(0), type_(value_t::string), value_(new string_t(s)) {} json::json(const bool b) noexcept - : type_(value_t::boolean), value_(b) + : final_type_(0), type_(value_t::boolean), value_(b) {} json::json(const array_t& a) - : type_(value_t::array), value_(new array_t(a)) + : final_type_(0), type_(value_t::array), value_(new array_t(a)) {} json::json(array_t&& a) - : type_(value_t::array), value_(new array_t(std::move(a))) + : final_type_(0), type_(value_t::array), value_(new array_t(std::move(a))) {} json::json(const object_t& o) - : type_(value_t::object), value_(new object_t(o)) + : final_type_(0), type_(value_t::object), value_(new object_t(o)) {} json::json(object_t&& o) - : type_(value_t::object), value_(new object_t(std::move(o))) + : final_type_(0), type_(value_t::object), value_(new object_t(std::move(o))) {} /*! @@ -711,33 +720,90 @@ as is to create an array. @bug With the described approach, we would fail to recognize an array whose first element is again an arrays as array. + +@param a an initializer list to create from +@param type_deduction whether the type (array/object) shall eb deducted +@param manual_type if type deduction is switched of, pass a manual type */ -json::json(list_init_t a) +json::json(list_init_t a, bool type_deduction, value_t manual_type) : final_type_(0) { + // the initializer list could describe an object + bool is_object = true; + // check if each element is an array with two elements whose first element // is a string for (const auto& element : a) { - if (element.type_ != value_t::array or - element.size() != 2 or - element[0].type_ != value_t::string) + if ((element.final_type_ == 1 and element.type_ == value_t::array) + or (element.type_ != value_t::array or element.size() != 2 or element[0].type_ != value_t::string)) { - - // the initializer list describes an array - type_ = value_t::array; - value_ = new array_t(a); - return; + // we found an element that makes it impossible to use the + // initializer list as object + is_object = false; + break; } } - // the initializer list is a list of pairs - type_ = value_t::object; - value_ = new object_t(); - for (const json& element : a) + // adjust type if type deduction is not wanted + if (not type_deduction) { - const std::string k = element[0]; - value_.object->emplace(std::make_pair(std::move(k), - std::move(element[1]))); + // mark this object's type as final + final_type_ = 1; + + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_object = false; + } + + // if object is wanted but impossible, throw an exception + if (manual_type == value_t::object and not is_object) + { + throw std::logic_error("cannot create JSON object"); + } + } + + if (is_object) + { + // the initializer list is a list of pairs -> create object + type_ = value_t::object; + value_ = new object_t(); + for (auto& element : a) + { + value_.object->emplace(std::make_pair(std::move(element[0]), std::move(element[1]))); + } + } + else + { + // the initializer list describes an array -> create array + type_ = value_t::array; + value_ = new array_t(std::move(a)); + } +} + +/*! +@param a initializer list to create an array from +@return array +*/ +json json::array(list_init_t a) +{ + return json(a, false, value_t::array); +} + +/*! +@param a initializer list to create an object from +@return object +*/ +json json::object(list_init_t a) +{ + // if more than one element is in the initializer list, wrap it + if (a.size() > 1) + { + return json({a}, false, value_t::object); + } + else + { + return json(a, false, value_t::object); } } diff --git a/src/json.cc b/src/json.cc index bec3fff6c..1457e2e0a 100644 --- a/src/json.cc +++ b/src/json.cc @@ -84,6 +84,9 @@ json::json(const value_t t) } } +json::json() noexcept : final_type_(0), type_(value_t::null) +{} + /*! Construct a null JSON object. */ @@ -96,35 +99,35 @@ Construct a string JSON object. @param s a string to initialize the JSON object with */ json::json(const std::string& s) - : type_(value_t::string), value_(new string_t(s)) + : final_type_(0), type_(value_t::string), value_(new string_t(s)) {} json::json(std::string&& s) - : type_(value_t::string), value_(new string_t(std::move(s))) + : final_type_(0), type_(value_t::string), value_(new string_t(std::move(s))) {} json::json(const char* s) - : type_(value_t::string), value_(new string_t(s)) + : final_type_(0), type_(value_t::string), value_(new string_t(s)) {} json::json(const bool b) noexcept - : type_(value_t::boolean), value_(b) + : final_type_(0), type_(value_t::boolean), value_(b) {} json::json(const array_t& a) - : type_(value_t::array), value_(new array_t(a)) + : final_type_(0), type_(value_t::array), value_(new array_t(a)) {} json::json(array_t&& a) - : type_(value_t::array), value_(new array_t(std::move(a))) + : final_type_(0), type_(value_t::array), value_(new array_t(std::move(a))) {} json::json(const object_t& o) - : type_(value_t::object), value_(new object_t(o)) + : final_type_(0), type_(value_t::object), value_(new object_t(o)) {} json::json(object_t&& o) - : type_(value_t::object), value_(new object_t(std::move(o))) + : final_type_(0), type_(value_t::object), value_(new object_t(std::move(o))) {} /*! @@ -141,33 +144,90 @@ as is to create an array. @bug With the described approach, we would fail to recognize an array whose first element is again an arrays as array. + +@param a an initializer list to create from +@param type_deduction whether the type (array/object) shall eb deducted +@param manual_type if type deduction is switched of, pass a manual type */ -json::json(list_init_t a) +json::json(list_init_t a, bool type_deduction, value_t manual_type) : final_type_(0) { + // the initializer list could describe an object + bool is_object = true; + // check if each element is an array with two elements whose first element // is a string for (const auto& element : a) { - if (element.type_ != value_t::array or - element.size() != 2 or - element[0].type_ != value_t::string) + if ((element.final_type_ == 1 and element.type_ == value_t::array) + or (element.type_ != value_t::array or element.size() != 2 or element[0].type_ != value_t::string)) { - - // the initializer list describes an array - type_ = value_t::array; - value_ = new array_t(a); - return; + // we found an element that makes it impossible to use the + // initializer list as object + is_object = false; + break; } } - // the initializer list is a list of pairs - type_ = value_t::object; - value_ = new object_t(); - for (const json& element : a) + // adjust type if type deduction is not wanted + if (not type_deduction) { - const std::string k = element[0]; - value_.object->emplace(std::make_pair(std::move(k), - std::move(element[1]))); + // mark this object's type as final + final_type_ = 1; + + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_object = false; + } + + // if object is wanted but impossible, throw an exception + if (manual_type == value_t::object and not is_object) + { + throw std::logic_error("cannot create JSON object"); + } + } + + if (is_object) + { + // the initializer list is a list of pairs -> create object + type_ = value_t::object; + value_ = new object_t(); + for (auto& element : a) + { + value_.object->emplace(std::make_pair(std::move(element[0]), std::move(element[1]))); + } + } + else + { + // the initializer list describes an array -> create array + type_ = value_t::array; + value_ = new array_t(std::move(a)); + } +} + +/*! +@param a initializer list to create an array from +@return array +*/ +json json::array(list_init_t a) +{ + return json(a, false, value_t::array); +} + +/*! +@param a initializer list to create an object from +@return object +*/ +json json::object(list_init_t a) +{ + // if more than one element is in the initializer list, wrap it + if (a.size() > 1) + { + return json({a}, false, value_t::object); + } + else + { + return json(a, false, value_t::object); } } diff --git a/src/json.h b/src/json.h index f6acc7611..8fb096275 100644 --- a/src/json.h +++ b/src/json.h @@ -140,7 +140,7 @@ class json /// create an object according to given type json(const value_t); /// create a null object - json() = default; + json() noexcept; /// create a null object json(std::nullptr_t) noexcept; /// create a string object from a C++ string @@ -160,7 +160,7 @@ class json /// create an object (move) json(object_t&&); /// create from an initializer list (to an array or object) - json(list_init_t); + json(list_init_t, bool = true, value_t = value_t::array); /*! @brief create a number object (integer) @@ -171,7 +171,7 @@ class json std::numeric_limits::is_integer, T>::type = 0> json(const T n) noexcept - : type_(value_t::number), + : final_type_(0), type_(value_t::number), value_(static_cast(n)) {} @@ -184,7 +184,7 @@ class json std::is_floating_point::value>::type > json(const T n) noexcept - : type_(value_t::number_float), + : final_type_(0), type_(value_t::number_float), value_(static_cast(n)) {} @@ -229,6 +229,11 @@ class json /// destructor ~json() noexcept; + /// explicit keyword to force array creation + static json array(list_init_t = list_init_t()); + /// explicit keyword to force object creation + static json object(list_init_t = list_init_t()); + /// create from string representation static json parse(const std::string&); /// create from string representation @@ -404,9 +409,10 @@ class json const_reverse_iterator crend() const noexcept; private: + /// whether the type is final + unsigned final_type_ : 1; /// the type of this object value_t type_ = value_t::null; - /// the payload value value_ {}; diff --git a/test/json_unit.cc b/test/json_unit.cc index da83ca880..6712dc6a8 100644 --- a/test/json_unit.cc +++ b/test/json_unit.cc @@ -304,13 +304,70 @@ TEST_CASE("array") CHECK(v == vec[static_cast(v)]); } } + } - + SECTION("Initializer lists") + { // edge case: This should be an array with two elements which are in // turn arrays with two strings. However, this is treated like the // initializer list of an object. json j_should_be_an_array = { {"foo", "bar"}, {"baz", "bat"} }; CHECK(j_should_be_an_array.type() == json::value_t::object); + + json j_is_an_array = { json::array({"foo", "bar"}), {"baz", "bat"} }; + CHECK(j_is_an_array.type() == json::value_t::array); + + // array outside initializer list + json j_pair_array = json::array({"s1", "s2"}); + CHECK(j_pair_array.type() == json::value_t::array); + + // array inside initializer list + json j_pair_array2 = {json::array({"us1", "us2"})}; + CHECK(j_pair_array2.type() == json::value_t::array); + + // array() is [] + json j_empty_array_explicit = json::array(); + CHECK(j_empty_array_explicit.type() == json::value_t::array); + + // object() is [] + json j_empty_object_explicit = json::object(); + CHECK(j_empty_object_explicit.type() == json::value_t::object); + + // {object({"s1", "s2"})} is [{"s1": "s2"}] + json j_explicit_object = {json::object({"s1", "s2"})}; + CHECK(j_explicit_object.type() == json::value_t::array); + + // object({"s1", "s2"}) is [{"s1": "s2"}] + json j_explicit_object2 = json::object({"s1", "s2"}); + CHECK(j_explicit_object2.type() == json::value_t::object); + + // object({{"s1", "s2"}}) is [{"s1": "s2"}] + json j_explicit_object3 = json::object({{"s1", "s2"}}); + CHECK(j_explicit_object3.type() == json::value_t::object); + + // check errors when explicit object creation is demanded + CHECK_THROWS_AS(json::object({"s1"}), std::logic_error); + CHECK_THROWS_AS(json::object({"s1", 1, 3}), std::logic_error); + CHECK_THROWS_AS(json::object({1, "s1"}), std::logic_error); + CHECK_THROWS_AS(json::object({{1, "s1"}}), std::logic_error); + CHECK_THROWS_AS(json::object({{"foo", "bar"}, {1, "s1"}}), std::logic_error); + + // {} is null + json j_null = {}; + CHECK(j_null.type() == json::value_t::null); + + // {{}} is [] + json j_empty_array_implicit = {{}}; + CHECK(j_empty_array_implicit.type() == json::value_t::array); + + // {1} is [1] + json j_singleton_array = {1}; + CHECK(j_singleton_array.type() == json::value_t::array); + + // test case from issue #8 + json j_issue8 = {json::array({"a", "b"}), json::array({"c", "d"})}; + CHECK(j_issue8.type() == json::value_t::array); + CHECK(j_issue8.dump() == "[[\"a\",\"b\"],[\"c\",\"d\"]]"); } SECTION("Iterators and empty arrays")