diff --git a/.clang-format b/.clang-format index 89a7883..725b1f5 100644 --- a/.clang-format +++ b/.clang-format @@ -2,7 +2,7 @@ Language: Cpp BasedOnStyle: LLVM AlwaysBreakTemplateDeclarations: Yes -BreakBeforeBraces: Attach +BreakBeforeBraces: Allman ColumnLimit: 160 SpaceAfterTemplateKeyword: true Standard: c++20 @@ -29,3 +29,4 @@ IncludeCategories: Priority: 30 PointerAlignment: Left QualifierAlignment: Left +NamespaceIndentation: All diff --git a/Test/Test.cpp b/Test/Test.cpp index 34b0e90..789fd22 100644 --- a/Test/Test.cpp +++ b/Test/Test.cpp @@ -1,3 +1,30 @@ #include +#include -int main() { cppjson::hello_world(); } +int main() +{ + 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); +} diff --git a/cppjson/include/cppjson/cppjson.hpp b/cppjson/include/cppjson/cppjson.hpp index 2955248..6a0cebf 100644 --- a/cppjson/include/cppjson/cppjson.hpp +++ b/cppjson/include/cppjson/cppjson.hpp @@ -1,5 +1,4 @@ -#pragma once +#pragma once -namespace cppjson { -void hello_world(); -} // namespace cppjson +#include "formatter.hpp" +#include "object.hpp" diff --git a/cppjson/include/cppjson/formatter.hpp b/cppjson/include/cppjson/formatter.hpp new file mode 100644 index 0000000..6e5da70 --- /dev/null +++ b/cppjson/include/cppjson/formatter.hpp @@ -0,0 +1,125 @@ +#include +#include "object.hpp" + +template <> +struct std::formatter +{ + constexpr auto parse(std::format_parse_context& context) { return context.begin(); } + + auto format(const cppjson::JsonObject& object, std::format_context& context) const + { + switch (object._dataType) + { + case cppjson::JsonType::Null: return std::format_to(context.out(), "null"); + case cppjson::JsonType::Bool: return std::format_to(context.out(), "{}", object.DangerousAs()); + case cppjson::JsonType::Number: return std::format_to(context.out(), "{}", object.DangerousAs()); + case cppjson::JsonType::String: return std::format_to(context.out(), "\"{}\"", object.DangerousAs()); + case cppjson::JsonType::Object: + { + const auto& node = object.DangerousAs(); + + std::string built = "{ "; + for (const auto& [key, value] : node._nodes) built += std::format("\"{}\": {}, ", key, value); + + if (!node._nodes.empty()) // remove trailing commas + { + built.pop_back(); + built.pop_back(); + built += " }"; + } + else + built += "}"; + + return std::format_to(context.out(), "{}", built); + } + case cppjson::JsonType::Array: + { + const auto& array = object.DangerousAs(); + + std::string built = "[ "; + for (const auto& element : array._objects) built += std::format("{}, ", element); + + if (!array._objects.empty()) // remove trailing commas + { + built.pop_back(); + built.pop_back(); + built += " ]"; + } + else + built += "]"; + + return std::format_to(context.out(), "{}", built); + } + } + + throw std::logic_error("Unknown type"); + } +}; + +template <> +struct std::formatter +{ + constexpr auto parse(std::format_parse_context& context) { return context.begin(); } + + auto format(const cppjson::Object& object, std::format_context& context) const + { + std::string built = "{ "; + for (const auto& [key, value] : object._nodes) built += std::format("\"{}\": {}, ", key, value); + + if (!object._nodes.empty()) // remove trailing commas + { + built.pop_back(); + built.pop_back(); + built += " }"; + } + else + built += "}"; + + return std::format_to(context.out(), "{}", built); + } +}; + +template <> +struct std::formatter +{ + constexpr auto parse(std::format_parse_context& context) { return context.begin(); } + + auto format(const cppjson::Array& array, std::format_context& context) const + { + std::string built = "[ "; + for (const auto& element : array._objects) built += std::format("{}, ", element); + + if (!array._objects.empty()) // remove trailing commas + { + built.pop_back(); + built.pop_back(); + built += " ]"; + } + else + built += "]"; + + return std::format_to(context.out(), "{}", built); + } +}; + +template <> +struct std::formatter +{ + constexpr auto parse(std::format_parse_context& context) { return context.begin(); } + + auto format(const cppjson::Object::ObjectProxy& object, std::format_context& context) const + { + return std::format_to(context.out(), "{}", object._object.get()); + } +}; + +template <> +struct std::formatter +{ + constexpr auto parse(std::format_parse_context& context) { return context.begin(); } + + auto format(const cppjson::Object::ConstObjectProxy& object, std::format_context& context) const + { + return std::format_to(context.out(), "{}", object._object.get()); + } +}; diff --git a/cppjson/include/cppjson/object.hpp b/cppjson/include/cppjson/object.hpp new file mode 100644 index 0000000..c192cbd --- /dev/null +++ b/cppjson/include/cppjson/object.hpp @@ -0,0 +1,187 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cppjson +{ + enum struct JsonType + { + Null, + String, + Object, + Number, + Bool, + Array + }; + + class JsonObject + { + public: + explicit JsonObject(); + JsonObject(const JsonObject& other); + JsonObject(JsonObject&& other) noexcept; + JsonObject& operator=(const JsonObject& other); + JsonObject& operator=(JsonObject&& other) noexcept; + ~JsonObject(); + + template + T& As() noexcept(false); + + template + const T& As() const noexcept(false); + + private: + JsonType _dataType{}; + std::byte* _dataStorage{}; + + void Destroy(); + template + T& DangerousAs() noexcept + { + return *std::launder(reinterpret_cast(this->_dataStorage)); + } + template + const T& DangerousAs() const noexcept + { + return *std::launder(reinterpret_cast(this->_dataStorage)); + } + + friend struct std::formatter; + }; + + class Object + { + public: + explicit Object() = default; + Object(const Object&) = default; + Object(Object&&) = default; + Object& operator=(const Object&) = default; + Object& operator=(Object&&) = default; + ~Object() = default; + + class ObjectProxy + { + public: + explicit ObjectProxy(JsonObject& object) : _object(std::ref(object)) {} + + template + requires(!std::same_as, JsonObject>) + explicit(false) operator T&() + { + return this->_object.get().As(); + } + + template + requires(!std::same_as, JsonObject>) + explicit(false) operator const T&() const + { + return this->_object.get().As(); + } + + template + std::conditional_t && !std::same_as, void, T&> operator=(T&& assignment) + { + if constexpr (std::integral && !std::same_as) static_cast(*this) = static_cast(assignment); + else + return static_cast(*this) = std::forward(assignment); + } + + template + std::string& operator=(const char (&str)[N]) + { + return static_cast(*this) = std::string{str}; + } + + ObjectProxy operator[](const std::string& key); + template + ObjectProxy operator[](const char (&key)[N]) + { + return (*this)[std::string{key}]; + } + + private: + std::reference_wrapper _object; + + friend struct std::formatter; + }; + + class ConstObjectProxy + { + public: + explicit ConstObjectProxy(const JsonObject& object) : _object(std::ref(object)) {} + template + explicit(false) operator const T&() const + { + return this->_object.get().As(); + } + + ConstObjectProxy operator[](const std::string& key) const; + + private: + std::reference_wrapper _object; + + friend struct std::formatter; + }; + + ObjectProxy operator[](const std::string& key) { return ObjectProxy{this->_nodes[key]}; } + + ConstObjectProxy operator[](const std::string& key) const + { + if (!this->_nodes.contains(key)) throw std::logic_error("Invalid key" + key); + + return ConstObjectProxy{this->_nodes.at(key)}; + } + + private: + std::unordered_map _nodes{}; + + friend struct std::formatter; + friend struct std::formatter; + }; + + class Array + { + public: + explicit Array() = default; + ~Array() = default; + + Object::ObjectProxy operator[]() { return Object::ObjectProxy{this->_objects.emplace_back()}; } + + Object::ObjectProxy EmplaceBack(const auto& object) + { + if constexpr (std::same_as) return Object::ObjectProxy{this->_objects.emplace_back()}; + else + { + auto& emplaced = this->_objects.emplace_back(); + emplaced.As>() = object; + return Object::ObjectProxy{emplaced}; + } + } + + Object::ObjectProxy operator[](const std::size_t index) + { + if (index >= this->_objects.size()) throw std::logic_error("Out of bound"); + return Object::ObjectProxy{this->_objects.at(index)}; + } + + Object::ConstObjectProxy operator[](const std::size_t index) const + { + if (index >= this->_objects.size()) throw std::logic_error("Out of bound"); + return Object::ConstObjectProxy{this->_objects.at(index)}; + } + + private: + std::vector _objects{}; + + friend struct std::formatter; + friend struct std::formatter; + }; +} // namespace cppjson diff --git a/cppjson/src/cppjson.cpp b/cppjson/src/cppjson.cpp index e12af32..e69de29 100644 --- a/cppjson/src/cppjson.cpp +++ b/cppjson/src/cppjson.cpp @@ -1,4 +0,0 @@ -#include -#include - -void cppjson::hello_world() { std::println("Hewwo wowld"); } diff --git a/cppjson/src/object.cpp b/cppjson/src/object.cpp new file mode 100644 index 0000000..955950d --- /dev/null +++ b/cppjson/src/object.cpp @@ -0,0 +1,187 @@ +#include "cppjson/object.hpp" +#include +#include +#include +#include + +constexpr std::size_t DataStorageSize = std::max({sizeof(std::string), sizeof(cppjson::Object), sizeof(double), sizeof(bool)}); + +cppjson::JsonObject::JsonObject() : _dataStorage(static_cast(::operator new(DataStorageSize))) {} + +cppjson::JsonObject::JsonObject(const cppjson::JsonObject& other) +{ + if (other._dataStorage == nullptr) return; + + this->_dataType = other._dataType; + this->_dataStorage = static_cast(::operator new(DataStorageSize)); + std::memcpy(this->_dataStorage, other._dataStorage, DataStorageSize); +} +cppjson::JsonObject::JsonObject(JsonObject&& other) noexcept +{ + this->_dataType = std::exchange(other._dataType, cppjson::JsonType::Null); + this->_dataStorage = std::exchange(other._dataStorage, static_cast(::operator new(DataStorageSize))); +} +cppjson::JsonObject& cppjson::JsonObject::operator=(const cppjson::JsonObject& other) +{ + if (&other != this) + { + this->_dataType = other._dataType; + this->_dataStorage = static_cast(::operator new(DataStorageSize)); + std::memcpy(this->_dataStorage, other._dataStorage, DataStorageSize); + } + return *this; +} +cppjson::JsonObject& cppjson::JsonObject::operator=(cppjson::JsonObject&& other) noexcept +{ + if (&other != this) + { + this->_dataType = std::exchange(other._dataType, cppjson::JsonType::Null); + this->_dataStorage = std::exchange(other._dataStorage, static_cast(::operator new(DataStorageSize))); + } + return *this; +} +cppjson::JsonObject::~JsonObject() +{ + this->Destroy(); + ::operator delete(this->_dataStorage); +} + +void cppjson::JsonObject::Destroy(void) +{ + using cppjson::Array; + using cppjson::Object; + using std::string; + + switch (std::exchange(this->_dataType, JsonType::Null)) + { + case JsonType::Null: + case JsonType::Number: + case JsonType::Bool: break; + case JsonType::String: return DangerousAs().~string(); + case JsonType::Object: return DangerousAs().~Object(); + case JsonType::Array: return DangerousAs().~Array(); + } +} + +template <> +std::string& cppjson::JsonObject::As() noexcept(false) +{ + if (this->_dataType == JsonType::Null) + { + this->_dataType = JsonType::String; + return *new (this->_dataStorage) std::string{}; + } + + if (this->_dataType != JsonType::String) throw std::logic_error("Cannot convert this object to a string"); + return DangerousAs(); +} + +template <> +double& cppjson::JsonObject::As() noexcept(false) +{ + if (this->_dataType == JsonType::Null) + { + this->_dataType = JsonType::Number; + return *new (this->_dataStorage) double{}; + } + + if (this->_dataType != JsonType::Number) throw std::logic_error("Cannot convert this object to a double"); + return DangerousAs(); +} + +template <> +bool& cppjson::JsonObject::As() noexcept(false) +{ + if (this->_dataType == JsonType::Null) + { + this->_dataType = JsonType::Bool; + return *new (this->_dataStorage) bool{}; + } + + if (this->_dataType != JsonType::Bool) throw std::logic_error("Cannot convert this object to a bool"); + return DangerousAs(); +} + +template <> +cppjson::Object& cppjson::JsonObject::As() noexcept(false) +{ + if (this->_dataType == JsonType::Null) + { + this->_dataType = JsonType::Object; + return *new (this->_dataStorage) cppjson::Object{}; + } + + if (this->_dataType != JsonType::Object) throw std::logic_error("Cannot convert this object to a bool"); + return DangerousAs(); +} + +template <> +cppjson::Array& cppjson::JsonObject::As() noexcept(false) +{ + if (this->_dataType == JsonType::Null) + { + this->_dataType = JsonType::Array; + return *new (this->_dataStorage) cppjson::Array{}; + } + + if (this->_dataType != JsonType::Array) throw std::logic_error("Cannot convert this object to an array"); + return DangerousAs(); +} + +template <> +std::nullptr_t& cppjson::JsonObject::As() noexcept(false) +{ + if (std::exchange(this->_dataType, JsonType::Null) == JsonType::Null) return DangerousAs(); + + Destroy(); + return *new (this->_dataStorage) std::nullptr_t{}; +} + +template <> +const std::string& cppjson::JsonObject::As() const noexcept(false) +{ + if (this->_dataType != JsonType::String) throw std::logic_error("Cannot convert this object to a string"); + return DangerousAs(); +} + +template <> +const double& cppjson::JsonObject::As() const noexcept(false) +{ + if (this->_dataType != JsonType::Number) throw std::logic_error("Cannot convert this object to a double"); + return DangerousAs(); +} + +template <> +const bool& cppjson::JsonObject::As() const noexcept(false) +{ + if (this->_dataType != JsonType::Bool) throw std::logic_error("Cannot convert this object to a bool"); + return DangerousAs(); +} + +template <> +const cppjson::Object& cppjson::JsonObject::As() const noexcept(false) +{ + if (this->_dataType != JsonType::Object) throw std::logic_error("Cannot convert this object to an object"); + return DangerousAs(); +} + +template <> +const cppjson::Array& cppjson::JsonObject::As() const noexcept(false) +{ + if (this->_dataType != JsonType::Array) throw std::logic_error("Cannot convert this object to an Array"); + return DangerousAs(); +} + +template <> +const std::nullptr_t& cppjson::JsonObject::As() const noexcept(false) +{ + if (this->_dataType != JsonType::Null) throw std::logic_error("Cannot convert this object to a null"); + return DangerousAs(); +} + +cppjson::Object::ObjectProxy cppjson::Object::ObjectProxy::operator[](const std::string& key) { return ObjectProxy{this->_object.get().As()[key]}; } + +cppjson::Object::ConstObjectProxy cppjson::Object::ConstObjectProxy::operator[](const std::string& key) const +{ + return ConstObjectProxy{this->_object.get().As()[key]}; +}