Skip to content

Commit 972caab

Browse files
committed
Добавлена инициализация из стандартных контейнеров.
Дополнена документация. Изменён natvis. Добавлены тесты.
1 parent b9e8f0a commit 972caab

6 files changed

Lines changed: 186 additions & 30 deletions

File tree

‎CMakeLists.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function(Simstr)
6363
simstr
6464
GIT_REPOSITORY https://github.com/orefkov/simstr.git
6565
GIT_SHALLOW TRUE
66-
GIT_TAG 04d253e
66+
GIT_TAG fb0e2f1
6767
FIND_PACKAGE_ARGS
6868
)
6969
FetchContent_MakeAvailable(simstr)

‎for_debug/simjson.natvis‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
<DisplayString Condition="type_==Json::Type::Text">text: {val_.text}</DisplayString>
88
<DisplayString Condition="type_==Json::Type::Integer">int: {val_.integer}</DisplayString>
99
<DisplayString Condition="type_==Json::Type::Real">real: {val_.real}</DisplayString>
10-
<DisplayString Condition="type_==Json::Type::Object">obj: {*val_.object}</DisplayString>
11-
<DisplayString Condition="type_==Json::Type::Array">arr: {*val_.array}</DisplayString>
10+
<DisplayString Condition="type_==Json::Type::Object">obj: {val_.object}</DisplayString>
11+
<DisplayString Condition="type_==Json::Type::Array">arr: {val_.array}</DisplayString>
1212
<DisplayString>[undefined]</DisplayString>
1313
<Expand>
1414
<ExpandedItem Condition="type_==Json::Type::Object">*val_.object</ExpandedItem>

‎include/simjson/json.h‎

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,40 @@ struct StreamedJsonParserBase {
5252
int idxUnicode_{};
5353
};
5454

