Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ CMakeUserPresets.json

# CMake build output
/out
/TestResults
/Testing
24 changes: 18 additions & 6 deletions Test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()
83 changes: 60 additions & 23 deletions Test/Test.cpp
Original file line number Diff line number Diff line change
@@ -1,30 +1,67 @@
#include <gtest/gtest.h>
#include <cppjson/cppjson.hpp>
#include <print>

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<std::string>(obj["string"]));
EXPECT_TRUE(IsType<double>(obj["number"]));
EXPECT_TRUE(IsType<bool>(obj["boolean"]));
EXPECT_TRUE(IsType<std::nullptr_t>(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<const std::string&>(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();
}
9 changes: 9 additions & 0 deletions cppjson-Test-results.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="(empty)"
tests="0"
failures="0"
disabled="0"
skipped="0"
hostname=""
time="0"
timestamp="2025-09-14T20:47:43"/>
43 changes: 41 additions & 2 deletions cppjson/include/cppjson/object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ namespace cppjson
template <typename T>
const T& As() const noexcept(false);

[[nodiscard]] bool operator==(const JsonObject& other) const;

private:
JsonType _dataType{};
std::byte* _dataStorage{};
Expand All @@ -55,6 +57,9 @@ namespace cppjson
}

friend struct std::formatter<cppjson::JsonObject>;

template <typename T>
friend bool IsType(const JsonObject& object) noexcept;
};

class Object
Expand All @@ -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:
Expand All @@ -76,14 +85,14 @@ namespace cppjson
requires(!std::same_as<std::remove_cvref_t<T>, JsonObject>)
explicit(false) operator T&()
{
return this->_object.get().As<T>();
return this->_object.get().As<std::remove_cvref_t<T>>();
}

template <typename T>
requires(!std::same_as<std::remove_cvref_t<T>, JsonObject>)
explicit(false) operator const T&() const
{
return this->_object.get().As<T>();
return this->_object.get().As<const std::remove_cvref_t<T>>();
}

template <typename T>
Expand All @@ -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<JsonObject> _object;

friend struct std::formatter<cppjson::Object::ObjectProxy>;
template <typename T>
friend bool IsType(const Object::ObjectProxy& proxy) noexcept;
};

class ConstObjectProxy
Expand Down Expand Up @@ -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<JsonObject> _objects{};

friend struct std::formatter<cppjson::JsonObject>;
friend struct std::formatter<cppjson::Array>;
};

template <typename T>
[[nodiscard]] bool IsType(const JsonObject& object) noexcept
{
if constexpr (std::same_as<std::remove_cvref_t<T>, std::nullptr_t>) return object._dataType == JsonType::Null;
else if constexpr (std::same_as<std::remove_cvref_t<T>, std::string>) return object._dataType == JsonType::String;
else if constexpr (std::same_as<std::remove_cvref_t<T>, Object>) return object._dataType == JsonType::Object;
else if constexpr (std::same_as<std::remove_cvref_t<T>, double>) return object._dataType == JsonType::Number;
else if constexpr (std::same_as<std::remove_cvref_t<T>, bool>) return object._dataType == JsonType::Bool;
else if constexpr (std::same_as<std::remove_cvref_t<T>, Array>) return object._dataType == JsonType::Array;
else
return false;
}

template <typename T>
[[nodiscard]] bool IsType(const Object::ObjectProxy& proxy) noexcept
{
return IsType<T>(proxy._object.get());
}
} // namespace cppjson
26 changes: 26 additions & 0 deletions cppjson/src/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<double>() == other.DangerousAs<double>();
case JsonType::Bool: return this->DangerousAs<bool>() == other.DangerousAs<bool>();
case JsonType::String: return this->DangerousAs<std::string>() == other.DangerousAs<std::string>();
case JsonType::Object: return this->DangerousAs<cppjson::Object>() == other.DangerousAs<cppjson::Object>();
case JsonType::Array: return this->DangerousAs<cppjson::Array>() == other.DangerousAs<cppjson::Array>();
default: return false;
}
}

void cppjson::JsonObject::Destroy(void)
{
using cppjson::Array;
Expand Down Expand Up @@ -185,3 +200,14 @@ cppjson::Object::ConstObjectProxy cppjson::Object::ConstObjectProxy::operator[](
{
return ConstObjectProxy{this->_object.get().As<Object>()[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;
}
Loading