#pragma once #include "json.hpp" #include "../loader/Log.hpp" #include #include namespace geode { template struct JsonChecker; template struct is_iterable : std::false_type {}; template struct is_iterable())), 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 const char* 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 = bool(*)(T const&); 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; void setError(std::string const& error); public: Json& json() { return m_json; } JsonMaybeSomething( JsonChecker& checker, Json& json, std::string const& hierarchy, bool hasValue ) : m_checker(checker), m_json(json), m_hierarchy(hierarchy), m_hasValue(hasValue) {} bool isError() const; operator bool() const { return !isError(); } }; template struct JsonMaybeValue : public JsonMaybeSomething { bool m_inferType = true; JsonMaybeValue( JsonChecker& checker, Json& json, std::string const& hierarchy, bool hasValue ) : JsonMaybeSomething(checker, json, hierarchy, hasValue) {} JsonMaybeSomething& self() { return *static_cast*>(this); } 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; } JsonMaybeValue array() { this->as(); return *this; } 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 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(); } 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(); } }; JsonMaybeValue at(size_t i) { this->as(); if (this->isError()) return *this; if (self().m_json.size() <= i) { this->setError( self().m_hierarchy + ": has " + std::to_string(self().m_json.size()) + "items " ", expected to have at least " + std::to_string(i + 1) ); return *this; } return JsonMaybeValue( self().m_checker, self().m_json.at(i), self().m_hierarchy + "." + std::to_string(i), self().m_hasValue ); } Iterator> iterate() { this->as(); Iterator> iter; if (this->isError()) return iter; size_t i = 0; for (auto& obj : self().m_json) { iter.m_values.emplace_back( self().m_checker, obj, self().m_hierarchy + "." + std::to_string(i++), self().m_hasValue ); } return iter; } Iterator>> items() { this->as(); Iterator>> iter; if (this->isError()) return iter; for (auto& [k, v] : self().m_json.items()) { iter.m_values.emplace_back(k, JsonMaybeValue( self().m_checker, v, self().m_hierarchy + "." + k, self().m_hasValue )); } return iter; } }; template struct JsonMaybeObject : JsonMaybeSomething { std::set m_knownKeys; JsonMaybeObject( JsonChecker& checker, Json& json, std::string const& hierarchy, bool hasValue ) : JsonMaybeSomething(checker, json, hierarchy, hasValue) {} JsonMaybeSomething& self() { return *static_cast*>(this); } void addKnownKey(std::string const& key) { m_knownKeys.insert(key); } Json& json() { return self().m_json; } JsonMaybeValue emptyValue() { return JsonMaybeValue(self().m_checker, self().m_json, "", false); } JsonMaybeValue has(std::string const& key) { this->addKnownKey(key); if (this->isError()) return emptyValue(); if (!self().m_json.contains(key) || self().m_json[key].is_null()) { return emptyValue(); } return JsonMaybeValue(self().m_checker, self().m_json[key], key, true); } JsonMaybeValue needs(std::string const& key) { this->addKnownKey(key); if (this->isError()) return emptyValue(); if (!self().m_json.contains(key)) { this->setError( self().m_hierarchy + " is missing required key \"" + key + "\"" ); return emptyValue(); } return JsonMaybeValue(self().m_checker, self().m_json[key], key, true); } void checkUnknownKeys() { for (auto& [key, _] : self().m_json.items()) { if (!m_knownKeys.count(key)) { // log::debug(self().m_hierarchy + " contains unknown key \"" + key + "\""); log::debug("{} contains unknown key \"{}\"", self().m_hierarchy, key); } } } }; template struct JsonChecker { std::variant m_result; Json& m_json; JsonChecker(Json& json) : m_json(json), m_result(std::monostate()) {} bool isError() const { return std::holds_alternative(m_result); } std::string getError() const { return std::get(m_result); } JsonMaybeValue root(std::string const& hierarchy) { return JsonMaybeValue(*this, m_json, hierarchy, true); } }; template void JsonMaybeSomething::setError(std::string const& error) { m_checker.m_result = error; } template bool JsonMaybeSomething::isError() const { return m_checker.isError() || !m_hasValue; } template JsonMaybeObject JsonMaybeValue::obj() { this->as(); return JsonMaybeObject( self().m_checker, self().m_json, self().m_hierarchy, self().m_hasValue ); } }