#pragma once #include "json.hpp" #include "../loader/Log.hpp" #include #include namespace geode { struct JsonChecker; 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_same_v || std::is_same_v ) { return value_t::string; } return value_t::null; } bool jsonConvertibleTo(value_t value, value_t to) { 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&); struct JsonMaybeObject; struct GEODE_DLL JsonMaybeSomething { protected: JsonChecker& m_checker; nlohmann::json& m_json; std::string m_hierarchy; bool m_hasValue; void setError(std::string const& error); public: nlohmann::json& json() { return m_json; } JsonMaybeSomething( JsonChecker& checker, nlohmann::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(); } }; struct GEODE_DLL JsonMaybeValue : JsonMaybeSomething { bool m_inferType = true; JsonMaybeValue( JsonChecker& checker, nlohmann::json& json, std::string const& hierarchy, bool hasValue ) : JsonMaybeSomething(checker, json, hierarchy, hasValue) {} template JsonMaybeValue as() { if (this->isError()) return *this; if (!jsonConvertibleTo(m_json.type(), T)) { this->setError( m_hierarchy + ": Invalid type \"" + m_json.type_name() + "\", expected \"" + jsonValueTypeToString(T) + "\"" ); } m_inferType = false; return *this; } template JsonMaybeValue asOneOf() { if (this->isError()) return *this; bool isOneOf = (... || jsonConvertibleTo(m_json.type(), T)); if (!isOneOf) { this->setError( m_hierarchy + ": Invalid type \"" + m_json.type_name() + "\", expected one of \"" + (jsonValueTypeToString(T), ...) + "\"" ); } m_inferType = false; return *this; } template JsonMaybeValue is() { if (this->isError()) return *this; m_hasValue = jsonConvertibleTo(m_json.type(), T); m_inferType = false; return *this; } template JsonMaybeValue validate(JsonValueValidator validator) { if (this->isError()) return *this; try { if (!validator(m_json.get())) { this->setError(m_hierarchy + ": Invalid value format"); } } catch(...) { this->setError( m_hierarchy + ": Invalid type \"" + std::string(m_json.type_name()) + "\"" ); } return *this; } template JsonMaybeValue inferType() { if (this->isError() || !m_inferType) return *this; return this->as()>(); } JsonMaybeValue intoRaw(nlohmann::json& target) { if (this->isError()) return *this; target = m_json; return *this; } template JsonMaybeValue into(T& target) { return this->intoAs(target); } template JsonMaybeValue intoAs(T& target) { this->inferType(); if (this->isError()) return *this; try { target = m_json.get(); } catch(...) { this->setError( m_hierarchy + ": Invalid type \"" + std::string(m_json.type_name()) + "\"" ); } return *this; } template T get() { this->inferType(); if (this->isError()) return T(); try { return m_json.get(); } catch(...) { this->setError( m_hierarchy + ": Invalid type to get \"" + std::string(m_json.type_name()) + "\"" ); } return T(); } JsonMaybeObject obj(); template struct Iterator { std::vector m_values; using iterator = std::vector::iterator; using const_iterator = 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(); } }; Iterator iterate() { this->as(); Iterator iter; if (this->isError()) return iter; size_t i = 0; for (auto& obj : m_json) { iter.m_values.emplace_back( m_checker, obj, m_hierarchy + "." + std::to_string(i++), m_hasValue ); } return iter; } Iterator> items() { this->as(); Iterator> iter; if (this->isError()) return iter; for (auto& [k, v] : m_json.items()) { iter.m_values.emplace_back(k, JsonMaybeValue( m_checker, v, m_hierarchy + "." + k, m_hasValue )); } return iter; } }; struct GEODE_DLL JsonMaybeObject : JsonMaybeSomething { std::set m_knownKeys; JsonMaybeObject( JsonChecker& checker, nlohmann::json& json, std::string const& hierarchy, bool hasValue ) : JsonMaybeSomething(checker, json, hierarchy, hasValue) {} void addKnownKey(std::string const& key) { m_knownKeys.insert(key); } nlohmann::json& json() { return m_json; } JsonMaybeValue emptyValue() { return JsonMaybeValue(m_checker, m_json, "", false); } JsonMaybeValue has(std::string const& key) { if (this->isError()) return emptyValue(); this->addKnownKey(key); if (!m_json.contains(key) || m_json[key].is_null()) { return emptyValue(); } return JsonMaybeValue(m_checker, m_json[key], key, true); } JsonMaybeValue needs(std::string const& key) { if (this->isError()) return emptyValue(); this->addKnownKey(key); if (!m_json.contains(key)) { this->setError( m_hierarchy + " is missing required key \"" + key + "\"" ); return emptyValue(); } return JsonMaybeValue(m_checker, m_json[key], key, true); } void checkUnknownKeys() { for (auto& [key, _] : m_json.items()) { if (!m_knownKeys.count(key)) { Log::get() << m_hierarchy + " contains unknown key \"" + key + "\""; } } } }; struct GEODE_DLL JsonChecker { std::variant m_result; nlohmann::json& m_json; JsonChecker( nlohmann::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); } JsonMaybeObject root(std::string const& hierarchy) { if (!m_json.is_object()) { m_result = hierarchy + ": Root is not an object"; return JsonMaybeObject(*this, m_json, hierarchy, false); } return JsonMaybeObject(*this, m_json, hierarchy, true); } }; }