JSON library made for Geode. Main feature is preserving insertion order, along with the use of Geode's Result library.
#include <matjson.hpp>
auto result = matjson::parse(R"(
{
"hello": "world",
"foo": {
"bar": 2000
}
}
)");
if (!result) {
println("Failed to parse json: {}", result.unwrapErr());
}
matjson::Value object = result.unwrap();
object["hello"].asString().unwrap(); // world
object["foo"]["bar"].asInt().unwrap(); // 2000
object.isObject(); // true
object.isString(); // false
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.
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 header, which is also well documented.
To parse JSON into a matjson::Value
, you can simply use the matjson::parse
method
Result<matjson::Value, matjson::ParseError> matjson::parse(std::string_view str);
Result<matjson::Value, matjson::ParseError> matjson::parse(std::istream& stream);
GEODE_UNWRAP_INTO(matjson::Value value, matjson::parse("[1, 2, 3, 4]"));
std::ifstream file("file.json");
GEODE_UNWRAP_INTO(matjson::Value value2, matjson::parse(file));
To convert a matjson::Value
back to a JSON string, use dump()
/// 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);
matjson::Value object = matjson::makeObject({
{"x", 10}
});
std::string jsonStr = object.dump();
// {
// "x": 10
// }
std::string compact = object.dump(matjson::NO_INDENTATION);
// {"x":10}
There are many different ways to access inner properties
Result<Value&> Value::get(std::string_view key);
Result<Value&> 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 tostd::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.
matjson::Value object = matjson::parse(R"(
{
"hello": "world",
"foo": {
"bar": 2000
"numbers": [123, false, "not a number actually"]
}
}
)").unwrap();
object["hello"]; // matjson::Value("world")
object.get("hello"); // Ok(matjson::Value("world"))
object["foo"]["bar"].asInt(); // Ok(2000)
object.contains("sup"); // false
// will insert "sup" into object as long as its not const.
// behaves like std::map::operator[]
object["sup"];
object["nya"] = 123;
// not the case for .get
object.get("something else"); // Err("key not found")
It's possible to (de)serialize your own types into json, by specializing matjson::Serialize<T>
.
#include <matjson.hpp>
struct User {
std::string name;
int age;
};
template <>
struct matjson::Serialize<User> {
static geode::Result<User> 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 toJson(const User& user) {
return matjson::makeObject({
{ "name", user.name },
{ "age", user.age }
});
}
};
int main() {
User user = { "mat", 123 };
// gets implicitly converted into json
matjson::Value value = user;
value.dump(matjson::NO_INDENTATION); // {"name":"mat","age":123}
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>().unwrap();
user2.name; // "hello"
}
Serialization is available for many standard library containers in #include <matjson/std.hpp>
, as mentioned in Support for standard library types.
There is also experimental support for reflection.
Objects and arrays have special methods, since they can be iterated and mutated.
// Does nothing if 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<matjson::Value>)
constructor.
matjson::Value obj = matjson::makeObject({
{"hello", "world"},
{"number", "123"}
});
for (auto& [key, value] : obj) {
// key is std::string
// value is matjson::Value
}
// just iterates through the values
for (auto& value : obj) {
// value is matjson::Value
}
obj.set("foo", true);
matjson::Value arr({ 1, 2, "hello", true });
arr[0]; // 1
arr[2]; // "hello"
arr[9]; // null
arr.get(9); // Err("index out of bounds")
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<matjson::Value>& vec = arr.asArray().unwrap();
The library now has experimental support for reflection using the qlibs/reflect library, which only supports aggregate types.
Including the optional header will define JSON serialization for any type it can, making them able to be used with matjson instantly
#include <matjson/reflect.hpp>
struct Stats {
int hunger;
int health;
};
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<User>().unwrap();
}
There is built in support for serializing std containers by including an optional header:
Supported classes (given T is serializable):
std::vector<T>
std::span<T>
std::map<std::string, T>
std::unordered_map<std::string, T>
std::set<T>
std::unordered_set<T>
std::optional<T>
-null
is prioritized asstd::nullopt
std::shared_ptr<T>
- same as abovestd::unique_ptr<T>
- same as above
#include <matjson/std.hpp>
std::vector<int> nums = { 1, 2, 3 };
std::map<std::string, std::string> map = {
{ "hello", "foo" }
};
matjson::Value jsonNums = nums;
matjson::Value jsonMap = map;
This library was initially made due to concerns of the compile speeds of nlohmann/json, which was included all over the place in geode.
Nowadays the main benefit of this library is integration with Geode's Result type.
Mods can use whatever JSON library they want to, if matjson doesnt fit their needs