Skip to content

Commit f7f692f

Browse files
authored
Add objects, arrays & formatter (#12)
* cppjson::JsonObject primitive, tagged union Signed-off-by: TymianekPL <[email protected]> * std::formatter specialisation for json objects Signed-off-by: TymianekPL <[email protected]> * make formatter specialisation sexy ⚡ Signed-off-by: TymianekPL <[email protected]> * sync with pr3: Changes C++23 => C++20 in .clang-format Signed-off-by: TymianekPL <[email protected]> * Add cppjson::Object Signed-off-by: TymianekPL <[email protected]> * Fix clang-format Signed-off-by: TymianekPL <[email protected]> * cppjson::Object Signed-off-by: TymianekPL <[email protected]> * mark conversions as explicitly implicit Signed-off-by: TymianekPL <[email protected]> * formatter specialisations for proxies Signed-off-by: TymianekPL <[email protected]> * Sub objects Signed-off-by: TymianekPL <[email protected]> * Fix violation of rule of 5 in JsonObject (#4) * Add declarations for JsonObject's special functions * Define copy/move constructors * Define copy/move assignment operators * Implicitly create bytes * Implicitly create bytes in exchange() * Fix includes lol (utility, cstring) * Destroy() objects * Fix ambiguous operator[] (#6) * Fix ambiguous operator[] * template typo lol * Call Destroy() in type erase Signed-off-by: TymianekPL <[email protected]> * Ensure safe cleanup of arrays Signed-off-by: TymianekPL <[email protected]> * Arrays Signed-off-by: TymianekPL <[email protected]> * add emplace * Split into multiple files & add finish arrays Signed-off-by: TymianekPL <[email protected]> --------- Signed-off-by: TymianekPL <[email protected]>
1 parent bc50d19 commit f7f692f

File tree

7 files changed

+532
-10
lines changed

7 files changed

+532
-10
lines changed

.clang-format

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Language: Cpp
33
BasedOnStyle: LLVM
44
AlwaysBreakTemplateDeclarations: Yes
5-
BreakBeforeBraces: Attach
5+
BreakBeforeBraces: Allman
66
ColumnLimit: 160
77
SpaceAfterTemplateKeyword: true
88
Standard: c++20
@@ -29,3 +29,4 @@ IncludeCategories:
2929
Priority: 30
3030
PointerAlignment: Left
3131
QualifierAlignment: Left
32+
NamespaceIndentation: All

Test/Test.cpp

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
11
#include <cppjson/cppjson.hpp>
2+
#include <print>
23

3-
int main() { cppjson::hello_world(); }
4+
int main()
5+
{
6+
cppjson::Object object{};
7+
std::println("{}", object);
8+
object["test1"] = "Hello World";
9+
object["test2"] = 123.0;
10+
object["sub"]["veryNested"] = 6.0;
11+
cppjson::Array& array = object["array"];
12+
array[] = 2;
13+
array[] = 6.0;
14+
array[0] = 1;
15+
array[] = "Stirng";
16+
array.EmplaceBack(nullptr);
17+
try
18+
{
19+
array[2] = true;
20+
}
21+
catch (const std::logic_error& error)
22+
{
23+
std::println("Error = {}", error.what());
24+
}
25+
26+
std::println("{}", object);
27+
std::println("object[\"test1\"] = {}", object["test1"]);
28+
const std::string test = object["test1"];
29+
std::println("test = {}", test);
30+
}

cppjson/include/cppjson/cppjson.hpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
#pragma once
1+
#pragma once
22

3-
namespace cppjson {
4-
void hello_world();
5-
} // namespace cppjson
3+
#include "formatter.hpp"
4+
#include "object.hpp"

cppjson/include/cppjson/formatter.hpp

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#include <format>
2+
#include "object.hpp"
3+
4+
template <>
5+
struct std::formatter<cppjson::JsonObject>
6+
{
7+
constexpr auto parse(std::format_parse_context& context) { return context.begin(); }
8+
9+
auto format(const cppjson::JsonObject& object, std::format_context& context) const
10+
{
11+
switch (object._dataType)
12+
{
13+
case cppjson::JsonType::Null: return std::format_to(context.out(), "null");
14+
case cppjson::JsonType::Bool: return std::format_to(context.out(), "{}", object.DangerousAs<bool>());
15+
case cppjson::JsonType::Number: return std::format_to(context.out(), "{}", object.DangerousAs<double>());
16+
case cppjson::JsonType::String: return std::format_to(context.out(), "\"{}\"", object.DangerousAs<std::string>());
17+
case cppjson::JsonType::Object:
18+
{
19+
const auto& node = object.DangerousAs<cppjson::Object>();
20+
21+
std::string built = "{ ";
22+
for (const auto& [key, value] : node._nodes) built += std::format("\"{}\": {}, ", key, value);
23+
24+
if (!node._nodes.empty()) // remove trailing commas
25+
{
26+
built.pop_back();
27+
built.pop_back();
28+
built += " }";
29+
}
30+
else
31+
built += "}";
32+
33+
return std::format_to(context.out(), "{}", built);
34+
}
35+
case cppjson::JsonType::Array:
36+
{
37+
const auto& array = object.DangerousAs<cppjson::Array>();
38+
39+
std::string built = "[ ";
40+
for (const auto& element : array._objects) built += std::format("{}, ", element);
41+
42+
if (!array._objects.empty()) // remove trailing commas
43+
{
44+
built.pop_back();
45+
built.pop_back();
46+
built += " ]";
47+
}
48+
else
49+
built += "]";
50+
51+
return std::format_to(context.out(), "{}", built);
52+
}
53+
}
54+
55+
throw std::logic_error("Unknown type");
56+
}
57+
};
58+
59+
template <>
60+
struct std::formatter<cppjson::Object>
61+
{
62+
constexpr auto parse(std::format_parse_context& context) { return context.begin(); }
63+
64+
auto format(const cppjson::Object& object, std::format_context& context) const
65+
{
66+
std::string built = "{ ";
67+
for (const auto& [key, value] : object._nodes) built += std::format("\"{}\": {}, ", key, value);
68+
69+
if (!object._nodes.empty()) // remove trailing commas
70+
{
71+
built.pop_back();
72+
built.pop_back();
73+
built += " }";
74+
}
75+
else
76+
built += "}";
77+
78+
return std::format_to(context.out(), "{}", built);
79+
}
80+
};
81+
82+
template <>
83+
struct std::formatter<cppjson::Array>
84+
{
85+
constexpr auto parse(std::format_parse_context& context) { return context.begin(); }
86+
87+
auto format(const cppjson::Array& array, std::format_context& context) const
88+
{
89+
std::string built = "[ ";
90+
for (const auto& element : array._objects) built += std::format("{}, ", element);
91+
92+
if (!array._objects.empty()) // remove trailing commas
93+
{
94+
built.pop_back();
95+
built.pop_back();
96+
built += " ]";
97+
}
98+
else
99+
built += "]";
100+
101+
return std::format_to(context.out(), "{}", built);
102+
}
103+
};
104+
105+
template <>
106+
struct std::formatter<cppjson::Object::ObjectProxy>
107+
{
108+
constexpr auto parse(std::format_parse_context& context) { return context.begin(); }
109+
110+
auto format(const cppjson::Object::ObjectProxy& object, std::format_context& context) const
111+
{
112+
return std::format_to(context.out(), "{}", object._object.get());
113+
}
114+
};
115+
116+
template <>
117+
struct std::formatter<cppjson::Object::ConstObjectProxy>
118+
{
119+
constexpr auto parse(std::format_parse_context& context) { return context.begin(); }
120+
121+
auto format(const cppjson::Object::ConstObjectProxy& object, std::format_context& context) const
122+
{
123+
return std::format_to(context.out(), "{}", object._object.get());
124+
}
125+
};

cppjson/include/cppjson/object.hpp

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#pragma once
2+
3+
#include <algorithm>
4+
#include <concepts>
5+
#include <cstddef>
6+
#include <cstdint>
7+
#include <format>
8+
#include <functional>
9+
#include <string>
10+
#include <unordered_map>
11+
#include <vector>
12+
13+
namespace cppjson
14+
{
15+
enum struct JsonType
16+
{
17+
Null,
18+
String,
19+
Object,
20+
Number,
21+
Bool,
22+
Array
23+
};
24+
25+
class JsonObject
26+
{
27+
public:
28+
explicit JsonObject();
29+
JsonObject(const JsonObject& other);
30+
JsonObject(JsonObject&& other) noexcept;
31+
JsonObject& operator=(const JsonObject& other);
32+
JsonObject& operator=(JsonObject&& other) noexcept;
33+
~JsonObject();
34+
35+
template <typename T>
36+
T& As() noexcept(false);
37+
38+
template <typename T>
39+
const T& As() const noexcept(false);
40+
41+
private:
42+
JsonType _dataType{};
43+
std::byte* _dataStorage{};
44+
45+
void Destroy();
46+
template <typename T>
47+
T& DangerousAs() noexcept
48+
{
49+
return *std::launder(reinterpret_cast<T*>(this->_dataStorage));
50+
}
51+
template <typename T>
52+
const T& DangerousAs() const noexcept
53+
{
54+
return *std::launder(reinterpret_cast<T*>(this->_dataStorage));
55+
}
56+
57+
friend struct std::formatter<cppjson::JsonObject>;
58+
};
59+
60+
class Object
61+
{
62+
public:
63+
explicit Object() = default;
64+
Object(const Object&) = default;
65+
Object(Object&&) = default;
66+
Object& operator=(const Object&) = default;
67+
Object& operator=(Object&&) = default;
68+
~Object() = default;
69+
70+
class ObjectProxy
71+
{
72+
public:
73+
explicit ObjectProxy(JsonObject& object) : _object(std::ref(object)) {}
74+
75+
template <typename T>
76+
requires(!std::same_as<std::remove_cvref_t<T>, JsonObject>)
77+
explicit(false) operator T&()
78+
{
79+
return this->_object.get().As<T>();
80+
}
81+
82+
template <typename T>
83+
requires(!std::same_as<std::remove_cvref_t<T>, JsonObject>)
84+
explicit(false) operator const T&() const
85+
{
86+
return this->_object.get().As<T>();
87+
}
88+
89+
template <typename T>
90+
std::conditional_t<std::integral<T> && !std::same_as<T, bool>, void, T&> operator=(T&& assignment)
91+
{
92+
if constexpr (std::integral<T> && !std::same_as<T, bool>) static_cast<double&>(*this) = static_cast<double>(assignment);
93+
else
94+
return static_cast<T&>(*this) = std::forward<T>(assignment);
95+
}
96+
97+
template <std::size_t N>
98+
std::string& operator=(const char (&str)[N])
99+
{
100+
return static_cast<std::string&>(*this) = std::string{str};
101+
}
102+
103+
ObjectProxy operator[](const std::string& key);
104+
template <std::size_t N>
105+
ObjectProxy operator[](const char (&key)[N])
106+
{
107+
return (*this)[std::string{key}];
108+
}
109+
110+
private:
111+
std::reference_wrapper<JsonObject> _object;
112+
113+
friend struct std::formatter<cppjson::Object::ObjectProxy>;
114+
};
115+
116+
class ConstObjectProxy
117+
{
118+
public:
119+
explicit ConstObjectProxy(const JsonObject& object) : _object(std::ref(object)) {}
120+
template <typename T>
121+
explicit(false) operator const T&() const
122+
{
123+
return this->_object.get().As<T>();
124+
}
125+
126+
ConstObjectProxy operator[](const std::string& key) const;
127+
128+
private:
129+
std::reference_wrapper<const JsonObject> _object;
130+
131+
friend struct std::formatter<cppjson::Object::ConstObjectProxy>;
132+
};
133+
134+
ObjectProxy operator[](const std::string& key) { return ObjectProxy{this->_nodes[key]}; }
135+
136+
ConstObjectProxy operator[](const std::string& key) const
137+
{
138+
if (!this->_nodes.contains(key)) throw std::logic_error("Invalid key" + key);
139+
140+
return ConstObjectProxy{this->_nodes.at(key)};
141+
}
142+
143+
private:
144+
std::unordered_map<std::string, JsonObject> _nodes{};
145+
146+
friend struct std::formatter<cppjson::JsonObject>;
147+
friend struct std::formatter<cppjson::Object>;
148+
};
149+
150+
class Array
151+
{
152+
public:
153+
explicit Array() = default;
154+
~Array() = default;
155+
156+
Object::ObjectProxy operator[]() { return Object::ObjectProxy{this->_objects.emplace_back()}; }
157+
158+
Object::ObjectProxy EmplaceBack(const auto& object)
159+
{
160+
if constexpr (std::same_as<decltype(object), std::nullptr_t>) return Object::ObjectProxy{this->_objects.emplace_back()};
161+
else
162+
{
163+
auto& emplaced = this->_objects.emplace_back();
164+
emplaced.As<std::remove_cvref_t<decltype(object)>>() = object;
165+
return Object::ObjectProxy{emplaced};
166+
}
167+
}
168+
169+
Object::ObjectProxy operator[](const std::size_t index)
170+
{
171+
if (index >= this->_objects.size()) throw std::logic_error("Out of bound");
172+
return Object::ObjectProxy{this->_objects.at(index)};
173+
}
174+
175+
Object::ConstObjectProxy operator[](const std::size_t index) const
176+
{
177+
if (index >= this->_objects.size()) throw std::logic_error("Out of bound");
178+
return Object::ConstObjectProxy{this->_objects.at(index)};
179+
}
180+
181+
private:
182+
std::vector<JsonObject> _objects{};
183+
184+
friend struct std::formatter<cppjson::JsonObject>;
185+
friend struct std::formatter<cppjson::Array>;
186+
};
187+
} // namespace cppjson

cppjson/src/cppjson.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +0,0 @@
1-
#include <cppjson/cppjson.hpp>
2-
#include <print>
3-
4-
void cppjson::hello_world() { std::println("Hewwo wowld"); }

0 commit comments

Comments
 (0)