308 lines
8.8 KiB
C++
308 lines
8.8 KiB
C++
// Copyright (c) 2024 MIT License All rights reserved
|
|
|
|
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <limits>
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <cctype>
|
|
|
|
class JsonParser
|
|
{
|
|
public:
|
|
// Constructor that loads and parses JSON from specified file
|
|
explicit JsonParser(const std::string_view filename);
|
|
|
|
// Retrieves value for given dot-separated path, returns empty string if not found
|
|
std::string getValue(const std::string_view path) const
|
|
{
|
|
std::string lower_path{path};
|
|
std::transform(lower_path.begin(), lower_path.end(), lower_path.begin(), ::tolower);
|
|
|
|
if (auto it = values.find(lower_path); it != values.end()) {
|
|
return it->second;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// Converts string to unsigned char with range validation
|
|
static bool convertStringToUnsignedChar(const std::string_view str, unsigned char& result);
|
|
|
|
// Converts string to unsigned short with range validation
|
|
static bool convertStringToUnsignedShort(const std::string_view str, unsigned short& result);
|
|
|
|
// Converts string to unsigned int with range validation
|
|
static bool convertStringToUnsignedInt(const std::string_view str, unsigned int& result);
|
|
|
|
// Converts string to unsigned long with range validation
|
|
static bool convertStringToUnsignedLong(const std::string_view str, unsigned long& result);
|
|
|
|
// Returns whether the JSON file was successfully loaded and parsed
|
|
constexpr bool IsLoaded() const noexcept { return is_loaded; }
|
|
|
|
private:
|
|
std::map<std::string, std::string> values; // Flat map of dot-separated paths to string values
|
|
bool is_loaded{false}; // Success flag for file loading and parsing
|
|
|
|
// Recursively parses JSON object and builds flat key-value map with dot notation
|
|
void parseObject(const std::string& json, size_t& pos, const std::string& path);
|
|
|
|
// Skips whitespace characters in JSON string starting from given position
|
|
size_t skipWhitespace(const std::string& json, size_t pos) const noexcept;
|
|
|
|
// Parses JSON string literal and returns unescaped content
|
|
std::string parseString(const std::string& json, size_t& pos);
|
|
|
|
// Parses JSON number and returns string representation
|
|
std::string parseNumber(const std::string& json, size_t& pos);
|
|
|
|
// Parses JSON value (string, number, object) and stores in values map
|
|
void parseValue(const std::string& json, size_t& pos, const std::string& key);
|
|
|
|
// Helper template for string to unsigned integer conversion with type safety
|
|
template<typename T>
|
|
static bool convertStringToUnsigned(const std::string_view str, T& result);
|
|
};
|
|
|
|
// Constructor implementation - loads and parses JSON file
|
|
JsonParser::JsonParser(const std::string_view filename)
|
|
{
|
|
std::ifstream file{std::string{filename}};
|
|
if (!file.is_open()) {
|
|
std::cerr << "Error: Cannot open JSON file: " << filename << std::endl;
|
|
return;
|
|
}
|
|
|
|
std::ostringstream buffer;
|
|
buffer << file.rdbuf();
|
|
std::string json_content = buffer.str();
|
|
|
|
try {
|
|
size_t pos = 0;
|
|
parseObject(json_content, pos, "");
|
|
is_loaded = true;
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "Error parsing JSON: " << e.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
// Template implementation for safe string to unsigned conversion
|
|
template<typename T>
|
|
bool JsonParser::convertStringToUnsigned(const std::string_view str, T& result)
|
|
{
|
|
try {
|
|
unsigned long ul = std::stoul(std::string{str});
|
|
if (ul > std::numeric_limits<T>::max()) {
|
|
return false;
|
|
}
|
|
result = static_cast<T>(ul);
|
|
return true;
|
|
} catch (const std::invalid_argument&) {
|
|
return false;
|
|
} catch (const std::out_of_range&) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Converts string to unsigned char with validation
|
|
bool JsonParser::convertStringToUnsignedChar(const std::string_view str, unsigned char& result)
|
|
{
|
|
return convertStringToUnsigned(str, result);
|
|
}
|
|
|
|
// Converts string to unsigned short with validation
|
|
bool JsonParser::convertStringToUnsignedShort(const std::string_view str, unsigned short& result)
|
|
{
|
|
return convertStringToUnsigned(str, result);
|
|
}
|
|
|
|
// Converts string to unsigned int with validation
|
|
bool JsonParser::convertStringToUnsignedInt(const std::string_view str, unsigned int& result)
|
|
{
|
|
return convertStringToUnsigned(str, result);
|
|
}
|
|
|
|
// Converts string to unsigned long with validation
|
|
bool JsonParser::convertStringToUnsignedLong(const std::string_view str, unsigned long& result)
|
|
{
|
|
return convertStringToUnsigned(str, result);
|
|
}
|
|
|
|
// Skips whitespace characters and returns next non-whitespace position
|
|
size_t JsonParser::skipWhitespace(const std::string& json, size_t pos) const noexcept
|
|
{
|
|
while (pos < json.length() && std::isspace(json[pos])) {
|
|
++pos;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
// Parses JSON string literal, handling escape sequences
|
|
std::string JsonParser::parseString(const std::string& json, size_t& pos)
|
|
{
|
|
if (json[pos] != '"') {
|
|
throw std::runtime_error("Expected '\"' at start of string");
|
|
}
|
|
++pos; // Skip opening quote
|
|
|
|
std::string result;
|
|
while (pos < json.length() && json[pos] != '"') {
|
|
if (json[pos] == '\\') {
|
|
++pos;
|
|
if (pos >= json.length()) {
|
|
throw std::runtime_error("Unterminated escape sequence");
|
|
}
|
|
// Handle basic escape sequences
|
|
switch (json[pos]) {
|
|
case '"': result += '"'; break;
|
|
case '\\': result += '\\'; break;
|
|
case '/': result += '/'; break;
|
|
case 'b': result += '\b'; break;
|
|
case 'f': result += '\f'; break;
|
|
case 'n': result += '\n'; break;
|
|
case 'r': result += '\r'; break;
|
|
case 't': result += '\t'; break;
|
|
default: result += json[pos]; break;
|
|
}
|
|
} else {
|
|
result += json[pos];
|
|
}
|
|
++pos;
|
|
}
|
|
|
|
if (pos >= json.length() || json[pos] != '"') {
|
|
throw std::runtime_error("Unterminated string");
|
|
}
|
|
++pos; // Skip closing quote
|
|
return result;
|
|
}
|
|
|
|
// Parses JSON number and returns string representation
|
|
std::string JsonParser::parseNumber(const std::string& json, size_t& pos)
|
|
{
|
|
size_t start = pos;
|
|
|
|
// Handle optional minus sign
|
|
if (json[pos] == '-') {
|
|
++pos;
|
|
}
|
|
|
|
// Parse digits
|
|
while (pos < json.length() && std::isdigit(json[pos])) {
|
|
++pos;
|
|
}
|
|
|
|
// Handle decimal point and fractional part
|
|
if (pos < json.length() && json[pos] == '.') {
|
|
++pos;
|
|
while (pos < json.length() && std::isdigit(json[pos])) {
|
|
++pos;
|
|
}
|
|
}
|
|
|
|
// Handle exponent
|
|
if (pos < json.length() && (json[pos] == 'e' || json[pos] == 'E')) {
|
|
++pos;
|
|
if (pos < json.length() && (json[pos] == '+' || json[pos] == '-')) {
|
|
++pos;
|
|
}
|
|
while (pos < json.length() && std::isdigit(json[pos])) {
|
|
++pos;
|
|
}
|
|
}
|
|
|
|
return json.substr(start, pos - start);
|
|
}
|
|
|
|
// Parses JSON value and stores it in the values map
|
|
void JsonParser::parseValue(const std::string& json, size_t& pos, const std::string& key)
|
|
{
|
|
pos = skipWhitespace(json, pos);
|
|
|
|
if (pos >= json.length()) {
|
|
throw std::runtime_error("Unexpected end of JSON");
|
|
}
|
|
|
|
if (json[pos] == '"') {
|
|
// String value - store directly
|
|
std::string value = parseString(json, pos);
|
|
std::string lower_key = key;
|
|
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower);
|
|
values[lower_key] = value;
|
|
} else if (json[pos] == '{') {
|
|
// Nested object - recurse with current key as path prefix
|
|
parseObject(json, pos, key);
|
|
} else if (std::isdigit(json[pos]) || json[pos] == '-') {
|
|
// Number value - store as string
|
|
std::string value = parseNumber(json, pos);
|
|
std::string lower_key = key;
|
|
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower);
|
|
values[lower_key] = value;
|
|
} else {
|
|
// Skip other JSON types (true, false, null, arrays)
|
|
while (pos < json.length() && json[pos] != ',' && json[pos] != '}') {
|
|
++pos;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recursively parses JSON object and builds flat key-value structure
|
|
void JsonParser::parseObject(const std::string& json, size_t& pos, const std::string& path)
|
|
{
|
|
pos = skipWhitespace(json, pos);
|
|
|
|
if (pos >= json.length() || json[pos] != '{') {
|
|
throw std::runtime_error("Expected '{' at start of object");
|
|
}
|
|
++pos; // Skip opening brace
|
|
|
|
pos = skipWhitespace(json, pos);
|
|
|
|
// Handle empty object
|
|
if (pos < json.length() && json[pos] == '}') {
|
|
++pos;
|
|
return;
|
|
}
|
|
|
|
while (pos < json.length()) {
|
|
pos = skipWhitespace(json, pos);
|
|
|
|
// Parse key
|
|
if (json[pos] != '"') {
|
|
throw std::runtime_error("Expected string key in object");
|
|
}
|
|
std::string key = parseString(json, pos);
|
|
|
|
pos = skipWhitespace(json, pos);
|
|
|
|
// Expect colon
|
|
if (pos >= json.length() || json[pos] != ':') {
|
|
throw std::runtime_error("Expected ':' after object key");
|
|
}
|
|
++pos;
|
|
|
|
// Build full path with dot notation
|
|
std::string full_key = path.empty() ? key : path + "." + key;
|
|
|
|
// Parse value
|
|
parseValue(json, pos, full_key);
|
|
|
|
pos = skipWhitespace(json, pos);
|
|
|
|
// Check for continuation or end
|
|
if (pos < json.length() && json[pos] == ',') {
|
|
++pos; // Skip comma
|
|
continue;
|
|
} else if (pos < json.length() && json[pos] == '}') {
|
|
++pos; // Skip closing brace
|
|
break;
|
|
} else {
|
|
throw std::runtime_error("Expected ',' or '}' in object");
|
|
}
|
|
}
|
|
} |