55+
namespace jt {
56+
57+
template<typename K>
58+
using KeyType = StoreType<K, strhash<K>>;
59+
60+
template<typename T, typename K>
61+
concept JsonType = std::is_integral_v<std::remove_cvref_t<T>> ||
62+
std::is_floating_point_v<std::remove_cvref_t<T>> ||
63+
std::is_constructible_v<sstring<K>, T>;
64+
65+
template<typename T, typename K>
66+
concept JsonKeyType = std::convertible_to<T, simple_str<K>> ||
67+
std::same_as<std::remove_cvref_t<T>, KeyType<K>>;
68+
69+
template<typename T, typename K>
70+
concept JsonArraySource = requires(const T& t) {
71+
{ t.empty() } -> std::same_as<bool>;
72+
{ t.size() } -> std::same_as<size_t>;
73+
{ *t.begin() } -> JsonType<K>;
74+
{ *t.end() } -> JsonType<K>;
75+
};
76+
77+
template<typename T, typename K>
78+
concept JsonObjectSource = requires(const T& t) {
79+
{ t.empty() } -> std::same_as<bool>;
80+
{ t.size() } -> std::same_as<size_t>;
81+
{ t.begin()->first } -> JsonKeyType<K>;
82+
{ t.end()->first } -> JsonKeyType<K>;
83+
{ t.begin()->second } -> JsonType<K>;
84+
{ t.end()->second } -> JsonType<K>;
85+
};
86+
87+
} // namespace jt
88+
5589
/*!
5690
* @brief Класс для представления json значения
5791
* @tparam K - тип символов
@@ -64,19 +98,31 @@ class JsonValueTempl : public Json {
6498
using ssType = simple_str<K>;
6599

66100
using json_value = JsonValueTempl<K>;
67-
using json_object = std::shared_ptr<hashStrMap<K, JsonValueTempl<K>>>;
68-
using json_array = std::shared_ptr<std::vector<JsonValueTempl<K>>>;
69-
using obj_type = typename json_object::element_type;
70-
using arr_type = typename json_array::element_type;
101+
using obj_type = hashStrMap<K, JsonValueTempl<K>>;
102+
using arr_type = std::vector<JsonValueTempl<K>>;
103+
using json_object = std::shared_ptr<obj_type>;
104+
using json_array = std::shared_ptr<arr_type>;
71105

72106
/// Создает пустой объект с типом Undefined
73107
JsonValueTempl() : type_(Undefined) {}
74108
/// Конструктор копирования. Объекты и массивы копируются по ссылке
75109
JsonValueTempl(const JsonValueTempl& other);
76110
/// Конструктор перемещения
77111
JsonValueTempl(JsonValueTempl&& other) noexcept {
78-
// Расчёт на то, что shared_ptr не содержит указателей на свои внутренности
79-
memcpy((void*)this, &other, sizeof(*this));
112+
type_ = other.type_;
113+
switch (other.type_) {
114+
case Json::Text:
115+
new (&val_.text) strType(std::move(other.val_.text));
116+
break;
117+
case Json::Object:
118+
new (&val_.object) json_object(std::move(other.val_.object));
119+
break;
120+
case Json::Array:
121+
new (&val_.array) json_array(std::move(other.val_.array));
122+
break;
123+
default:
124+
memcpy(&val_, &other.val_, sizeof(val_));
125+
}
80126
other.type_ = Undefined;
81127
}
82128
/// Деструктор
@@ -128,21 +174,40 @@ class JsonValueTempl : public Json {
128174
/// Конструктор для создания дефолтного значения с типом type
129175
JsonValueTempl(Type type);
130176

131-
struct KeyInit : std::pair<const StoreType<K>, json_value> {
132-
using base = std::pair<const StoreType<K>, json_value>;
133-
KeyInit(const StoreType<K>& k, const json_value& v) : base(k, v){}
177+
struct KeyInit : std::pair<const jt::KeyType<K>, json_value> {
178+
using base = std::pair<const jt::KeyType<K>, json_value>;
179+
KeyInit(const jt::KeyType<K>& k, const json_value& v) : base(k, v){}
134180
KeyInit(ssType k, const json_value& v) : base(obj_type::toStoreType(k), v){}
135181
};
136182
using ObjectInit = std::initializer_list<KeyInit>;
137183
/// Конструктор из инициализатора для создания json::object: JsonValue v = {{"key1"_h, 1}, {"key2", "text"}, {"key3"_h, false}}; -> {"key1": 10, "key2": "text", "key3": false}
138184
JsonValueTempl(ObjectInit&& init) : type_(Object) {
139-
new (&val_.object) json_object(std::make_shared<obj_type>(std::move(reinterpret_cast<std::initializer_list<std::pair<const StoreType<K>, json_value>>&>(init))));
185+
new (&val_.object) json_object(std::make_shared<obj_type>(std::move(reinterpret_cast<std::initializer_list<std::pair<const jt::KeyType<K>, json_value>>&>(init))));
140186
}
141187
using ArrayInit = std::initializer_list<json_value>;
142188
/// Конструктор из инициализатора для создания json::array: JsonValue json = {"key1", 12, false, Json::null, 1.25, true}; -> ["key1", 12, false, null, 1.25, true]
143189
JsonValueTempl(ArrayInit&& init) : type_(Array) {
144190
new (&val_.array) json_array(std::make_shared<arr_type>(std::move(init)));
145191
}
192+
/// Конструктор из любого стандартного контейнера-массива, содержащего значения JSON.
193+
JsonValueTempl(jt::JsonArraySource<K> auto const& obj) : JsonValueTempl(emptyArray) {
194+
if (!obj.empty()) {
195+
auto& arr = *as_array();
196+
arr.reserve(obj.size());
197+
for (const auto& e : obj) {
198+
arr.emplace_back(e);
199+
}
200+
}
201+
}
202+
/// Конструктор из любого стандартного контейнера с парами, содержащими строковый ключ в first и значение JSON в second.
203+
JsonValueTempl(jt::JsonObjectSource<K> auto const& obj) : JsonValueTempl(emptyObject) {
204+
if (!obj.empty()) {
205+
auto& me = *as_object();
206+
for (const auto& [f, s] : obj) {
207+
me.emplace(f, s);
208+
}
209+
}
210+
}
146211

147212
struct Clone {
148213
const json_value& from;
@@ -174,7 +239,6 @@ class JsonValueTempl : public Json {
174239
/// Значение object
175240
bool is_object() const { return type_ == Object; }
176241

177-
178242
// ----------------------------- Boolean -----------------------------------------------
179243

180244
/// Получить значение как boolean. В отладочной версии проверяется, что значение действительно boolean
@@ -352,7 +416,7 @@ class JsonValueTempl : public Json {
352416
}
353417

354418
/// Обращение к свойству константного объекта по ключу. Не создаёт новую пару при отсутствии ключа
355-
template<typename T> requires (!std::is_integral_v<T>)
419+
template<jt::JsonKeyType<K> T>
356420
const json_value& at(T&& key) const {
357421
if (type_ == Object) {
358422
if (auto it = as_object()->find(std::forward<T>(key)); it != as_object()->end())
@@ -362,13 +426,13 @@ class JsonValueTempl : public Json {
362426
}
363427

364428
/// Обращение к свойству константного объекта по ключу. Не создаёт новую пару при отсутствии ключа
365-
template<typename T> requires (!std::is_integral_v<T>)
429+
template<jt::JsonKeyType<K> T>
366430
const json_value& operator[](T&& key) const {
367431
return at(std::forward<T>(key));
368432
}
369433

370434
/// Обращение к свойству константного объекта по ключам. Не создаёт новую пару при отсутствии ключа
371-
template<typename T, typename...Args> requires (!std::is_integral_v<T>)
435+
template<jt::JsonKeyType<K> T, typename...Args>
372436
const json_value& operator()(T&& key, Args&&...args) const {
373437
const json_value& res = at(std::forward<T>(key));
374438
if constexpr (sizeof...(Args) == 0) {
@@ -380,7 +444,7 @@ class JsonValueTempl : public Json {
380444

381445
/// Обращение к свойству объекта по ключу. Создаёт новую пару при отсутствии ключа
382446
/// Если значение не объект, заменят его на объект
383-
template<typename T> requires (!std::is_integral_v<T>)
447+
template<jt::JsonKeyType<K> T>
384448
json_value& operator[](T&& key) {
385449
if (type_ != Object) {
386450
assert(this != &UNDEFINED);
@@ -391,7 +455,7 @@ class JsonValueTempl : public Json {
391455

392456
/// Установка значения свойству объекта по ключу. Создаёт новую пару при отсутствии ключа
393457
/// Если значение не объект, заменят его на объект
394-
template<typename Key, typename ... Args> requires (!std::is_integral_v<Key>)
458+
template<jt::JsonKeyType<K> Key, typename ... Args>
395459
json_value& set(Key&& key, Args&& ... args) {
396460
if (type_ != Object) {
397461
assert(this != &UNDEFINED);
@@ -444,10 +508,10 @@ class JsonValueTempl : public Json {
444508
}
445509

446510
/// Слияние с другим JSON. Если replace == true, то другой JSON имеет приоритет, если он не undefined.
447-
/// Если оба объекта массивы, то другой массив дописывается к этому массиву.
511+
/// Если оба объекта массивы, то при append_arrays = true другой массив дописывается к этому массиву.
448512
/// Если оба JSONа объекты - то сливаются по ключам.
449513
/// Иначе другой JSON заменяет текущее значение.
450-
void merge(const json_value& other, bool replace = true);
514+
void merge(const json_value& other, bool replace = true, bool append_arrays = false);
451515

452516
/// Распарсить текст с json.
453517
static std::tuple<json_value, JsonParseResult, unsigned, unsigned> parse(ssType jsonString);

‎readme.md‎

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,19 @@
1414
для массивов - `std::vector<JsonValueTemp<K>>`, строки хранятся в `sstring<K>`.
1515

1616
## Основные возможности библиотеки
17+
- Работает со всеми строками simstr.
1718
- Поддерживает работу со строками `char`, `char16_t`, `char32_t`, `wchar_t`.
1819
- Удобное создание, чтение и модификация json значений.
20+
- Копирование таких JSON-значений, как массивы и объекты производится по ссылке (копируется только `shared_ptr`).
21+
- Возможно "глубокое" копирование aka клонирование, JSON-значений, в этом случае для массивов и объектов создаётся полная копия.
22+
- "Слияние" одного JSON объекта с другим, с возможностью задать приоритет.
1923
- Расширенная работа с числами - позволяет использовать int64_t и double.
2024
- Парсинг строки в Json, с поддержкой порционного парсинга.
2125
- Сериализация json в строку, с опциями - сортировка ключей, "читаемый" вывод, количество отступов и символ
2226
отступа при "читаемом" выводе.
2327

2428
## Основные объекты библиотеки
25-
- JsonValueTemp<K> - тип Json значения, параметр К задаёт тип используемых символов в строке. Алиасы:
29+
- JsonValueTempl<K> - тип Json значения, параметр К задаёт тип используемых символов в строке. Алиасы:
2630
- JsonValue - для строк char
2731
- JsonValueU - для строк char16_t
2832
- JsonValueUU - для строк char32_t
@@ -36,9 +40,10 @@
3640
можно просто включить файлы в свой проект. Для сборки также требуется [simstr](https://github.com/orefkov/simstr) (при использовании CMake
3741
скачивается автоматически).
3842

39-
Для работы `simjson` требуется компилятор стандарта не ниже С++20, как того требует simstr.
43+
Для работы `simjson` требуется компилятор с поддержкой стандарта не ниже С++20 (используются концепты).
4044

4145
## Примеры использования
46+
### Создание, чтение
4247
```cpp
4348
// Простой json, равный 1.
4449
JsonValue json = 1; // int64
@@ -74,8 +79,7 @@
7479
"not specified work dir");
7580
```
7681
77-
Задание дефолтных значений, чтение из файла и объединение с дефолтными значениями.
78-
82+
### Пример: Задание дефолтных значений, чтение из файла и объединение с дефолтными значениями.
7983
```cpp
8084
8185
JsonValue json_config = {
@@ -134,6 +138,46 @@ void read_config_from_file(ssa folder, ssa file_name) {
134138
json_config["runtime"_h]["location"_h]["base_dir"_h] = std::move(path_checked);
135139
}
136140
```
141+
### Пример - инициализация из стандартных контейнеров
142+
```cpp
143+
// Массивы
144+
std::vector<int> vals = {1, 2, 3, 4};
145+
JsonValue json = vals;
146+
EXPECT_EQ(json.type(), Json::Array);
147+
EXPECT_EQ(json.store(), "[1,2,3,4]");
148+
149+
JsonValue json1 = std::array<int, 4>{4, 3, 2, 1};
150+
EXPECT_EQ(json1.type(), Json::Array);
151+
EXPECT_EQ(json1.store(), "[4,3,2,1]");
152+
153+
std::list<stringa> texts = {"one", "two", "three"};
154+
JsonValue json2 = texts;
155+
EXPECT_EQ(json2.type(), Json::Array);
156+
EXPECT_EQ(json2.store(), "[\"one\",\"two\",\"three\"]");
157+
158+
// Ключ-значение
159+
hashStrMapA<int> vals = {
160+
{"one"_h, 1}, {"two"_h, 2}, {"three"_h, 3}, {"four"_h, 4}
161+
};
162+
JsonValue json = vals;
163+
EXPECT_EQ(json.type(), Json::Object);
164+
EXPECT_EQ(json.store(false, true), "{\"four\":4,\"one\":1,\"three\":3,\"two\":2}");
165+
166+
std::map<stringa, int> vals1 = {
167+
{"one", 1}, {"two", 2}, {"three", 3}, {"four", 4}
168+
};
169+
JsonValue json1 = vals1;
170+
EXPECT_EQ(json1.type(), Json::Object);
171+
EXPECT_EQ(json1.store(false, true), "{\"four\":4,\"one\":1,\"three\":3,\"two\":2}");
172+
173+
std::vector<std::pair<lstringa<20>, int>> vals2 = {
174+
{"one", 1}, {"two", 2}, {"three", 3}, {"four", 4}
175+
};
176+
JsonValue json2 = vals2;
177+
EXPECT_EQ(json2.type(), Json::Object);
178+
EXPECT_EQ(json2.store(false, true), "{\"four\":4,\"one\":1,\"three\":3,\"two\":2}");
179+
180+
```
137181
138182
## Сгенерированная документация
139183
[Находится здесь](https://snegopat.ru/simjson/docs/)

‎src/json.cpp‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,22 +369,31 @@ typename JsonValueTempl<K>::strType JsonValueTempl<K>::to_text() const {
369369
}
370370

371371
template<typename K>
372-
void JsonValueTempl<K>::merge(const json_value& other, bool replace) {
372+
void JsonValueTempl<K>::merge(const json_value& other, bool replace, bool append_arrays) {
373373
if (is_object() && other.is_object()) {
374374
// Надо слить по ключам
375375
auto& self = *as_object();
376376
for (const auto& [key, value]: *other.as_object()) {
377377
auto fnd = self.find(key);
378378
if (fnd != self.end()) {
379-
fnd->second.merge(value, replace);
379+
fnd->second.merge(value, replace, append_arrays);
380380
} else {
381381
self.try_emplace(key, value);
382382
}
383383
}
384384
} else if (is_array() && other.is_array()) {
385-
if (other.as_array()->size()) {
385+
if (append_arrays) {
386+
if (other.as_array()->size()) {
387+
auto& arr = *as_array();
388+
arr.reserve(arr.size() + other.as_array()->size());
389+
for (const auto& e : *other.as_array()) {
390+
arr.emplace_back(e);
391+
}
392+
}
393+
} else if (replace) {
386394
auto& arr = *as_array();
387-
arr.reserve(arr.size() + other.as_array()->size());
395+
arr.clear();
396+
arr.reserve(other.as_array()->size());
388397
for (const auto& e : *other.as_array()) {
389398
arr.emplace_back(e);
390399
}

‎tests/test_json.cpp‎

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <fstream>
66
#include <iostream>
77
#include <memory>
8+
#include <array>
89

910
namespace simjson::tests {
1011

@@ -388,7 +389,45 @@ TEST(SimJson, JsonThrow) {
388389
EXPECT_EQ(err_descr, "Need boolean value");
389390
}
390391

391-
/*
392+
TEST(SimJson, JsonStdInitArray) {
393+
std::vector<int> vals = {1, 2, 3, 4};
394+
JsonValue json = vals;
395+
EXPECT_EQ(json.type(), Json::Array);
396+
EXPECT_EQ(json.store(), "[1,2,3,4]");
397+
398+
JsonValue json1 = std::array<int, 4>{4, 3, 2, 1};
399+
EXPECT_EQ(json1.type(), Json::Array);
400+
EXPECT_EQ(json1.store(), "[4,3,2,1]");
401+
402+
std::list<stringa> texts = {"one", "two", "three"};
403+
JsonValue json2 = texts;
404+
EXPECT_EQ(json2.type(), Json::Array);
405+
EXPECT_EQ(json2.store(), "[\"one\",\"two\",\"three\"]");
406+
}
407+
408+
TEST(SimJson, JsonStdInitMap) {
409+
hashStrMapA<int> vals = {
410+
{"one"_h, 1}, {"two"_h, 2}, {"three"_h, 3}, {"four"_h, 4}
411+
};
412+
JsonValue json = vals;
413+
EXPECT_EQ(json.type(), Json::Object);
414+
EXPECT_EQ(json.store(false, true), "{\"four\":4,\"one\":1,\"three\":3,\"two\":2}");
415+
416+
std::map<stringa, int> vals1 = {
417+
{"one", 1}, {"two", 2}, {"three", 3}, {"four", 4}
418+
};
419+
JsonValue json1 = vals1;
420+
EXPECT_EQ(json1.type(), Json::Object);
421+
EXPECT_EQ(json1.store(false, true), "{\"four\":4,\"one\":1,\"three\":3,\"two\":2}");
422+
423+
std::vector<std::pair<lstringa<20>, int>> vals2 = {
424+
{"one", 1}, {"two", 2}, {"three", 3}, {"four", 4}
425+
};
426+
JsonValue json2 = vals2;
427+
EXPECT_EQ(json2.type(), Json::Object);
428+
EXPECT_EQ(json2.store(false, true), "{\"four\":4,\"one\":1,\"three\":3,\"two\":2}");
429+
}
430+
/*
392431
stringa getFileContent(stra fileName) {
393432
std::ifstream file(fileName, std::ios::binary | std::ios::ate);
394433
std::streamsize size = file.tellg();

0 commit comments

Comments
 (0)