diff --git a/README.md b/README.md index 67f874f..11d1ac4 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,13 @@ # MATjson -json library i made quickly for geode. preserves insertion order for objects - -- this was made to be easy to compile -- numbers are internally stored as double -- not made to handle extremely nested objects +JSON library made for [Geode](https://github.com/geode-sdk/json). Main feature is preserving insertion order, along with the use of [Geode's Result library](https://github.com/geode-sdk/result). ## Example ```cpp #include -auto object = matjson::parse(R"( +auto result = matjson::parse(R"( { "hello": "world", "foo": { @@ -19,38 +15,105 @@ auto object = matjson::parse(R"( } } )"); +if (!result) { + println("Failed to parse json: {}", result.unwrapErr()); +} +matjson::Value object = result.unwrap(); -object["hello"].as_string(); // world -object["foo"]["bar"].as_int(); // 2000 -object.is_object(); // true -object.is_string(); // false +object["hello"].asString().unwrap(); // world +object["foo"]["bar"].asInt().unwrap(); // 2000 +object.isObject(); // true +object.isString(); // false ``` +# Index + +* [Usage](#usage) + * [Parsing JSON](#parsing-json) + * [Dumping JSON](#dumping-json) + * [Accessing object properties](#accessing-object-properties) + * [Serializing custom types](#serialzing-custom-types) + * [Objects and arrays](#objects-and-arrays) + * [Reflection](#reflection) + * [Support for standard library types](#support-for-standard-library-types) + # Usage -## Loading from a file -For the sake of simplicity, the library can only parse from a string, so you will have to load the whole file in memory yourself. +The main class of the library is `matjson::Value`, which can represent any JSON value. You can access its type by using the `type()` method, or one of the `isString()` methods. +```cpp +matjson::Value value = ...; + +matjson::Type type = value.type(); +if (type == matjson::Type::Object) { + // ... +} +// same as +if (value.isObject()) { + // ... +} +``` +Anything not documented here can be found in the [matjson.hpp](https://github.com/geode-sdk/json/blob/main/include/matjson.hpp) header, which is also well documented. + +## Parsing JSON + +To parse JSON into a `matjson::Value`, you can simply use the `matjson::parse` method + +```cpp +Result matjson::parse(std::string_view str); +Result matjson::parse(std::istream& stream); +``` ```cpp -std::string file_contents = ... +GEODE_UNWRAP_INTO(matjson::Value value, matjson::parse("[1, 2, 3, 4]")); -auto object = matjson::parse(file_contents); +std::ifstream file("file.json"); +GEODE_UNWRAP_INTO(matjson::Value value2, matjson::parse(file)); ``` -## Saving to a file -The library can also only directly output a string. +## Dumping JSON + +To convert a `matjson::Value` back to a JSON string, use `dump()` +```cpp +/// Dumps the JSON value to a string, with a given indentation. +/// If the given indentation is matjson::NO_INDENTATION, the json is compacted. +/// If the given indentation is matjson::TAB_INDENTATION, the json is indented with tabs. +/// @param indentationSize The number of spaces to use for indentation +/// @return The JSON string +/// @note Due to limitations in the JSON format, NaN and Infinity float values get converted to null. +/// This behavior is the same as many other JSON libraries. +std::string Value::dump(int indentation = 4); +``` ```cpp -matjson::Value object = ... +matjson::Value object = matjson::makeObject({ + {"x", 10} +}); -std::string file_contents = object.dump(); -write_to_file("epic.json", file_contents); +std::string jsonStr = object.dump(); +// { +// "x": 10 +// } + +std::string compact = object.dump(matjson::NO_INDENTATION); +// {"x":10} ``` `Value::dump` can also control indentation, see [Dumping to string](#dumping-to-string) -## Accessing values -There are many different ways to access inner values, depending on your needs: +## Accessing object properties + +There are many different ways to access inner properties ```cpp -auto object = matjson::parse(R"( +Result Value::get(std::string_view key); +Result Value::get(std::size_t index); +// These are special! +Value& Value::operator[](std::string_view key); +Value& Value::operator[](std::size_t index); +``` +As noted, `operator[]` is special because: +* If the `matjson::Value` is not const, `operator[](std::string_view key)` will insert into the object, akin to `std::map`. \ +(This does not apply if the value is an array) +* Otherwise, trying to access missing values will return a special `null` JSON value. This value cannot be mutated. +```cpp +matjson::Value object = matjson::parse(R"( { "hello": "world", "foo": { @@ -58,22 +121,13 @@ auto object = matjson::parse(R"( "numbers": [123, false, "not a number actually"] } } -)"); - -object["hello"].as_string(); // "world" -object["hello"].as(); // "world" -object.get("hello"); // "world" +)").unwrap(); -object["foo"]["bar"].as_int(); // 2000 -object["foo"]["bar"].as(); // 2000 -object["foo"].get("bar"); // 2000 +object["hello"]; // matjson::Value("world") +object.get("hello"); // Ok(matjson::Value("world")) -object["foo"]["numbers"][0].as_int(); // 123 -object["foo"]["numbers"][0].as(); // 123 -object["foo"]["numbers"].get(0); // 123 +object["foo"]["bar"].asInt(); // Ok(2000) -object.try_get("foo"); // std::optional(...) -object.try_get("sup"); // std::nullopt object.contains("sup"); // false // will insert "sup" into object as long as its not const. @@ -81,35 +135,34 @@ object.contains("sup"); // false object["sup"]; object["nya"] = 123; -object["hello"].is_string(); // true -object["hello"].is(); // true +// not the case for .get +object.get("something else"); // Err("key not found") ``` -## Custom types -It is possible to (de)serialize your own types into json, by specializing `matjson::Serialize`. +## Serializing custom types + +It's possible to (de)serialize your own types into json, by specializing `matjson::Serialize`. ```cpp +#include + struct User { std::string name; int age; -} +}; template <> struct matjson::Serialize { - static User from_json(const matjson::Value& value) { - return User { - .name = value["name"].as_string(), - .age = value["age"].as_int(), - }; + static geode::Result fromJson(const matjson::Value& value) { + GEODE_UNWRAP_INTO(std::string name, value["name"].asString()); + GEODE_UNWRAP_INTO(int age, value["age"].asInt()); + return geode::Ok(User { name, age }); } - static matjson::Value to_json(const User& user) { - return matjson::Object { + static matjson::Value toJson(const User& user) { + return matjson::makeObject({ { "name", user.name }, { "age", user.age } - }; + }); } - // You can also implement this method: - // > static bool is_json(const matjson::Value& value); - // It is only used if you do value.is(); }; int main() { @@ -123,48 +176,120 @@ int main() { value["name"] = "hello"; // you have to use the templated methods for accessing custom types! // it will *not* implicitly convert to User - User user2 = value.as(); + User user2 = value.as().unwrap(); user2.name; // "hello" } ``` -## Dumping to string -To save a json value to a file or to pretty print it you can use the `matjson::Value::dump` method. +Serialization is available for many standard library containers in `#include `, as mentioned in [Support for standard library types](#support-for-standard-library-types). + +There is also experimental support for [reflection](#reflection). + +## Objects and arrays + +Objects and arrays have special methods, since they can be iterated and mutated. ```cpp -// from the previous section -matjson::Value value = User { "mat", 123 }; +// Does nothing is Value is not an object +void Value::set(std::string_view key, Value value); +// Does nothing if Value is not an array +void Value::push(Value value); +// Clears all entries from an array or object +void Value::clear(); +// Does nothing if Value is not an object +bool Value::erase(std::string_view key); +// Returns 0 if Value is not an object or array +std::size_t Value::size() const; +``` +To make objects you can use the `matjson::makeObject` function, and for arrays you can use the `Value::Value(std::vector)` constructor. +```cpp +matjson::Value obj = matjson::makeObject({ + {"hello", "world"}, + {"number", "123"} +}); + +for (auto& [key, value] : obj) { + // key is std::string + // value is matjson::Value +} -// by default dumps with 4 spaces -value.dump(); -// { -// "name": "mat", -// "age": 123 -// } +// just iterates through the values +for (auto& value : obj) { + // value is matjson::Value +} -value.dump(1); // 1 space indentation -// { -// "name": "mat", -// "age": 123 -// } +obj.set("foo", true); -// will create a compact json with no extra whitepace -value.dump(matjson::NO_INDENTATION); -value.dump(0); // same as above -// {"name":"mat","age":123} +matjson::Value arr({ 1, 2, "hello", true }); -value.dump(matjson::TAB_INDENTATION); // will indent with tabs -// like imagine '{\n\t"name": "mat",\n\t"age": 123\n}' +for (auto& value : obj) { + // values +} + +arr.push(nullptr); +arr.dump(matjson::NO_INDENTATION); // [1,2,"hello",true,null] + +// If you need direct access to the vector, for some reason +std::vector& vec = arr.asArray().unwrap(); ``` -## misc +## Reflection + +The library now has experimental support for reflection using the [qlibs/reflect](https://github.com/qlibs/reflect) library, which only supports [aggregate types](https://en.cppreference.com/w/cpp/language/aggregate_initialization). + +Including the optional header will define JSON serialization for any type it can, making them able to be used with matjson instantly + ```cpp -// default constructs to a json object, for convenience -matjson::Value value; +#include + +struct Stats { + int hunger; + int health; +}; -// null is nullptr -value["hello"] = nullptr; -value["hello"].is_null(); // true +struct User { + std::string name; + Stats stats; + bool registered; +}; + +int main() { + User user = User { + .name = "Joe", + .stats = Stats { + .hunger = 0, + .health = 100 + }, + .registered = true + }; + + matjson::Value value = user; + + value.dump(); + // { + // "name": "Joe", + // "stats": { + // "hunger": 0, + // "health": 0 + // }, + // "registered": true + // } + + User u2 = value.as().unwrap(); +} ``` -## ok thats like everything -just look at the main header for other things its not that hard to read the code +## Support for standard library types + +There is built in support for serializing std containers by including an optional header: + +```cpp +#include + +std::vector nums = { 1, 2, 3 }; +std::map map = { + { "hello", "foo" } +}; + +matjson::Value jsonNums = nums; +matjson::Value jsonMap = map; +``` diff --git a/include/matjson.hpp b/include/matjson.hpp index 5d24979..4e7f1fb 100644 --- a/include/matjson.hpp +++ b/include/matjson.hpp @@ -170,12 +170,12 @@ namespace matjson { /// Returns the value associated with the given index /// @param index Array index /// @return The value associated with the index, or an error if the index is out of bounds. - geode::Result get(size_t index); + geode::Result get(std::size_t index); /// Returns the value associated with the given index /// @param index Array index /// @return The value associated with the index, or an error if the index is out of bounds. - geode::Result get(size_t index) const; + geode::Result get(std::size_t index) const; /// Returns the value associated with the given key /// @param key Object key @@ -196,14 +196,14 @@ namespace matjson { /// @return The value associated with the index /// @note If the index is out of bounds, or this is not an array, /// returns a null value - Value& operator[](size_t index); + Value& operator[](std::size_t index); /// Returns the value associated with the given index /// @param index Array index /// @return The value associated with the index /// @note If the index is out of bounds, or this is not an array, /// returns a null value - Value const& operator[](size_t index) const; + Value const& operator[](std::size_t index) const; /// Sets the value associated with the given key /// @param key Object key @@ -369,7 +369,7 @@ namespace matjson { // This is used internally by C++ when destructuring the value, useful for range for loops: // > for (auto const& [key, value] : object) { ... } - template + template requires requires { std::is_same_v, Value>; } decltype(auto) get(T&& value) { if constexpr (Index == 0) { @@ -398,7 +398,7 @@ namespace matjson { // allow destructuring template <> -struct std::tuple_size : std::integral_constant {}; +struct std::tuple_size : std::integral_constant {}; template <> struct std::tuple_element<0, matjson::Value> {