mirror of
https://codeberg.org/vyn/rei-json.git
synced 2025-07-01 09:33:19 +00:00
first commit
This commit is contained in:
commit
eb5191a722
15 changed files with 1075 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
build
|
||||
.cache
|
||||
build-profiler.sh
|
||||
build-run.sh
|
36
CMakeLists.txt
Normal file
36
CMakeLists.txt
Normal file
|
@ -0,0 +1,36 @@
|
|||
cmake_minimum_required(VERSION 3.21)
|
||||
project(rei-json LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(SLINT_FEATURE_RENDERER_SKIA ON)
|
||||
set(SLINT_FEATURE_RENDERER_SOFTWARE ON)
|
||||
|
||||
add_library(rei-json
|
||||
src/parse.cpp
|
||||
src/toString.cpp
|
||||
src/Object.cpp
|
||||
src/Array.cpp
|
||||
src/Field.cpp
|
||||
)
|
||||
target_include_directories(rei-json PRIVATE "external")
|
||||
target_include_directories(rei-json PRIVATE "include")
|
||||
|
||||
Include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
Catch2
|
||||
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||
GIT_TAG v3.4.0 # or a later release
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(Catch2)
|
||||
|
||||
add_executable(tests
|
||||
tests/usage.cpp
|
||||
tests/parsing.cpp
|
||||
tests/stringify.cpp
|
||||
)
|
||||
target_include_directories(tests PRIVATE "include")
|
||||
target_link_libraries(tests PRIVATE rei-json)
|
||||
target_link_libraries(tests PRIVATE Catch2::Catch2WithMain)
|
44
README.md
Normal file
44
README.md
Normal file
|
@ -0,0 +1,44 @@
|
|||
# rei-cpp
|
||||
> [!warning]
|
||||
> This is a work in progress, not stable and the API might change.
|
||||
|
||||
A C++ JSON library.
|
||||
|
||||
## Example
|
||||
|
||||
```cpp
|
||||
#include "rei-json/json.h"
|
||||
#include <print>
|
||||
|
||||
int main() {
|
||||
auto objectJson = rei::json::JsonObject{};
|
||||
objectJson
|
||||
.set("keyPositiveNumber", 12)
|
||||
.set("keyNegativeNumber", -13)
|
||||
.set("keyBooleanTrue", true)
|
||||
.set("keyBooleanFalse", false)
|
||||
.set("keyString", "YEP")
|
||||
.set("keyEmptyString", "")
|
||||
.setNull("keyNull");
|
||||
|
||||
rei::json::JsonObject obj{};
|
||||
obj.set("keyNumberOnObject", 42);
|
||||
|
||||
rei::json::JsonArray array{};
|
||||
array.push(42);
|
||||
array.push("elemString");
|
||||
array.push("");
|
||||
array.push(true);
|
||||
array.push(false);
|
||||
array.pushNull();
|
||||
|
||||
objectJson.addObject("keyObject", std::move(obj));
|
||||
objectJson.addArray("keyArray", std::move(array));
|
||||
|
||||
auto newJsonString = rei::json::toString(objectJson);
|
||||
std::println("{}\n", newJsonString);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
42
include/rei-json/Array.h
Normal file
42
include/rei-json/Array.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace rei::json {
|
||||
class JsonObject;
|
||||
class FieldValue;
|
||||
class JsonArray {
|
||||
|
||||
public:
|
||||
|
||||
JsonArray();
|
||||
|
||||
JsonArray& push(int value);
|
||||
JsonArray& push(bool value);
|
||||
JsonArray& push(const std::string& value);
|
||||
JsonArray& push(const std::string&& value);
|
||||
JsonArray& push(const char * value);
|
||||
JsonArray& pushObject(const JsonObject& object);
|
||||
JsonArray& pushObject(const JsonObject&& object);
|
||||
JsonArray& pushArray(const JsonArray& array);
|
||||
JsonArray& pushArray(const JsonArray&& array);
|
||||
JsonArray& pushNull();
|
||||
|
||||
int getNumber(int index);
|
||||
bool getBool(int index);
|
||||
std::string getString(int index);
|
||||
JsonObject& getObject(int index);
|
||||
JsonArray& getArray(int index);
|
||||
bool hasNull(int index);
|
||||
|
||||
void forEach(std::function<void(FieldValue&, int)>);
|
||||
size_t count() const;
|
||||
|
||||
private:
|
||||
std::vector<FieldValue> elements;
|
||||
};
|
||||
|
||||
}
|
70
include/rei-json/Field.h
Normal file
70
include/rei-json/Field.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
#pragma once
|
||||
|
||||
#include "Object.h"
|
||||
#include "Array.h"
|
||||
#include <string>
|
||||
|
||||
namespace rei::json {
|
||||
|
||||
class JsonObject;
|
||||
class JsonArray;
|
||||
|
||||
enum FieldType {
|
||||
Null,
|
||||
Number,
|
||||
Bool,
|
||||
String,
|
||||
Object,
|
||||
Array
|
||||
};
|
||||
|
||||
class FieldValue {
|
||||
|
||||
public:
|
||||
|
||||
FieldValue(const FieldValue& other) noexcept;
|
||||
FieldValue& operator=(const FieldValue& other) noexcept;
|
||||
FieldValue& operator=(FieldValue&& other) noexcept;
|
||||
FieldValue(FieldValue&& other) noexcept;
|
||||
|
||||
FieldValue(FieldType type);
|
||||
FieldValue(int value);
|
||||
FieldValue(bool value);
|
||||
FieldValue(const std::string& value);
|
||||
FieldValue(std::string&& value);
|
||||
FieldValue(const JsonObject& value);
|
||||
FieldValue(JsonObject&& value);
|
||||
FieldValue(const JsonArray& value);
|
||||
FieldValue(JsonArray&& value) noexcept;
|
||||
|
||||
~FieldValue();
|
||||
|
||||
inline int asNumber() { return number; }
|
||||
inline bool asBool() { return boolean; }
|
||||
inline std::string& asString() { return string; }
|
||||
inline JsonObject& asObject() { return object; }
|
||||
inline JsonArray& asArray() { return array; }
|
||||
|
||||
inline bool isNumber() const { return type == FieldType::Number; }
|
||||
inline bool isBool() const { return type == FieldType::Bool; }
|
||||
inline bool isString() const { return type == FieldType::String; }
|
||||
inline bool isObject() const { return type == FieldType::Object; }
|
||||
inline bool isArray() const { return type == FieldType::Array; }
|
||||
inline bool isNull() const { return type == FieldType::Null; }
|
||||
|
||||
private:
|
||||
|
||||
void _copyValues(const FieldValue& other);
|
||||
void _moveValues(FieldValue&& other);
|
||||
void _destroy();
|
||||
|
||||
FieldType type;
|
||||
union {
|
||||
int number;
|
||||
bool boolean;
|
||||
std::string string;
|
||||
JsonObject object;
|
||||
JsonArray array;
|
||||
};
|
||||
};
|
||||
}
|
44
include/rei-json/Object.h
Normal file
44
include/rei-json/Object.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace rei::json {
|
||||
|
||||
class JsonArray;
|
||||
class FieldValue;
|
||||
|
||||
class JsonObject {
|
||||
public:
|
||||
|
||||
JsonObject();
|
||||
|
||||
bool contains(const std::string& key) const;
|
||||
|
||||
JsonObject& set(const std::string& key, int value);
|
||||
JsonObject& set(const std::string& key, bool value);
|
||||
JsonObject& set(const std::string& key, const std::string& value);
|
||||
JsonObject& set(const std::string& key, const std::string&& value);
|
||||
JsonObject& set(const std::string& key, const char * value);
|
||||
JsonObject& setNull(const std::string& key);
|
||||
JsonObject& addArray(const std::string& key, const JsonArray& array);
|
||||
JsonObject& addArray(const std::string& key, JsonArray&& array);
|
||||
JsonObject& addObject(const std::string& key, const JsonObject& object);
|
||||
JsonObject& addObject(const std::string& key, JsonObject&& object);
|
||||
|
||||
int getNumber(const std::string& key);
|
||||
bool getBool(const std::string& key);
|
||||
std::string getString(const std::string& key);
|
||||
JsonObject& getObject(const std::string& key);
|
||||
JsonArray& getArray(const std::string& key);
|
||||
bool hasNull(const std::string& key);
|
||||
|
||||
void forEach(std::function<void(FieldValue&, const std::string& key)>);
|
||||
size_t count() const;
|
||||
|
||||
private:
|
||||
|
||||
std::map<std::string, FieldValue> elements;
|
||||
};
|
||||
}
|
14
include/rei-json/json.h
Normal file
14
include/rei-json/json.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
#include "Object.h"
|
||||
#include "Array.h"
|
||||
#include "Field.h"
|
||||
#include <string>
|
||||
#include <variant>
|
||||
namespace rei::json {
|
||||
class std::variant<JsonObject, JsonArray> parse(const std::string& jsonStr);
|
||||
|
||||
std::string toString(std::variant<JsonObject, JsonArray>& element);
|
||||
std::string toString(JsonObject& object);
|
||||
std::string toString(JsonArray& object);
|
||||
|
||||
}
|
115
src/Array.cpp
Normal file
115
src/Array.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
#include "rei-json/Field.h"
|
||||
#include "rei-json/Array.h"
|
||||
#include "rei-json/Object.h"
|
||||
#include <print>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
namespace rei::json {
|
||||
|
||||
JsonArray::JsonArray() {
|
||||
elements.reserve(1000);
|
||||
}
|
||||
|
||||
JsonArray& JsonArray::push(int value) {
|
||||
elements.push_back(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonArray& JsonArray::push(bool value) {
|
||||
elements.push_back(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonArray& JsonArray::push(const char * value) {
|
||||
elements.push_back(std::string(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonArray& JsonArray::push(const std::string& value) {
|
||||
elements.push_back(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonArray& JsonArray::push(const std::string&& value) {
|
||||
elements.push_back(std::move(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonArray& JsonArray::pushNull() {
|
||||
elements.push_back(std::move(FieldValue{FieldType::Null}));
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonArray& JsonArray::pushObject(const JsonObject& object) {
|
||||
elements.push_back(object);
|
||||
return *this;
|
||||
};
|
||||
|
||||
JsonArray& JsonArray::pushObject(const JsonObject&& object) {
|
||||
elements.push_back(std::move(object));
|
||||
return *this;
|
||||
};
|
||||
|
||||
JsonArray& JsonArray::pushArray(const JsonArray& array) {
|
||||
elements.push_back(array);
|
||||
return *this;
|
||||
};
|
||||
|
||||
JsonArray& JsonArray::pushArray(const JsonArray&& array) {
|
||||
elements.push_back(std::move(array));
|
||||
return *this;
|
||||
};
|
||||
|
||||
int JsonArray::getNumber(int index) {
|
||||
if (index >= elements.size()) {
|
||||
throw std::logic_error(std::string("Trying to access non existing index: ") + std::to_string(index));
|
||||
}
|
||||
return elements[index].asNumber();;
|
||||
}
|
||||
|
||||
bool JsonArray::getBool(int index) {
|
||||
if (index >= elements.size()) {
|
||||
throw std::logic_error(std::string("Trying to access non existing index: ") + std::to_string(index));
|
||||
}
|
||||
return elements[index].asBool();;
|
||||
}
|
||||
|
||||
std::string JsonArray::getString(int index) {
|
||||
if (index >= elements.size()) {
|
||||
throw std::logic_error(std::string("Trying to access non existing index: ") + std::to_string(index));
|
||||
}
|
||||
return elements[index].asString();
|
||||
}
|
||||
|
||||
JsonObject& JsonArray::getObject(int index) {
|
||||
if (index >= elements.size()) {
|
||||
throw std::logic_error(std::string("Trying to access non existing index: ") + std::to_string(index));
|
||||
}
|
||||
return elements[index].asObject();
|
||||
}
|
||||
|
||||
JsonArray& JsonArray::getArray(int index) {
|
||||
if (index >= elements.size()) {
|
||||
throw std::logic_error(std::string("Trying to access non existing index: ") + std::to_string(index));
|
||||
}
|
||||
return elements[index].asArray();
|
||||
}
|
||||
|
||||
bool JsonArray::hasNull(int index) {
|
||||
if (index >= elements.size()) {
|
||||
throw std::logic_error(std::string("Trying to access non existing index: ") + std::to_string(index));
|
||||
}
|
||||
return elements[index].isNull();
|
||||
}
|
||||
|
||||
void JsonArray::forEach(std::function<void(FieldValue&, int)> func) {
|
||||
for (int i = 0; i < elements.size(); ++i) {
|
||||
func(elements[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
size_t JsonArray::count() const {
|
||||
return elements.size();
|
||||
}
|
||||
|
||||
}
|
127
src/Field.cpp
Normal file
127
src/Field.cpp
Normal file
|
@ -0,0 +1,127 @@
|
|||
#include "rei-json/Field.h"
|
||||
#include <algorithm>
|
||||
#include <ostream>
|
||||
#include <print>
|
||||
#include <utility>
|
||||
|
||||
namespace rei::json {
|
||||
|
||||
|
||||
|
||||
FieldValue::FieldValue(const FieldValue& other) noexcept {
|
||||
//std::println("I'm being copied ! (constructor) {} {}", static_cast<int>(other.type), other.number);
|
||||
_copyValues(std::move(other));
|
||||
}
|
||||
|
||||
FieldValue& FieldValue::operator=(const FieldValue& other) noexcept {
|
||||
//std::println("I'm being copied ! (operator) {}", static_cast<int>(other.type));
|
||||
_destroy();
|
||||
_copyValues(std::move(other));
|
||||
return *this;
|
||||
}
|
||||
|
||||
FieldValue& FieldValue::operator=(FieldValue&& other) noexcept {
|
||||
//std::println("I'm being moved ! (operator) {} {}", static_cast<int>(other.type), other.number);
|
||||
_destroy();
|
||||
_moveValues(std::move(other));
|
||||
return *this;
|
||||
}
|
||||
|
||||
FieldValue::FieldValue(FieldValue&& other) noexcept {
|
||||
//std::println("I'm being moved ! (constructor) {} {}", static_cast<int>(other.type), other.number);
|
||||
_moveValues(std::move(other));
|
||||
}
|
||||
|
||||
void FieldValue::_copyValues(const FieldValue& other) {
|
||||
switch (other.type) {
|
||||
case rei::json::FieldType::Number:
|
||||
number = other.number;
|
||||
break;
|
||||
case rei::json::FieldType::Bool:
|
||||
boolean = other.boolean;
|
||||
break;
|
||||
case rei::json::FieldType::String:
|
||||
new (&string) std::string(other.string);
|
||||
break;
|
||||
case rei::json::FieldType::Object:
|
||||
new (&object) JsonObject(other.object);
|
||||
break;
|
||||
case rei::json::FieldType::Array:
|
||||
new (&array) JsonArray(other.array);
|
||||
break;
|
||||
}
|
||||
type = other.type;
|
||||
}
|
||||
void FieldValue::_moveValues(FieldValue&& other) {
|
||||
switch (other.type) {
|
||||
case rei::json::FieldType::Number:
|
||||
number = other.number;
|
||||
break;
|
||||
case rei::json::FieldType::Bool:
|
||||
boolean = other.boolean;
|
||||
break;
|
||||
case rei::json::FieldType::String:
|
||||
new (&string) std::string(std::move(other.string));
|
||||
break;
|
||||
case rei::json::FieldType::Object:
|
||||
new (&object) JsonObject(std::move(other.object));
|
||||
break;
|
||||
case rei::json::FieldType::Array:
|
||||
new (&array) JsonArray(std::move(other.array));
|
||||
break;
|
||||
}
|
||||
type = std::move(other.type);
|
||||
}
|
||||
|
||||
FieldValue::FieldValue(FieldType type) : number(0), type(type) {
|
||||
|
||||
}
|
||||
|
||||
FieldValue::FieldValue(int value) : number(value), type(FieldType::Number) {
|
||||
|
||||
}
|
||||
|
||||
FieldValue::FieldValue(bool value) : boolean(value), type(FieldType::Bool) {
|
||||
|
||||
}
|
||||
|
||||
FieldValue::FieldValue(const std::string& value) : string(value), type(FieldType::String) {
|
||||
|
||||
}
|
||||
|
||||
FieldValue::FieldValue(std::string&& value) : string(std::move(value)), type(FieldType::String) {
|
||||
|
||||
}
|
||||
|
||||
FieldValue::FieldValue(const JsonObject& value) : object(value), type(FieldType::Object) {
|
||||
|
||||
}
|
||||
|
||||
FieldValue::FieldValue(JsonObject&& value) : object(std::move(value)), type(FieldType::Object) {
|
||||
|
||||
}
|
||||
|
||||
FieldValue::FieldValue(const JsonArray& value) : array(value), type(FieldType::Array) {
|
||||
//std::println("Array ! copy");
|
||||
}
|
||||
|
||||
FieldValue::FieldValue(JsonArray&& value) noexcept : array(std::move(value)), type(FieldType::Array) {
|
||||
//std::println("Array ! move");
|
||||
}
|
||||
|
||||
FieldValue::~FieldValue() {
|
||||
_destroy();
|
||||
}
|
||||
|
||||
void FieldValue::_destroy() {
|
||||
if (isString()) {
|
||||
(&string)->std::string::~string();
|
||||
} else if (isObject()) {
|
||||
object.~JsonObject();
|
||||
} else if (isArray()) {
|
||||
array.~JsonArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
119
src/Object.cpp
Normal file
119
src/Object.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
#include "rei-json/json.h"
|
||||
#include "rei-json/Field.h"
|
||||
#include "rei-json/Array.h"
|
||||
#include <print>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
namespace rei::json {
|
||||
|
||||
JsonObject::JsonObject() {
|
||||
|
||||
};
|
||||
|
||||
bool JsonObject::contains(const std::string& key) const {
|
||||
return elements.contains(key);
|
||||
}
|
||||
|
||||
JsonObject& JsonObject::set(const std::string& key, int value) {
|
||||
elements.insert_or_assign(key, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonObject& JsonObject::set(const std::string& key, bool value) {
|
||||
elements.insert_or_assign(key, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonObject& JsonObject::set(const std::string& key, const std::string& value) {
|
||||
elements.insert_or_assign(key, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonObject& JsonObject::set(const std::string& key, const std::string&& value) {
|
||||
elements.insert_or_assign(key, std::move(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonObject& JsonObject::setNull(const std::string& key) {
|
||||
elements.insert_or_assign(key, std::move(FieldValue{FieldType::Null}));
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonObject& JsonObject::set(const std::string& key, const char * value) {
|
||||
elements.insert_or_assign(key, std::string(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
int JsonObject::getNumber(const std::string& key) {
|
||||
if (!contains(key)) {
|
||||
throw std::logic_error(std::string("Trying to access non existing key: ") + key);
|
||||
}
|
||||
return elements.at(key).asNumber();
|
||||
}
|
||||
|
||||
bool JsonObject::getBool(const std::string& key) {
|
||||
if (!contains(key)) {
|
||||
throw std::logic_error(std::string("Trying to access non existing key: ") + key);
|
||||
}
|
||||
return elements.at(key).asBool();
|
||||
}
|
||||
|
||||
std::string JsonObject::getString(const std::string& key) {
|
||||
if (!contains(key)) {
|
||||
throw std::logic_error(std::string("Trying to access non existing key: ") + key);
|
||||
}
|
||||
return elements.at(key).asString();
|
||||
}
|
||||
|
||||
JsonObject& JsonObject::getObject(const std::string& key) {
|
||||
if (!contains(key)) {
|
||||
throw std::logic_error(std::string("Trying to access non existing key: ") + key);
|
||||
}
|
||||
return elements.at(key).asObject();
|
||||
}
|
||||
|
||||
JsonArray& JsonObject::getArray(const std::string& key) {
|
||||
if (!contains(key)) {
|
||||
throw std::logic_error(std::string("Trying to access non existing key: ") + key);
|
||||
}
|
||||
return elements.at(key).asArray();
|
||||
}
|
||||
|
||||
bool JsonObject::hasNull(const std::string& key) {
|
||||
if (!contains(key)) {
|
||||
throw std::logic_error(std::string("Trying to access non existing key: ") + key);
|
||||
}
|
||||
return elements.at(key).isNull();
|
||||
}
|
||||
|
||||
JsonObject& JsonObject::addArray(const std::string& key, const JsonArray& array) {
|
||||
elements.insert_or_assign(key, array);
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonObject& JsonObject::addArray(const std::string& key, JsonArray&& array) {
|
||||
elements.insert_or_assign(key, std::move(array));
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonObject& JsonObject::addObject(const std::string& key, const JsonObject& object) {
|
||||
elements.insert_or_assign(key, object);
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonObject& JsonObject::addObject(const std::string& key, JsonObject&& object) {
|
||||
elements.insert_or_assign(key, std::move(object));
|
||||
return *this;
|
||||
}
|
||||
|
||||
void JsonObject::forEach(std::function<void(FieldValue&, const std::string& key)> func) {
|
||||
for (auto& element : elements) {
|
||||
func(element.second, element.first);
|
||||
}
|
||||
}
|
||||
|
||||
size_t JsonObject::count() const {
|
||||
return elements.size();
|
||||
}
|
||||
}
|
202
src/parse.cpp
Normal file
202
src/parse.cpp
Normal file
|
@ -0,0 +1,202 @@
|
|||
#include "rei-json/Array.h"
|
||||
#include "rei-json/Field.h"
|
||||
#include "rei-json/json.h"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <charconv>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <print>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
namespace rei::json {
|
||||
|
||||
void goToNextDoubleQuote(const std::string& str, int* const i) {
|
||||
while (str[*i] != '"') {
|
||||
if (str[*i] == '\\') {
|
||||
++(*i);
|
||||
}
|
||||
++(*i);
|
||||
}
|
||||
}
|
||||
|
||||
void skipDigits(const std::string& str, int* const i) {
|
||||
while (isdigit(str[*i]) || str[*i] == '-') {
|
||||
++(*i);
|
||||
}
|
||||
}
|
||||
|
||||
void skipWhiteSpaces(const std::string& str, int* const i) {
|
||||
while (str[*i] == ' ' || str[*i] == '\t' || str[*i] == '\n' || str[*i] == '\v') {
|
||||
++(*i);
|
||||
}
|
||||
}
|
||||
|
||||
void parseObject(const std::string &str, int *i, JsonObject* object);
|
||||
void parseArray(const std::string &str, int *i, JsonArray* object);
|
||||
|
||||
void parseObjectField(const std::string &str, int *i, JsonObject* object) {
|
||||
if (str[*i] != '"') {
|
||||
throw "wrong json: expected opening quote";
|
||||
}
|
||||
++(*i);
|
||||
const int keyStart = *i;
|
||||
goToNextDoubleQuote(str, i);
|
||||
const int keyLength = *i - keyStart;
|
||||
const std::string key = str.substr(keyStart, keyLength);
|
||||
++(*i);
|
||||
skipWhiteSpaces(str, i);
|
||||
if (str[*i] != ':') {
|
||||
throw "wrong json: expect 2 dots";
|
||||
}
|
||||
++(*i);
|
||||
skipWhiteSpaces(str, i);
|
||||
|
||||
if (str[*i] == '"') {
|
||||
++(*i);
|
||||
const int valueStart = *i;
|
||||
goToNextDoubleQuote(str, i);
|
||||
const int valueLength = *i - valueStart;
|
||||
const std::string valueStr = str.substr(valueStart, valueLength);
|
||||
object->set(key, std::move(valueStr));
|
||||
++(*i);
|
||||
} else if (isdigit(str[*i]) || str[*i] == '-') {
|
||||
const int valueStart = *i;
|
||||
skipDigits(str, i);
|
||||
const int valueLength = *i - valueStart;
|
||||
const std::string valueStr = str.substr(valueStart, valueLength);
|
||||
int value;
|
||||
std::from_chars(str.c_str() + valueStart, str.c_str() + valueStart + valueLength, value);
|
||||
object->set(key, value);
|
||||
} else if (std::string_view{str}.substr(*i, 4) == "true") {
|
||||
const int valueStart = *i;
|
||||
*i += 4;
|
||||
const int valueLength = *i - valueStart;
|
||||
object->set(key, true);
|
||||
} else if (std::string_view{str}.substr(*i, 5) == "false") {
|
||||
const int valueStart = *i;
|
||||
*i += 5;
|
||||
const int valueLength = *i - valueStart;
|
||||
object->set(key, false);
|
||||
} else if (std::string_view{str}.substr(*i, 4) == "null") {
|
||||
*i += 4;
|
||||
object->setNull(key);
|
||||
} else if (str[*i] == '{'){
|
||||
JsonObject newObject{};
|
||||
parseObject(str, i, &newObject);
|
||||
object->addObject(key, std::move(newObject));
|
||||
} else if (str[*i] == '['){
|
||||
JsonArray newArray{};
|
||||
parseArray(str, i, &newArray);
|
||||
object->addArray(key, std::move(newArray));
|
||||
}
|
||||
}
|
||||
|
||||
void parseArrayField(const std::string &str, int *i, JsonArray* array) {
|
||||
skipWhiteSpaces(str, i);
|
||||
if (str[*i] == '"') {
|
||||
++(*i);
|
||||
const int valueStart = *i;
|
||||
goToNextDoubleQuote(str, i);
|
||||
const int valueLength = *i - valueStart;
|
||||
const std::string valueStr = str.substr(valueStart, valueLength);
|
||||
array->push(std::move(valueStr));
|
||||
++(*i);
|
||||
} else if (isdigit(str[*i])) {
|
||||
const int valueStart = *i;
|
||||
skipDigits(str, i);
|
||||
const int valueLength = *i - valueStart;
|
||||
const std::string valueStr = str.substr(valueStart, valueLength);
|
||||
array->push(std::stoi(valueStr));
|
||||
} else if (std::string_view{str}.substr(*i, 4) == "true") {
|
||||
const int valueStart = *i;
|
||||
*i += 4;
|
||||
const int valueLength = *i - valueStart;
|
||||
array->push(true);
|
||||
} else if (std::string_view{str}.substr(*i, 4) == "null") {
|
||||
*i += 4;
|
||||
array->pushNull();
|
||||
} else if (std::string_view{str}.substr(*i, 5) == "false") {
|
||||
const int valueStart = *i;
|
||||
*i += 5;
|
||||
const int valueLength = *i - valueStart;
|
||||
array->push(false);
|
||||
} else if (str[*i] == '{'){
|
||||
JsonObject newObject{};
|
||||
parseObject(str, i, &newObject);
|
||||
array->pushObject(std::move(newObject));
|
||||
} else if (str[*i] == '['){
|
||||
JsonArray newArray{};
|
||||
parseArray(str, i, &newArray);
|
||||
array->pushArray(std::move(newArray));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void parseArray(const std::string &str, int *i, JsonArray* object) {
|
||||
if (str[*i] != '[') {
|
||||
throw "wrong json";
|
||||
}
|
||||
++(*i);
|
||||
skipWhiteSpaces(str, i);
|
||||
while (str[*i] != ']') {
|
||||
parseArrayField(str, i, object);
|
||||
skipWhiteSpaces(str, i);
|
||||
if (str[*i] == ',') {
|
||||
++(*i);
|
||||
skipWhiteSpaces(str, i);
|
||||
if (str[*i] == ']') {
|
||||
throw "wrong json: extra comma on last array field";
|
||||
}
|
||||
} else if (str[*i] != ']') {
|
||||
throw "wrong json: expected array end";
|
||||
}
|
||||
}
|
||||
++(*i);
|
||||
}
|
||||
|
||||
void parseObject(const std::string &str, int *i, JsonObject* object) {
|
||||
if (str[*i] != '{') {
|
||||
throw "wrong json";
|
||||
}
|
||||
++(*i);
|
||||
skipWhiteSpaces(str, i);
|
||||
while (str[*i] != '}') {
|
||||
if (str[*i] != '"') {
|
||||
throw "wrong json: expect opening key quote";
|
||||
}
|
||||
parseObjectField(str, i, object);
|
||||
skipWhiteSpaces(str, i);
|
||||
if (str[*i] == ',') {
|
||||
++(*i);
|
||||
skipWhiteSpaces(str, i);
|
||||
if (str[*i] == '}') {
|
||||
throw "wrong json: extra comma on last object field";
|
||||
}
|
||||
} else if (str[*i] != '}') {
|
||||
const std::string sstr = str.substr(*i - 1);
|
||||
throw "wrong json: expected object end";
|
||||
}
|
||||
}
|
||||
++(*i);
|
||||
|
||||
}
|
||||
|
||||
std::variant<JsonObject, JsonArray> parse(const std::string& jsonStr) {
|
||||
int i = 0;
|
||||
skipWhiteSpaces(jsonStr, &i);
|
||||
if (jsonStr[i] == '{') {
|
||||
JsonObject object{};
|
||||
parseObject(jsonStr, &i, &object);
|
||||
return std::move(object);
|
||||
} else if (jsonStr[i] == '[') {
|
||||
JsonArray array{};
|
||||
parseArray(jsonStr, &i, &array);
|
||||
return std::move(array);
|
||||
}
|
||||
throw std::runtime_error("Bad json syntax");
|
||||
}
|
||||
}
|
103
src/toString.cpp
Normal file
103
src/toString.cpp
Normal file
|
@ -0,0 +1,103 @@
|
|||
#include "rei-json/Array.h"
|
||||
#include "rei-json/Field.h"
|
||||
#include "rei-json/Object.h"
|
||||
#include "rei-json/json.h"
|
||||
#include <cctype>
|
||||
#include <print>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
namespace rei::json {
|
||||
|
||||
void arrayToString(JsonArray& array, std::string& str, int currentIndentation, int indentIncrement);
|
||||
|
||||
inline void objectToString(JsonObject& object, std::string& str, int currentIndentation, int indentIncrement) {
|
||||
int elementCount = object.count();
|
||||
//std::string str = "{";
|
||||
str += "{";
|
||||
currentIndentation += indentIncrement;
|
||||
std::string padding = "\n" + std::string(currentIndentation, ' ');
|
||||
object.forEach([&](FieldValue& element, const std::string& key) {
|
||||
str += padding + "\"" + key + "\": ";
|
||||
if (element.isNumber()) {
|
||||
str += std::to_string(element.asNumber());
|
||||
} else if (element.isBool()) {
|
||||
str += element.asBool() ? "true" : "false";
|
||||
} else if (element.isNull()) {
|
||||
str += "null";
|
||||
} else if (element.isString()) {
|
||||
str += "\"" + element.asString() + "\"";
|
||||
} else if (element.isObject()) {
|
||||
objectToString(element.asObject(), str, currentIndentation, indentIncrement);
|
||||
} else if (element.isArray()) {
|
||||
arrayToString(element.asArray(), str, currentIndentation, indentIncrement);
|
||||
}
|
||||
elementCount--;
|
||||
if (elementCount > 0) {
|
||||
str += ",";
|
||||
}
|
||||
});
|
||||
currentIndentation -= indentIncrement;
|
||||
if (object.count() > 0) {
|
||||
str += "\n" + std::string(currentIndentation, ' ');
|
||||
}
|
||||
str += "}";
|
||||
//return str;
|
||||
}
|
||||
|
||||
inline void arrayToString(JsonArray& array, std::string& str, int currentIndentation, int indentIncrement) {
|
||||
int elementCount = array.count();
|
||||
//std::string str = "[";
|
||||
str += "[";
|
||||
currentIndentation += indentIncrement;
|
||||
std::string padding = "\n" + std::string(currentIndentation, ' ');
|
||||
array.forEach([&](FieldValue& element, int index) {
|
||||
str += padding;
|
||||
if (element.isNumber()) {
|
||||
str += std::to_string(element.asNumber());
|
||||
} else if (element.isBool()) {
|
||||
str += element.asBool() ? "true" : "false";
|
||||
} else if (element.isNull()) {
|
||||
str += "null";
|
||||
} else if (element.isString()) {
|
||||
str += "\"" + element.asString() + "\"";
|
||||
} else if (element.isObject()) {
|
||||
objectToString(element.asObject(), str, currentIndentation, indentIncrement);
|
||||
} else if (element.isArray()) {
|
||||
arrayToString(element.asArray(), str, currentIndentation, indentIncrement);
|
||||
}
|
||||
--elementCount;
|
||||
if (elementCount > 0) {
|
||||
str += ",";
|
||||
}
|
||||
});
|
||||
currentIndentation -= indentIncrement;
|
||||
if (array.count() > 0) {
|
||||
str += "\n" + std::string(currentIndentation, ' ');
|
||||
}
|
||||
str += "]";
|
||||
//return str;
|
||||
}
|
||||
|
||||
std::string toString(std::variant<JsonObject, JsonArray>& element) {
|
||||
if (std::holds_alternative<JsonObject>(element)) {
|
||||
return toString(std::get<JsonObject>(element));
|
||||
} else if (std::holds_alternative<JsonArray>(element)) {
|
||||
return toString(std::get<JsonArray>(element));
|
||||
|
||||
}
|
||||
throw std::runtime_error("Bad json format");
|
||||
}
|
||||
|
||||
std::string toString(JsonObject& object) {
|
||||
std::string str;
|
||||
objectToString(object, str, 0, 4);
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string toString(JsonArray& array) {
|
||||
std::string str;
|
||||
arrayToString(array, str, 0, 4);
|
||||
return str;
|
||||
}
|
||||
}
|
50
tests/parsing.cpp
Normal file
50
tests/parsing.cpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#include "rei-json/Object.h"
|
||||
#include "rei-json/json.h"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <string>
|
||||
|
||||
TEST_CASE("Parsing json object") {
|
||||
std::string jsonStr = R"({
|
||||
"keyArray": [
|
||||
42,
|
||||
"elemString",
|
||||
"",
|
||||
true,
|
||||
false,
|
||||
null
|
||||
],
|
||||
"keyBooleanFalse": false,
|
||||
"keyBooleanTrue": true,
|
||||
"keyEmptyString": "",
|
||||
"keyNegativeNumber": -13,
|
||||
"keyNull": null,
|
||||
"keyObject": {
|
||||
"keyNumberOnObject": 42
|
||||
},
|
||||
"keyPositiveNumber": 12,
|
||||
"keyString": "YEP"
|
||||
})";
|
||||
auto json = rei::json::parse(jsonStr);
|
||||
auto& objectJson = std::get<rei::json::JsonObject>(json);
|
||||
|
||||
REQUIRE(objectJson.getNumber("keyPositiveNumber") == 12);
|
||||
REQUIRE(objectJson.getNumber("keyNegativeNumber") == -13);
|
||||
REQUIRE(objectJson.getBool("keyBooleanTrue") == true);
|
||||
REQUIRE(objectJson.getBool("keyBooleanFalse") == false);
|
||||
REQUIRE(objectJson.getString("keyString") == "YEP");
|
||||
REQUIRE(objectJson.getString("keyEmptyString") == "");
|
||||
REQUIRE(objectJson.getObject("keyObject").getNumber("keyNumberOnObject") == 42);
|
||||
REQUIRE(objectJson.hasNull("keyNull") == true);
|
||||
REQUIRE(objectJson.hasNull("keyPositiveNumber") == false);
|
||||
REQUIRE_THROWS(objectJson.getNumber("nonExistantKey"));
|
||||
|
||||
auto& arrayJson = objectJson.getArray("keyArray");
|
||||
REQUIRE(arrayJson.getNumber(0) == 42);
|
||||
REQUIRE(arrayJson.getString(1) == "elemString");
|
||||
REQUIRE(arrayJson.getString(2) == "");
|
||||
REQUIRE(arrayJson.getBool(3) == true);
|
||||
REQUIRE(arrayJson.getBool(4) == false);
|
||||
REQUIRE(arrayJson.hasNull(5));
|
||||
REQUIRE_THROWS(arrayJson.getNumber(99));
|
||||
}
|
||||
|
54
tests/stringify.cpp
Normal file
54
tests/stringify.cpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#include "rei-json/json.h"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <string>
|
||||
|
||||
TEST_CASE("Stringify json") {
|
||||
auto objectJson = rei::json::JsonObject{};
|
||||
objectJson
|
||||
.set("keyPositiveNumber", 12)
|
||||
.set("keyNegativeNumber", -13)
|
||||
.set("keyBooleanTrue", true)
|
||||
.set("keyBooleanFalse", false)
|
||||
.set("keyString", "YEP")
|
||||
.set("keyEmptyString", "")
|
||||
.setNull("keyNull");
|
||||
|
||||
rei::json::JsonObject obj{};
|
||||
obj.set("keyNumberOnObject", 42);
|
||||
|
||||
rei::json::JsonArray array{};
|
||||
array.push(42);
|
||||
array.push("elemString");
|
||||
array.push("");
|
||||
array.push(true);
|
||||
array.push(false);
|
||||
array.pushNull();
|
||||
|
||||
objectJson.addObject("keyObject", std::move(obj));
|
||||
objectJson.addArray("keyArray", std::move(array));
|
||||
|
||||
std::string expectedJson = R"({
|
||||
"keyArray": [
|
||||
42,
|
||||
"elemString",
|
||||
"",
|
||||
true,
|
||||
false,
|
||||
null
|
||||
],
|
||||
"keyBooleanFalse": false,
|
||||
"keyBooleanTrue": true,
|
||||
"keyEmptyString": "",
|
||||
"keyNegativeNumber": -13,
|
||||
"keyNull": null,
|
||||
"keyObject": {
|
||||
"keyNumberOnObject": 42
|
||||
},
|
||||
"keyPositiveNumber": 12,
|
||||
"keyString": "YEP"
|
||||
})";
|
||||
|
||||
REQUIRE(rei::json::toString(objectJson) == expectedJson);
|
||||
}
|
||||
|
||||
|
51
tests/usage.cpp
Normal file
51
tests/usage.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#include "rei-json/json.h"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <print>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
TEST_CASE("Basic json") {
|
||||
auto objectJson = rei::json::JsonObject{};
|
||||
objectJson
|
||||
.set("keyPositiveNumber", 12)
|
||||
.set("keyNegativeNumber", -13)
|
||||
.set("keyBooleanTrue", true)
|
||||
.set("keyBooleanFalse", false)
|
||||
.set("keyString", "YEP")
|
||||
.set("keyEmptyString", "")
|
||||
.setNull("keyNull");
|
||||
|
||||
rei::json::JsonObject obj{};
|
||||
obj.set("keyNumberOnObject", 42);
|
||||
|
||||
rei::json::JsonArray array{};
|
||||
array.push(42);
|
||||
array.push("elemString");
|
||||
array.push("");
|
||||
array.push(true);
|
||||
array.push(false);
|
||||
array.pushNull();
|
||||
|
||||
objectJson.addObject("keyObject", std::move(obj));
|
||||
objectJson.addArray("keyArray", std::move(array));
|
||||
|
||||
REQUIRE(objectJson.getNumber("keyPositiveNumber") == 12);
|
||||
REQUIRE(objectJson.getNumber("keyNegativeNumber") == -13);
|
||||
REQUIRE(objectJson.getBool("keyBooleanTrue") == true);
|
||||
REQUIRE(objectJson.getBool("keyBooleanFalse") == false);
|
||||
REQUIRE(objectJson.getString("keyString") == "YEP");
|
||||
REQUIRE(objectJson.getString("keyEmptyString") == "");
|
||||
REQUIRE(objectJson.getObject("keyObject").getNumber("keyNumberOnObject") == 42);
|
||||
REQUIRE(objectJson.hasNull("keyNull") == true);
|
||||
REQUIRE(objectJson.hasNull("keyPositiveNumber") == false);
|
||||
REQUIRE_THROWS(objectJson.getNumber("nonExistantKey"));
|
||||
|
||||
auto& arrayJson = objectJson.getArray("keyArray");
|
||||
REQUIRE(arrayJson.getNumber(0) == 42);
|
||||
REQUIRE(arrayJson.getString(1) == "elemString");
|
||||
REQUIRE(arrayJson.getString(2) == "");
|
||||
REQUIRE(arrayJson.getBool(3) == true);
|
||||
REQUIRE(arrayJson.getBool(4) == false);
|
||||
REQUIRE(arrayJson.hasNull(5));
|
||||
REQUIRE_THROWS(arrayJson.getNumber(99));
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue