1
0
json/json.hpp
2025-07-31 22:24:41 -05:00

299 lines
6.9 KiB
C++

#pragma once
#include <variant>
#include <vector>
#include <unordered_map>
#include <string>
#include <string_view>
#include <charconv>
#include <stdexcept>
#include <fstream>
#include <sstream>
class JsonParser {
public:
struct JsonValue;
using JsonNull = std::monostate;
using JsonBool = bool;
using JsonNumber = double;
using JsonString = std::string;
using JsonArray = std::vector<JsonValue>;
using JsonObject = std::unordered_map<std::string, JsonValue>;
struct JsonValue {
std::variant<JsonNull, JsonBool, JsonNumber, JsonString, JsonArray, JsonObject> value;
template<typename T>
bool is() const { return std::holds_alternative<T>(value); }
template<typename T>
const T& as() const { return std::get<T>(value); }
template<typename T>
T& as() { return std::get<T>(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<char>(codepoint);
} else if (codepoint < 0x800) {
result += static_cast<char>(0xC0 | (codepoint >> 6));
result += static_cast<char>(0x80 | (codepoint & 0x3F));
} else {
result += static_cast<char>(0xE0 | (codepoint >> 12));
result += static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F));
result += static_cast<char>(0x80 | (codepoint & 0x3F));
}
break;
}
default: throw std::runtime_error("Invalid escape sequence");
}
} else if (static_cast<unsigned char>(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());
}
};