#pragma once #include "../external/json/json.hpp" #include "../loader/Log.hpp" #include #include namespace geode { template 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 = nlohmann::detail::value_t; 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::boolean: return "boolean"; case value_t::binary: return "binary"; case value_t::discarded: return "discarded"; case value_t::number_integer: return "integer"; case value_t::number_unsigned: return "integer"; case value_t::number_float: return "number"; } } template constexpr value_t getJsonType() { if constexpr (std::is_same_v) { return value_t::boolean; } else if constexpr (std::is_floating_point_v) { return value_t::number_float; } else if constexpr (std::is_unsigned_v) { return value_t::number_unsigned; } else if constexpr (std::is_integral_v) { return value_t::number_integer; } 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_float || value == value_t::number_integer || value == value_t::number_unsigned) { return to == value_t::number_float || to == value_t::number_integer || to == value_t::number_unsigned; } return value == to; } } template using JsonValueValidator = std::function; template struct JsonMaybeObject; template struct JsonMaybeValue; template struct JsonMaybeSomething { protected: JsonChecker& m_checker; Json& m_json; std::string m_hierarchy; bool m_hasValue; friend struct JsonMaybeObject; friend struct JsonMaybeValue; GEODE_DLL void setError(std::string const& error); public: GEODE_DLL Json& json(); GEODE_DLL JsonMaybeSomething( JsonChecker& checker, Json& json, std::string const& hierarchy, bool hasValue ); GEODE_DLL bool isError() const; GEODE_DLL operator bool() const; }; template struct JsonMaybeValue : public JsonMaybeSomething { bool m_inferType = true; GEODE_DLL JsonMaybeValue( JsonChecker& checker, Json& json, std::string const& hierarchy, bool hasValue ); GEODE_DLL JsonMaybeSomething& self(); template JsonMaybeValue& as() { if (this->isError()) return *this; if (!jsonConvertibleTo(self().m_json.type(), T)) { this->setError( self().m_hierarchy + ": Invalid type \"" + self().m_json.type_name() + "\", expected \"" + jsonValueTypeToString(T) + "\"" ); } m_inferType = false; return *this; } GEODE_DLL JsonMaybeValue& array(); template JsonMaybeValue& asOneOf() { if (this->isError()) return *this; bool isOneOf = (... || jsonConvertibleTo(self().m_json.type(), T)); if (!isOneOf) { this->setError( self().m_hierarchy + ": Invalid type \"" + self().m_json.type_name() + "\", expected one of \"" + (jsonValueTypeToString(T), ...) + "\"" ); } m_inferType = false; return *this; } template JsonMaybeValue& is() { if (this->isError()) return *this; self().m_hasValue = jsonConvertibleTo(self().m_json.type(), T); m_inferType = false; return *this; } template JsonMaybeValue& validate(JsonValueValidator validator) { if (this->isError()) return *this; try { if (!validator(self().m_json.template get())) { this->setError(self().m_hierarchy + ": Invalid value format"); } } catch (...) { this->setError( self().m_hierarchy + ": Invalid type \"" + std::string(self().m_json.type_name()) + "\"" ); } return *this; } template JsonMaybeValue& validate(bool (*validator)(T const&)) { return this->validate(std::function(validator)); } template JsonMaybeValue& inferType() { if (this->isError() || !m_inferType) return *this; return this->as()>(); } template JsonMaybeValue& intoRaw(T& target) { if (this->isError()) return *this; target = self().m_json; return *this; } template JsonMaybeValue& into(T& target) { return this->intoAs(target); } template JsonMaybeValue& into(std::optional& target) { return this->intoAs>(target); } template JsonMaybeValue& intoAs(T& target) { this->inferType(); if (this->isError()) return *this; try { target = self().m_json.template get(); } catch (...) { this->setError( self().m_hierarchy + ": Invalid type \"" + std::string(self().m_json.type_name()) + "\"" ); } return *this; } template T get() { this->inferType(); if (this->isError()) return T(); try { return self().m_json.template get(); } catch (...) { this->setError( self().m_hierarchy + ": Invalid type to get \"" + std::string(self().m_json.type_name()) + "\"" ); } return T(); } GEODE_DLL JsonMaybeObject obj(); template struct Iterator { std::vector m_values; using iterator = typename std::vector::iterator; using const_iterator = typename std::vector::const_iterator; iterator begin() { return m_values.begin(); } iterator end() { return m_values.end(); } const_iterator begin() const { return m_values.begin(); } const_iterator end() const { return m_values.end(); } }; GEODE_DLL JsonMaybeValue at(size_t i); GEODE_DLL Iterator> iterate(); GEODE_DLL Iterator>> items(); }; template struct JsonMaybeObject : JsonMaybeSomething { std::set m_knownKeys; GEODE_DLL JsonMaybeObject( JsonChecker& checker, Json& json, std::string const& hierarchy, bool hasValue ); GEODE_DLL JsonMaybeSomething& self(); GEODE_DLL void addKnownKey(std::string const& key); GEODE_DLL Json& json(); GEODE_DLL JsonMaybeValue emptyValue(); GEODE_DLL JsonMaybeValue has(std::string const& key); GEODE_DLL JsonMaybeValue needs(std::string const& key); GEODE_DLL void checkUnknownKeys(); }; template struct JsonChecker { std::variant m_result; Json& m_json; GEODE_DLL JsonChecker(Json& json); GEODE_DLL bool isError() const; GEODE_DLL std::string getError() const; GEODE_DLL JsonMaybeValue root(std::string const& hierarchy); }; }