diff --git a/.gitignore b/.gitignore index c1d1196..70c721b 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,5 @@ CMakeUserPresets.json # CMake build output /out +/TestResults +/Testing diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index c04d300..fb7f542 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -1,15 +1,27 @@ -add_executable(cppjson-Test) +add_executable(cppjson-Test) target_link_libraries(cppjson-Test PRIVATE cppjson::cppjson ) +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/heads/main.zip +) +FetchContent_MakeAvailable(googletest) + + target_sources(cppjson-Test PRIVATE Test.cpp ) -add_test( - NAME cppjson-Test - COMMAND cppjson-Test - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" -) +target_link_libraries(cppjson-Test PRIVATE gtest_main) + +include(GoogleTest) +gtest_discover_tests(cppjson-Test) + +if(MSVC) + add_compile_options("/Zi") + add_link_options("/PROFILE") +endif() diff --git a/Test/Test.cpp b/Test/Test.cpp index 789fd22..0500636 100644 --- a/Test/Test.cpp +++ b/Test/Test.cpp @@ -1,30 +1,67 @@ +#include #include -#include -int main() +TEST(BasicTests, ArraySize) +{ + cppjson::Array array{}; + array[] = 1; + array[] = 2; + array[] = 3.0; + EXPECT_TRUE(array.Size() == 3); +} + +TEST(BasicTests, InvalidAssignment) +{ + cppjson::Object obj{}; + obj["number"] = 123.0; + EXPECT_THROW({ obj["number"] = "NaN"; }, std::logic_error); +} + +TEST(BasicTests, NestedObjects) +{ + cppjson::Object object{}; + object["sub"]["veryNested"] = 6.0; + + EXPECT_EQ(6.0, (double)object["sub"]["veryNested"]); +} + +TEST(BasicTests, ObjectTypes) +{ + cppjson::Object obj{}; + obj["string"] = "Hello"; + obj["number"] = 42.0; + obj["boolean"] = true; + obj["null"] = nullptr; + + EXPECT_TRUE(IsType(obj["string"])); + EXPECT_TRUE(IsType(obj["number"])); + EXPECT_TRUE(IsType(obj["boolean"])); + EXPECT_TRUE(IsType(obj["null"])); +} + +TEST(BasicTests, Primitives) { cppjson::Object object{}; - std::println("{}", object); object["test1"] = "Hello World"; object["test2"] = 123.0; - object["sub"]["veryNested"] = 6.0; - cppjson::Array& array = object["array"]; - array[] = 2; - array[] = 6.0; - array[0] = 1; - array[] = "Stirng"; - array.EmplaceBack(nullptr); - try - { - array[2] = true; - } - catch (const std::logic_error& error) - { - std::println("Error = {}", error.what()); - } - - std::println("{}", object); - std::println("object[\"test1\"] = {}", object["test1"]); - const std::string test = object["test1"]; - std::println("test = {}", test); + + EXPECT_EQ("Hello World", static_cast(object["test1"])); + EXPECT_EQ(123.0, (double)object["test2"]); +} + +TEST(BasicTests, ValueComparisons) +{ + cppjson::Object obj{}; + obj["a"] = 5.0; + obj["b"] = 5.0; + obj["c"] = 10.0; + + EXPECT_TRUE(obj["a"] == obj["b"]); + EXPECT_FALSE(obj["a"] == obj["c"]); +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/cppjson-Test-results.xml b/cppjson-Test-results.xml new file mode 100644 index 0000000..108af66 --- /dev/null +++ b/cppjson-Test-results.xml @@ -0,0 +1,9 @@ + + diff --git a/cppjson/include/cppjson/object.hpp b/cppjson/include/cppjson/object.hpp index c192cbd..4270f20 100644 --- a/cppjson/include/cppjson/object.hpp +++ b/cppjson/include/cppjson/object.hpp @@ -38,6 +38,8 @@ namespace cppjson template const T& As() const noexcept(false); + [[nodiscard]] bool operator==(const JsonObject& other) const; + private: JsonType _dataType{}; std::byte* _dataStorage{}; @@ -55,6 +57,9 @@ namespace cppjson } friend struct std::formatter; + + template + friend bool IsType(const JsonObject& object) noexcept; }; class Object @@ -67,6 +72,10 @@ namespace cppjson Object& operator=(Object&&) = default; ~Object() = default; + [[nodiscard]] bool IsEmpty() const noexcept { return this->_nodes.empty(); } + + [[nodiscard]] bool operator==(const Object& other) const; + class ObjectProxy { public: @@ -76,14 +85,14 @@ namespace cppjson requires(!std::same_as, JsonObject>) explicit(false) operator T&() { - return this->_object.get().As(); + return this->_object.get().As>(); } template requires(!std::same_as, JsonObject>) explicit(false) operator const T&() const { - return this->_object.get().As(); + return this->_object.get().As>(); } template @@ -106,11 +115,14 @@ namespace cppjson { return (*this)[std::string{key}]; } + [[nodiscard]] bool operator==(const ObjectProxy& other) const { return this->_object.get() == other._object.get(); } private: std::reference_wrapper _object; friend struct std::formatter; + template + friend bool IsType(const Object::ObjectProxy& proxy) noexcept; }; class ConstObjectProxy @@ -178,10 +190,37 @@ namespace cppjson return Object::ConstObjectProxy{this->_objects.at(index)}; } + [[nodiscard]] std::size_t Size() const noexcept { return this->_objects.size(); } + + [[nodiscard]] bool operator==(const Array& other) const + { + if (this->_objects.size() != other._objects.size()) return false; + return std::equal(this->_objects.begin(), this->_objects.end(), other._objects.begin()); + } + private: std::vector _objects{}; friend struct std::formatter; friend struct std::formatter; }; + + template + [[nodiscard]] bool IsType(const JsonObject& object) noexcept + { + if constexpr (std::same_as, std::nullptr_t>) return object._dataType == JsonType::Null; + else if constexpr (std::same_as, std::string>) return object._dataType == JsonType::String; + else if constexpr (std::same_as, Object>) return object._dataType == JsonType::Object; + else if constexpr (std::same_as, double>) return object._dataType == JsonType::Number; + else if constexpr (std::same_as, bool>) return object._dataType == JsonType::Bool; + else if constexpr (std::same_as, Array>) return object._dataType == JsonType::Array; + else + return false; + } + + template + [[nodiscard]] bool IsType(const Object::ObjectProxy& proxy) noexcept + { + return IsType(proxy._object.get()); + } } // namespace cppjson diff --git a/cppjson/src/object.cpp b/cppjson/src/object.cpp index 955950d..070bfe6 100644 --- a/cppjson/src/object.cpp +++ b/cppjson/src/object.cpp @@ -46,6 +46,21 @@ cppjson::JsonObject::~JsonObject() ::operator delete(this->_dataStorage); } +bool cppjson::JsonObject::operator==(const JsonObject& other) const +{ + if (other._dataType != this->_dataType) return false; + switch (this->_dataType) + { + case JsonType::Null: return true; + case JsonType::Number: return this->DangerousAs() == other.DangerousAs(); + case JsonType::Bool: return this->DangerousAs() == other.DangerousAs(); + case JsonType::String: return this->DangerousAs() == other.DangerousAs(); + case JsonType::Object: return this->DangerousAs() == other.DangerousAs(); + case JsonType::Array: return this->DangerousAs() == other.DangerousAs(); + default: return false; + } +} + void cppjson::JsonObject::Destroy(void) { using cppjson::Array; @@ -185,3 +200,14 @@ cppjson::Object::ConstObjectProxy cppjson::Object::ConstObjectProxy::operator[]( { return ConstObjectProxy{this->_object.get().As()[key]}; } + +bool cppjson::Object::operator==(const Object& other) const +{ + if (this->_nodes.size() != other._nodes.size()) return false; + for (const auto& [key, value] : this->_nodes) + { + if (!other._nodes.contains(key)) return false; + if (!(value == other._nodes.at(key))) return false; + } + return true; +}