// Copyright (c) Robin E.R. Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "pch.h" #include "catch.hpp" #include #include #include #include "json.hpp" #include "json_variant.hpp" #include #include using namespace pipedal; class JsonTestTarget { private: JsonTestTarget(const JsonTestTarget &) {} // hide copy constructor. JsonTestTarget &operator=(const JsonTestTarget &) { return *this; } // hide assignment. public: JsonTestTarget() // make sure reading works without a default cosntructor. { } bool operator==(const JsonTestTarget &other) const { return this->int_ == other.int_ && this->float_ == other.float_ && this->double_ == other.double_ && this->string_ == other.string_ && std::equal(this->ints_.begin(), this->ints_.end(), other.ints_.begin(), other.ints_.end()) && std::equal(this->strings_.begin(), this->strings_.end(), other.strings_.begin(), other.strings_.end()); } public: int int_ = 1; float float_ = 3; double double_ = 4; public: std::string string_{"5"}; std::vector ints_{1, 2, 3, 4, 5}; std::vector strings_{"a", "b", "c", "d"}; std::vector> compound_array_{{1, 2}, {3}, {4, 5, 6}, {9}}; public: static json_map::storage_type jmap; public: JsonTestTarget(json_reader &reader) { #ifdef JUNK while (true) { const char *name = reader.read_member_name(); if (name == nullptr) { break; } } #endif } }; json_map::storage_type JsonTestTarget::jmap{{ // json_map::reference("char_", &JsonTestTarget::char_), json_map::reference("int", &JsonTestTarget::int_), json_map::reference("floatt", &JsonTestTarget::float_), json_map::reference("double", &JsonTestTarget::double_), json_map::reference("ints", &JsonTestTarget::ints_), json_map::reference("string", &JsonTestTarget::string_), json_map::reference("strings", &JsonTestTarget::strings_), json_map::reference("compoundarray", &JsonTestTarget::compound_array_), }}; TEST_CASE("json write", "[json_write_test]") { std::cout << "== json write ==" << std::endl; std::stringstream os; json_writer writer{os}; JsonTestTarget testTarget; writer.write(testTarget); std::cout << os.str() << std::endl; } static std::string get_json() { std::stringstream os; json_writer writer{os}; JsonTestTarget testTarget; writer.write(testTarget); return os.str(); } TEST_CASE("json read", "[json_read_test]") { JsonTestTarget source; source.ints_ = {1, 7, 4}; source.string_ = "xyz"; source.double_ = 99483.1837; std::stringstream os; json_writer writer(os); writer.write(source); std::string json = os.str(); std::stringstream input(json); json_reader reader{input}; JsonTestTarget dest; reader.read(&dest); REQUIRE(reader.is_complete()); REQUIRE(source == dest); JsonTestTarget *pDest; std::stringstream input2(json); json_reader reader2{input2}; reader2.read(&pDest); // JsonTestTarget *testTarget = reader.read_object(); } TEST_CASE("json smart ptrs", "[json_smart_ptrs][Build][Dev]") { std::string json = get_json(); { std::unique_ptr uniquePtr; std::stringstream input(json); json_reader reader{input}; reader.read(&uniquePtr); } { std::shared_ptr sharedPtr; std::stringstream input(json); json_reader reader{input}; reader.read(&sharedPtr); } } template U & VariantAs(json_variant& v) { throw std::runtime_error("Missing specialization."); } template <> inline json_null &VariantAs(json_variant& v) { return v.as_null(); } template <> inline bool &VariantAs(json_variant& v) { return v.as_bool(); } template <> inline double &VariantAs(json_variant& v) { return v.as_number(); } template <> inline std::string &VariantAs(json_variant& v) { return v.as_string(); } template <> inline std::shared_ptr &VariantAs>(json_variant& v) { return v.as_object(); } template <> inline std::shared_ptr &VariantAs>(json_variant& v) { return v.as_array(); } template <> inline json_variant &VariantAs(json_variant& v) { return v; } template void TestVariantRoundTrip(const T &value) { json_variant variant(value); T &out = VariantAs(variant); REQUIRE(out == value); std::string output; { std::stringstream s; json_writer writer(s); writer.write(variant); output = s.str(); } std::cout << output << std::endl; { std::stringstream s(output); json_reader reader(s); json_variant outputVariant; reader.read(&outputVariant); REQUIRE(outputVariant == variant); } } void TestVariantRoundTrip(json_variant &value) { json_variant variant(value); REQUIRE(variant == value); std::string output; { std::stringstream s; json_writer writer(s); writer.write(variant); output = s.str(); } std::cout << output << std::endl; json_variant outputVariant; { std::stringstream s(output); json_reader reader(s); json_variant outputVariant; reader.read(&outputVariant); REQUIRE(outputVariant == variant); } } class X { public: template requires std::derived_from bool write(T &v) { (void)v; return true; } template bool write(T &v) { (void)v; return false; } }; void TestVariantSFINAE() { X x; json_variant v; dynamic_cast(v); REQUIRE(x.write(v) == true); int i; REQUIRE(x.write(i) == false); } void TestIlleglUtf8Sequences() { { std::string json = "\"\x80\x80\x80\""; std::stringstream s(json); json_reader reader(s); std::string v; reader.read(&v); // just sufficient that it doesn't throw. } { std::string illegalJsonString = "123\x80\x80\x80xyz"; std::stringstream ss; json_writer writer(ss); writer.write(illegalJsonString); std::string output = ss.str(); std::string result = ss.str(); REQUIRE(result == "\"123\\uFFFDyz\""); // U+FFFD is the replacement character for illegal UTF-8 sequences. } } TEST_CASE("json variants", "[json_variants][Build][Dev]") { { TestIlleglUtf8Sequences(); TestVariantSFINAE(); TestVariantRoundTrip(0.0); TestVariantRoundTrip(std::string("abc")); TestVariantRoundTrip(json_null()); json_array array; array.push_back(json_null()); array.push_back(3.25E19); array.push_back(std::string("abc")); json_variant variantArray{std::move(array)}; REQUIRE(array.size() == 0); // did it get moved property? TestVariantRoundTrip(variantArray); { json_object obj; obj["a"] = json_null(); obj["b"] = 0.25; obj["c"] = std::move(variantArray); json_variant variantObj{std::move(obj)}; REQUIRE((variantArray.is_null() || variantArray.size() == 0) == true); // did move happen? REQUIRE(obj.size() == 0); // did move happen? TestVariantRoundTrip(variantObj); variantObj["a"] = std::string("abc"); TestVariantRoundTrip(variantObj); } { json_variant x = json_variant::make_array(); x.resize(3); x[0] = "def"; } } REQUIRE(json_object::allocation_count() == 0); REQUIRE(json_array::allocation_count() == 0); }