mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-06-01 01:54:00 +00:00
111 lines
3.6 KiB
C++
111 lines
3.6 KiB
C++
/**
|
|
* @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 <spdlog/spdlog.h>
|
|
|
|
#include <boost/json.hpp>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
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<double>();
|
|
}
|
|
|
|
static std::vector<std::string> 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<std::string> 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<Location> 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<Location> 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;
|
|
}
|