#pragma once #include #include "../loader/Log.hpp" #include #include #include namespace geode { struct JsonChecker; template struct is_iterable : std::false_type {}; template struct is_iterable< T, std::void_t())), decltype(std::end(std::declval()))>> : std::true_type {}; template constexpr bool is_iterable_v = is_iterable::value; namespace { using value_t = matjson::Type; constexpr char const* jsonValueTypeToString(value_t type) { switch (type) { default: case value_t::Null: return "null"; case value_t::Object: return "object"; case value_t::Array: return "array"; case value_t::String: return "string"; case value_t::Bool: return "boolean"; case value_t::Number: return "number"; } } template constexpr value_t getJsonType() { if constexpr (std::is_same_v) { return value_t::Bool; } else if constexpr (std::is_floating_point_v) { return value_t::Number; } else if constexpr (std::is_unsigned_v) { return value_t::Number; } else if constexpr (std::is_integral_v) { return value_t::Number; } else if constexpr (std::is_constructible_v) { return value_t::String; } else if constexpr (is_iterable_v) { return value_t::Array; } return value_t::Null; } bool jsonConvertibleTo(value_t value, value_t to) { // if we don't know the type we're passing into, // everything's valid if (to == value_t::Null) return true; if (value == value_t::Number) { return to == value_t::Number; } return value == to; } } template using JsonValueValidator = std::function; struct JsonMaybeObject; struct JsonMaybeValue; class GEODE_DLL JsonExpectedValue final { protected: class Impl; std::unique_ptr m_impl; JsonExpectedValue(); JsonExpectedValue(Impl* from, matjson::Value& scope, std::string_view key); static const char* matJsonTypeToString(matjson::Type ty); bool hasError() const; void setError(std::string_view error); matjson::Value const& getJSONRef() const; template void setError(fmt::format_string error, Args&&... args) { this->setError(fmt::format(error, std::forward(args)...)); } template std::optional tryGet() { if (this->hasError()) return std::nullopt; if constexpr (std::is_same_v) { return this->getJSONRef(); } else { try { if (this->getJSONRef().is()) { return this->getJSONRef().as(); } else { this->setError( "unexpected type {}", this->matJsonTypeToString(this->getJSONRef().type()) ); } } // matjson can throw variant exceptions too so you need to do this catch(std::exception const& e) { this->setError("unable to parse json: {}", e); } } return std::nullopt; } public: JsonExpectedValue(matjson::Value const& value, std::string_view rootScopeName); ~JsonExpectedValue(); JsonExpectedValue(JsonExpectedValue&&); JsonExpectedValue& operator=(JsonExpectedValue&&); JsonExpectedValue(JsonExpectedValue const&) = delete; JsonExpectedValue& operator=(JsonExpectedValue const&) = delete; /** * Get a copy of the underlying raw JSON value */ matjson::Value json() const; /** * Get the key name of this JSON value. If this is an array index, * returns the index as a string. If this is the root object, * returns the root scope name. */ std::string key() const; /** * Check the type of this JSON value. Does not set an error. If an * error is already set, always returns false */ bool is(matjson::Type type) const; bool isNull() const; bool isBool() const; bool isNumber() const; bool isString() const; bool isArray() const; bool isObject() const; /** * Asserts that this JSON value is of the specified type. If it is * not, an error is set and all subsequent operations are no-ops * @returns Itself */ JsonExpectedValue& assertIs(matjson::Type type); JsonExpectedValue& assertIsNull(); JsonExpectedValue& assertIsBool(); JsonExpectedValue& assertIsNumber(); JsonExpectedValue& assertIsString(); JsonExpectedValue& assertIsArray(); JsonExpectedValue& assertIsObject(); /** * Asserts that this JSON value is one of a list of specified types * @returns Itself */ JsonExpectedValue& assertIs(std::initializer_list type); // -- Dealing with values -- template T get(T const& defaultValue = T()) { if (auto v = this->tryGet()) { return *std::move(v); } return defaultValue; } template JsonExpectedValue& into(T& value) { if (auto v = this->tryGet()) { value = *std::move(v); } return *this; } template JsonExpectedValue& into(std::optional& value) { if (auto v = this->tryGet()) { value.emplace(*std::move(v)); } return *this; } template JsonExpectedValue& mustBe(std::string_view name, auto predicate) requires requires { { predicate(std::declval()) } -> std::convertible_to; } { if (this->hasError()) return *this; if (auto v = this->tryGet()) { if (!predicate(*v)) { this->setError("json value is not {}", name); } } return *this; } template JsonExpectedValue& mustBe(std::string_view name, auto predicate) requires requires { { predicate(std::declval()) } -> std::convertible_to>; } { if (this->hasError()) return *this; if (auto v = this->tryGet()) { auto p = predicate(*v); if (!p) { this->setError("json value is not {}: {}", name, p.unwrapErr()); } } return *this; } // -- Dealing with objects -- /** * Check if this object has an optional key. Asserts that this JSON * value is an object. If the key doesn't exist, returns a * `JsonExpectValue` that does nothing * @returns The key, which is a no-op value if it didn't exist */ JsonExpectedValue has(std::string_view key); /** * Check if this object has an optional key. Asserts that this JSON * value is an object. If the key doesn't exist, sets an error and * returns a `JsonExpectValue` that does nothing * @returns The key, which is a no-op value if it didn't exist */ JsonExpectedValue needs(std::string_view key); /** * Asserts that this JSON value is an object. Get all object * properties */ std::vector> properties(); /** * Asserts that this JSON value is an object. Logs unknown keys to * the console as warnings */ void checkUnknownKeys(); // -- Dealing with arrays -- /** * Asserts that this JSON value is an array. Returns the length of * the array, or 0 on error */ size_t length(); /** * Asserts that this JSON value is an array. Returns the value at * the specified index. If there is no value at that index, sets an * error */ JsonExpectedValue at(size_t index); /** * Asserts that this JSON value is an array. Returns the array items * @warning The old JsonChecker used `items` for iterating object * properties - on this new API that function is called `properties`! */ std::vector items(); operator bool() const; Result<> ok(); template Result ok(T value) { auto ok = this->ok(); if (!ok) { return Err(ok.unwrapErr()); } return Ok(std::forward(value)); } }; GEODE_DLL JsonExpectedValue checkJson(matjson::Value const& json, std::string_view rootScopeName); }