Designed to work with JSON when using the simstr library.
Version 1.2.3.
This library contains a simple implementation of a simple JsonValue object for working with JSON using string objects
of the simstr library, since other libraries mainly work with std::string or raw const char*.
The task was not to somehow compete in performance or optimality with other libraries, I mainly use it
for working with small config files - read, modify, write. However, it also copes quite well with large files.
For json objects, std::unordered_map is used, in the form of hashStrMap<K, JsonValueTemp<K>>,
for arrays - std::vector<JsonValueTemp<K>>, strings are stored in sstring<K>.
- Works with all simstr strings.
- Supports working with strings
char,char16_t,char32_t,wchar_t. - Convenient creation, reading and modification of json values.
- Copying JSON values such as arrays and objects is done by reference (only
shared_ptris copied). - Possible "deep" copying aka cloning of JSON values, in this case a full copy is created for arrays and objects.
- "Merging" one JSON object with another, with the ability to set priority.
- Extended work with numbers - allows you to use int64_t and double.
- Parsing a string into Json, with support for partial parsing.
- Serializing json to a string, with options - sorting keys, "readable" output, number of indents and symbol indentation with "readable" output.
- JsonValueTempl - Json value type, parameter K specifies the type of characters used in the string. Aliases:
- JsonValue - for char strings
- JsonValueU - for char16_t strings
- JsonValueUU - for char32_t strings
- JsonValueW - for wchar_t strings
- StreamedJsonParser - parser of a string into JSON, supporting "partial" parsing. For example, data comes in portions from the network, you feed it to the parser as it arrives, until it either parses, or throws an error.
simjson consists of a header file and one source file. You can connect as a CMake project via add_subdirectory (the simjson library),
you can simply include the files in your project. Building also requires simstr (when using CMake
downloads automatically).
simjson requires a compiler with support for a standard no lower than C++20 (concepts are used).
// Simple json, equal to 1.
JsonValue json = 1; // int64
stringa text = json.store(); // "1"
// Initialization of the object, ""_h - slightly optimizes the key, calculating the hash during compilation
JsonValue obj = {
{"Key1"_h, 1},
{"Key2"_h, true},
{"Key3", {1, 2, Json::null, "test", false}}, // here will be an array [1, 2, null, "test", false]
{"Key4"_h, {
{"subkey1"_h, true},
{"subkey2", "subkey"},
}},
};
// Empty object
JsonValue test;
// Saving keys, immediately several levels
test["a"_h]["b"_h]["c"_h] = 10;
text = json.store(); // {"a":{"b":{"c":10}}}
// Converts a json object to an array
test[0] = "value";
test[-1] = true; // When using -1 - the value is added to the end of the array
test[10] = false; // Will increase the array size to 11
// Read value
JsonValue config;
....
stringa log_path = config("instance"_h, "base"_h, "log"_h, "path"_h).text().value_or("./log.txt");
stringa work_dir = config("instance"_h, "base"_h, "work_dir"_h).text_or_throw<std::runtime_error>(
"not specified work dir");JsonValue json_config = {
{"section1"_h, {
{"sub1"_h, {
{"path"_h, "."},
{"workers"_h, 10},
}},
{"sub2"_h, {
{"check"_h, true},
}},
{"key1"_h, true},
{"key2"_h, false},
}},
{"section2"_h, {
{"key1"_h, false},
{"key2"_h, true},
}},
};
inline bool is_file_exist(stra path) {
auto status = std::filesystem::status(path.to_sv());
return std::filesystem::status_known(status) && std::filesystem::is_regular_file(status);
}
void read_config_from_file(ssa folder, ssa file_name) {
stringa path_checked = folder + e_if(folder(-1) != PATH_SEPARATOR, PATH_SEPARATOR);
if (!file_name.is_empty()) {
lstringa<MAX_PATH> fullPath = path_checked + file_name;
stringa config;
if (is_file_exist(fullPath)) {
config = get_file_content(fullPath);
auto [readed, error, line, col] = JsonValue::parse(config);
if (error != JsonParseResult::Success) {
std::cerr << "Error in parse config file " << fullPath <<
" at line " << line << ", col " << col << std::endl;
throw std::runtime_error{"Error parse config file"};
}
json_config.merge(readed);
}
// We may have new keys in the default config, and I would like their default values
// also drop to the file, to make it easier to edit later.
stringa new_config = json_config.store(true, true, ' ', 4);
if (new_config != config) {
// New keys have been added to the default config
std::ofstream file(fullPath.c_str(), std::ios::binary | std::ios::trunc);
if (!file.is_open()) {
std::cerr << "Error in create config file " << fullPath << std::endl;
} else {
file.write(new_config.c_str(), new_config.length());
}
}
}
json_config["runtime"_h]["location"_h]["base_dir"_h] = std::move(path_checked);
} // Arrays
std::vector<int> vals = {1, 2, 3, 4};
JsonValue json = vals;
EXPECT_EQ(json.type(), Json::Array);
EXPECT_EQ(json.store(), "[1,2,3,4]");
JsonValue json1 = std::array<int, 4>{4, 3, 2, 1};
EXPECT_EQ(json1.type(), Json::Array);
EXPECT_EQ(json1.store(), "[4,3,2,1]");
std::list<stringa> texts = {"one", "two", "three"};
JsonValue json2 = texts;
EXPECT_EQ(json2.type(), Json::Array);
EXPECT_EQ(json2.store(), "[\"one\",\"two\",\"three\"]");
// Key-value
hashStrMapA<int> vals = {
{"one"_h, 1}, {"two"_h, 2}, {"three"_h, 3}, {"four"_h, 4}
};
JsonValue json = vals;
EXPECT_EQ(json.type(), Json::Object);
EXPECT_EQ(json.store(false, true), "{\"four\":4,\"one\":1,\"three\":3,\"two\":2}");
std::map<stringa, int> vals1 = {
{"one", 1}, {"two", 2}, {"three", 3}, {"four", 4}
};
JsonValue json1 = vals1;
EXPECT_EQ(json1.type(), Json::Object);
EXPECT_EQ(json1.store(false, true), "{\"four\":4,\"one\":1,\"three\":3,\"two\":2}");
std::vector<std::pair<lstringa<20>, int>> vals2 = {
{"one", 1}, {"two", 2}, {"three", 3}, {"four", 4}
};
JsonValue json2 = vals2;
EXPECT_EQ(json2.type(), Json::Object);
EXPECT_EQ(json2.store(false, true), "{\"four\":4,\"one\":1,\"three\":3,\"two\":2}");