/** * @file json_handling/json_loader.cc * @brief Parses curated location JSON input into strongly typed Location * records with strict field validation and descriptive error reporting. */ #include "json_handling/json_loader.h" #include #include #include #include #include #include #include static std::string ReadRequiredString(const boost::json::object& object, const char* key) { const boost::json::value* value = object.if_contains(key); if (value == nullptr || !value->is_string()) { throw std::runtime_error(std::string("Missing or invalid string field: ") + key); } const std::string_view text = value->as_string(); return std::string(text); } static double ReadRequiredNumber(const boost::json::object& object, const char* key) { const boost::json::value* value = object.if_contains(key); if (value == nullptr || !value->is_number()) { throw std::runtime_error(std::string("Missing or invalid numeric field: ") + key); } return value->to_number(); } static std::vector ReadRequiredStringArray( const boost::json::object& object, const char* key) { const boost::json::value* value = object.if_contains(key); if (value == nullptr || !value->is_array()) { throw std::runtime_error(std::string("Missing or invalid string array field: ") + key); } const auto& array = value->as_array(); std::vector items; items.reserve(array.size()); for (const auto& item : array) { if (!item.is_string()) { throw std::runtime_error(std::string("Missing or invalid string array field: ") + key); } items.emplace_back(item.as_string()); } return items; } std::vector JsonLoader::LoadLocations( const std::filesystem::path& filepath) { std::ifstream input(filepath); if (!input.is_open()) { throw std::runtime_error("Failed to open locations file: " + filepath.string()); } std::stringstream buffer; buffer << input.rdbuf(); const std::string content = buffer.str(); boost::system::error_code error; boost::json::value root = boost::json::parse(content, error); if (error) { throw std::runtime_error("Failed to parse locations JSON: " + error.message()); } if (!root.is_array()) { throw std::runtime_error( "Invalid locations JSON: root element must be an array"); } std::vector locations; const auto& items = root.as_array(); locations.reserve(items.size()); for (const auto& item : items) { if (!item.is_object()) { throw std::runtime_error( "Invalid locations JSON: each entry must be an object"); } const auto& object = item.as_object(); locations.push_back(Location{ .city = ReadRequiredString(object, "city"), .state_province = ReadRequiredString(object, "state_province"), .iso3166_2 = ReadRequiredString(object, "iso3166_2"), .country = ReadRequiredString(object, "country"), .iso3166_1 = ReadRequiredString(object, "iso3166_1"), .local_languages = ReadRequiredStringArray(object, "local_languages"), .latitude = ReadRequiredNumber(object, "latitude"), .longitude = ReadRequiredNumber(object, "longitude"), }); } spdlog::info("[JsonLoader] Loaded {} locations from {}", locations.size(), filepath.string()); return locations; }