#pragma once #include #include #include #include #include #include #include #include #include class JsonParser { public: struct JsonValue; using JsonNull = std::monostate; using JsonBool = bool; using JsonNumber = double; using JsonString = std::string; using JsonArray = std::vector; using JsonObject = std::unordered_map; struct JsonValue { std::variant value; template bool is() const { return std::holds_alternative(value); } template const T& as() const { return std::get(value); } template T& as() { return std::get(value); } }; private: std::string_view input; size_t pos = 0; void skip_whitespace() { while (pos < input.size() && (input[pos] == ' ' || input[pos] == '\t' || input[pos] == '\n' || input[pos] == '\r')) { ++pos; } } char peek() const { return pos < input.size() ? input[pos] : '\0'; } char consume() { return pos < input.size() ? input[pos++] : '\0'; } void expect(char expected) { if (consume() != expected) { throw std::runtime_error("Expected '" + std::string(1, expected) + "'"); } } JsonValue parse_null() { if (input.substr(pos, 4) == "null") { pos += 4; return JsonValue{JsonNull{}}; } throw std::runtime_error("Invalid null"); } JsonValue parse_bool() { if (input.substr(pos, 4) == "true") { pos += 4; return JsonValue{true}; } if (input.substr(pos, 5) == "false") { pos += 5; return JsonValue{false}; } throw std::runtime_error("Invalid boolean"); } JsonValue parse_number() { size_t start = pos; if (peek() == '-') consume(); if (peek() == '0') { consume(); } else if (peek() >= '1' && peek() <= '9') { consume(); while (peek() >= '0' && peek() <= '9') consume(); } else { throw std::runtime_error("Invalid number"); } if (peek() == '.') { consume(); if (!(peek() >= '0' && peek() <= '9')) { throw std::runtime_error("Invalid decimal"); } while (peek() >= '0' && peek() <= '9') consume(); } if (peek() == 'e' || peek() == 'E') { consume(); if (peek() == '+' || peek() == '-') consume(); if (!(peek() >= '0' && peek() <= '9')) { throw std::runtime_error("Invalid exponent"); } while (peek() >= '0' && peek() <= '9') consume(); } double result; auto [ptr, ec] = std::from_chars(input.data() + start, input.data() + pos, result); if (ec != std::errc{}) { throw std::runtime_error("Number parsing failed"); } return JsonValue{result}; } std::string parse_string_content() { std::string result; result.reserve(32); // optimization for typical strings while (pos < input.size() && peek() != '"') { char c = consume(); if (c == '\\') { if (pos >= input.size()) throw std::runtime_error("Unterminated escape"); char escaped = consume(); switch (escaped) { 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; case 'u': { if (pos + 4 > input.size()) throw std::runtime_error("Invalid unicode escape"); auto hex = input.substr(pos, 4); pos += 4; int codepoint = 0; auto [ptr, ec] = std::from_chars(hex.data(), hex.data() + 4, codepoint, 16); if (ec != std::errc{}) throw std::runtime_error("Invalid unicode hex"); // Simple UTF-8 encoding for BMP if (codepoint < 0x80) { result += static_cast(codepoint); } else if (codepoint < 0x800) { result += static_cast(0xC0 | (codepoint >> 6)); result += static_cast(0x80 | (codepoint & 0x3F)); } else { result += static_cast(0xE0 | (codepoint >> 12)); result += static_cast(0x80 | ((codepoint >> 6) & 0x3F)); result += static_cast(0x80 | (codepoint & 0x3F)); } break; } default: throw std::runtime_error("Invalid escape sequence"); } } else if (static_cast(c) < 0x20) { throw std::runtime_error("Unescaped control character"); } else { result += c; } } if (peek() != '"') throw std::runtime_error("Unterminated string"); return result; } JsonValue parse_string() { expect('"'); auto content = parse_string_content(); expect('"'); return JsonValue{std::move(content)}; } JsonValue parse_array() { expect('['); skip_whitespace(); JsonArray array; if (peek() == ']') { consume(); return JsonValue{std::move(array)}; } while (true) { array.push_back(parse_value()); skip_whitespace(); char next = peek(); if (next == ']') { consume(); break; } else if (next == ',') { consume(); skip_whitespace(); } else { throw std::runtime_error("Expected ',' or ']' in array"); } } return JsonValue{std::move(array)}; } JsonValue parse_object() { expect('{'); skip_whitespace(); JsonObject object; if (peek() == '}') { consume(); return JsonValue{std::move(object)}; } while (true) { if (peek() != '"') throw std::runtime_error("Expected string key"); expect('"'); auto key = parse_string_content(); expect('"'); skip_whitespace(); expect(':'); skip_whitespace(); auto value = parse_value(); object.emplace(std::move(key), std::move(value)); skip_whitespace(); char next = peek(); if (next == '}') { consume(); break; } else if (next == ',') { consume(); skip_whitespace(); } else { throw std::runtime_error("Expected ',' or '}' in object"); } } return JsonValue{std::move(object)}; } JsonValue parse_value() { skip_whitespace(); char c = peek(); switch (c) { case 'n': return parse_null(); case 't': case 'f': return parse_bool(); case '"': return parse_string(); case '[': return parse_array(); case '{': return parse_object(); case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return parse_number(); default: throw std::runtime_error("Unexpected character"); } } public: JsonValue parse(std::string_view json) { input = json; pos = 0; auto result = parse_value(); skip_whitespace(); if (pos < input.size()) { throw std::runtime_error("Extra characters after JSON"); } return result; } JsonValue parse_file(const std::string& filepath) { std::ifstream file(filepath); if (!file.is_open()) { throw std::runtime_error("Cannot open file: " + filepath); } std::ostringstream buffer; buffer << file.rdbuf(); if (file.bad()) { throw std::runtime_error("Error reading file: " + filepath); } return parse(buffer.str()); } };