// Copyright (c) 2024 MIT License All rights reserved #pragma once #include #include #include #include #include #include #include #include #include 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 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 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 bool JsonParser::convertStringToUnsigned(const std::string_view str, T& result) { try { unsigned long ul = std::stoul(std::string{str}); if (ul > std::numeric_limits::max()) { return false; } result = static_cast(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"); } } }