/*! * \file support/json_parser.h * \brief Helps to parse JSON strings and objects. */ #ifndef MLC_LLM_SUPPORT_JSON_PARSER_H_ #define MLC_LLM_SUPPORT_JSON_PARSER_H_ #include #include #include #include #include #include #include "result.h" namespace mlc { namespace llm { namespace json { using ::tvm::ffi::json::Array; using ::tvm::ffi::json::Object; using ::tvm::ffi::json::Value; /*! * \brief Parse a JSON string to a JSON object. * \param json_str The JSON string to parse. * \return The parsed JSON object. */ inline Object ParseToJSONObject(const std::string& json_str) { tvm::ffi::String err; Value result = ::tvm::ffi::json::Parse(json_str, &err); TVM_FFI_CHECK(err.empty(), ValueError) << "Failed to parse JSON: err. The JSON string is:" << json_str; auto opt = result.try_cast(); TVM_FFI_CHECK(opt.has_value(), ValueError) << "The given string is not a JSON object: " << json_str; return *opt; } /*! * \brief Parse a JSON string to a JSON object. * \param json_str The JSON string to parse. * \return The parsed JSON object, or the error message. */ inline Result ParseToJSONObjectWithResultReturn(const std::string& json_str) { using TResult = Result; tvm::ffi::String err; Value result = ::tvm::ffi::json::Parse(json_str, &err); if (!err.empty()) { return TResult::Error("Failed to parse JSON: err. The JSON string is: " + json_str + ". The error is " + std::string(err)); } auto opt = result.try_cast(); if (!opt.has_value()) { return TResult::Error("ValueError: The given string is not a JSON object: " + json_str); } return TResult::Ok(*opt); } /*! * \brief Lookup a JSON object by a key, and convert it to a given type. * \param json The JSON object to look up. * \param key The key to look up. * \tparam ValueType The type to be converted to. * \return The converted value. */ template ValueType Lookup(const Object& json, const std::string& key); /*! * \brief Lookup a JSON array by an index, and convert it to a given type. * \param json The JSON array to look up. * \param index The index to look up. * \tparam ValueType The type to be converted to. * \return The converted value. */ template ValueType Lookup(const Array& json, int index); /*! * \brief Lookup a JSON object by a key, and convert it to a given type. * If the key doesn't exist or has null value, the default value is returned. * \param json The JSON object to look up. * \param key The key to look up. * \tparam ValueType The type to be converted to. * \return The converted value, or the default value if the key doesn't exist or has null value. */ template inline ValueType LookupOrDefault(const Object& json, const std::string& key, const ValueType& default_value) { if (json.count(key) == 0 || json.at(key) == nullptr) { return default_value; } auto opt = json.at(key).try_cast(); TVM_FFI_CHECK(opt.has_value(), ValueError) << "key `" << key << "` has unexpected type"; return *opt; } /*! * \brief Lookup a JSON object by a key, and convert it to a given type. * If the key doesn't exist or has null value, return std::nullopt. * \param json The JSON object to look up. * \param key The key to look up. * \tparam ValueType The type to be converted to. * \return The converted value, or std::nullopt if the value doesn't exist or has null value. */ template inline std::optional LookupOptional(const Object& json, const std::string& key) { if (json.count(key) == 0 || json.at(key) == nullptr) { return std::nullopt; } auto opt = json.at(key).try_cast(); TVM_FFI_CHECK(opt.has_value(), ValueError) << "key `" << key << "` has unexpected type"; return *opt; } /*! * \brief Lookup a JSON object by a key, and convert it to a given type. * \param json The JSON object to look up. * \param key The key to look up. * \tparam ValueType The type to be converted to. * \return The converted value, or the error message. */ template inline Result LookupWithResultReturn(const Object& json, const std::string& key) { using TResult = Result; if (json.count(key) == 0) { return TResult::Error("ValueError: key \"" + key + "\" not found in the JSON object"); } auto opt = json.at(key).try_cast(); if (!opt.has_value()) { return TResult::Error("ValueError: key \"" + key + "\" has unexpected value type."); } return TResult::Ok(*opt); } /*! * \brief Lookup a JSON object by a key, and convert it to a given type. * If the key doesn't exist or has null value, the default value is returned. * \param json The JSON object to look up. * \param key The key to look up. * \tparam ValueType The type to be converted to. * \return The converted value, or the default value if the key doesn't exist or has null value * , or the error message. */ template inline Result LookupOrDefaultWithResultReturn(const Object& json, const std::string& key, const ValueType& default_value) { using TResult = Result; if (json.count(key) == 0 || json.at(key) == nullptr) { return TResult::Ok(default_value); } auto opt = json.at(key).try_cast(); if (!opt.has_value()) { return TResult::Error("ValueError: key \"" + key + "\" has unexpected value type."); } return TResult::Ok(*opt); } /*! * \brief Lookup a JSON object by a key, and convert it to a given type. * If the key doesn't exist or has null value, return std::nullopt. * \param json The JSON object to look up. * \param key The key to look up. * \tparam ValueType The type to be converted to. * \return The converted value, or std::nullopt if the value doesn't exist or has null value, * , or the error message. */ template inline Result> LookupOptionalWithResultReturn(const Object& json, const std::string& key) { using TResult = Result>; if (json.count(key) == 0 || json.at(key) == nullptr) { return TResult::Ok(std::nullopt); } auto opt = json.at(key).try_cast(); if (!opt.has_value()) { return TResult::Error("ValueError: key \"" + key + "\" has unexpected value type."); } return TResult::Ok(*opt); } // Implementation details /*! \brief Shape extension to incorporate symbolic shapes. */ struct SymShapeTuple { tvm::ffi::Shape shape_values; std::vector sym_names; /*! \brief Convert symbolic shape tuple to static shape tuple with model config. */ tvm::ffi::Shape ToStatic(const Object& model_config) { std::vector shape; shape.reserve(shape_values.size()); for (int i = 0; i < static_cast(shape_values.size()); ++i) { if (shape_values[i] != -1) { shape.push_back(shape_values[i]); } else { auto opt = model_config.at(sym_names[i]).try_cast(); TVM_FFI_CHECK(opt.has_value(), ValueError) << "model config is expected to contain \"" << sym_names[i] << "\" as an integer. However, the given config has unexpected type for \"" << sym_names[i] << "\"."; shape.push_back(*opt); } } return tvm::ffi::Shape(std::move(shape)); } }; namespace details { inline DLDataType DTypeFromString(const std::string& s) { return tvm::ffi::StringToDLDataType(s); } inline SymShapeTuple SymShapeTupleFromArray(const Array& shape) { std::vector result; std::vector sym_names; result.reserve(shape.size()); sym_names.reserve(shape.size()); for (int i = 0; i < static_cast(shape.size()); ++i) { const auto& dim = shape[i]; auto str_opt = dim.try_cast(); if (str_opt.has_value()) { result.push_back(-1); sym_names.push_back(*str_opt); } else { auto int_opt = dim.try_cast(); TVM_FFI_CHECK(int_opt.has_value(), ValueError) << "shape has unexpected type"; result.push_back(*int_opt); sym_names.push_back(""); } } return SymShapeTuple{tvm::ffi::Shape(std::move(result)), sym_names}; } } // namespace details template inline ValueType Lookup(const Object& json, const std::string& key) { TVM_FFI_CHECK(json.count(key) != 0, ValueError) << "key `" << key << "` not found in the JSON object"; auto opt = json.at(key).try_cast(); TVM_FFI_CHECK(opt.has_value(), ValueError) << "key `" << key << "` has unexpected type"; return *opt; } template inline ValueType Lookup(const Array& json, int index) { TVM_FFI_ICHECK(index < static_cast(json.size())) << "IndexError: json::array index out of range"; auto opt = json[index].try_cast(); TVM_FFI_ICHECK(opt.has_value()) << "ValueError: value at index `" << index << "` has unexpected type"; return *opt; } template <> inline DLDataType Lookup(const Object& json, const std::string& key) { return details::DTypeFromString(Lookup(json, key)); } template <> inline DLDataType Lookup(const Array& json, int index) { return details::DTypeFromString(Lookup(json, index)); } template <> inline SymShapeTuple Lookup(const Object& json, const std::string& key) { return details::SymShapeTupleFromArray(Lookup(json, key)); } template <> inline SymShapeTuple LookupOrDefault(const Object& json, const std::string& key, const SymShapeTuple& default_value) { if (json.count(key) == 0 || json.at(key) == nullptr) { return default_value; } return details::SymShapeTupleFromArray(Lookup(json, key)); } template <> inline SymShapeTuple Lookup(const Array& json, int index) { return details::SymShapeTupleFromArray(Lookup(json, index)); } } // namespace json } // namespace llm } // namespace mlc #endif // MLC_LLM_SUPPORT_JSON_PARSER_H_