#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace VFJson { struct Value { using Array = std::vector; using Object = std::unordered_map; using Storage = std::variant; Storage Data = nullptr; bool IsObject() const { return std::holds_alternative(Data); } bool IsArray() const { return std::holds_alternative(Data); } bool IsString() const { return std::holds_alternative(Data); } bool IsNumber() const { return std::holds_alternative(Data); } const Object& AsObject() const { static const Object Empty; return IsObject() ? std::get(Data) : Empty; } const Array& AsArray() const { static const Array Empty; return IsArray() ? std::get(Data) : Empty; } const Value& Get(std::string_view Key) const { static const Value Null; const Object& O = AsObject(); auto It = O.find(std::string(Key)); return It == O.end() ? Null : It->second; } double Number(double Fallback = 0.0) const { return IsNumber() ? std::get(Data) : Fallback; } std::int64_t Integer(std::int64_t Fallback = 0) const { return IsNumber() ? static_cast(std::get(Data)) : Fallback; } bool Boolean(bool Fallback = false) const { return std::holds_alternative(Data) ? std::get(Data) : Fallback; } const std::string& String() const { static const std::string Empty; return IsString() ? std::get(Data) : Empty; } }; class Parser { public: explicit Parser(std::string Source) : Text(std::move(Source)) {} bool Parse(Value& Out, std::string& Error) { SkipSpace(); if (!ReadValue(Out, Error)) return false; SkipSpace(); if (Position != Text.size()) { Error = "trailing characters after JSON value"; return false; } return true; } private: std::string Text; size_t Position = 0; void SkipSpace() { while (Position < Text.size() && static_cast(Text[Position]) <= 0x20) ++Position; } bool Consume(char C) { SkipSpace(); if (Position < Text.size() && Text[Position] == C) { ++Position; return true; } return false; } static void AppendUtf8(std::string& Out, std::uint32_t Code) { if (Code <= 0x7f) Out.push_back(static_cast(Code)); else if (Code <= 0x7ff) { Out.push_back(static_cast(0xc0 | (Code >> 6))); Out.push_back(static_cast(0x80 | (Code & 0x3f))); } else if (Code <= 0xffff) { Out.push_back(static_cast(0xe0 | (Code >> 12))); Out.push_back(static_cast(0x80 | ((Code >> 6) & 0x3f))); Out.push_back(static_cast(0x80 | (Code & 0x3f))); } else { Out.push_back(static_cast(0xf0 | (Code >> 18))); Out.push_back(static_cast(0x80 | ((Code >> 12) & 0x3f))); Out.push_back(static_cast(0x80 | ((Code >> 6) & 0x3f))); Out.push_back(static_cast(0x80 | (Code & 0x3f))); } } bool Hex4(std::uint32_t& Out) { if (Position + 4 > Text.size()) return false; Out = 0; for (std::int32_t I = 0; I < 4; ++I) { const char C = Text[Position++]; const std::int32_t N = (C >= '0' && C <= '9') ? C - '0' : (C >= 'a' && C <= 'f') ? C - 'a' + 10 : (C >= 'A' && C <= 'F') ? C - 'A' + 10 : -1; if (N < 0) return false; Out = (Out << 4) | static_cast(N); } return true; } bool ReadString(std::string& Out, std::string& Error) { SkipSpace(); if (Position >= Text.size() || Text[Position++] != '"') { Error = "expected JSON string"; return false; } while (Position < Text.size()) { const unsigned char C = static_cast(Text[Position++]); if (C == '"') return true; if (C < 0x20) { Error = "control character in JSON string"; return false; } if (C != '\\') { Out.push_back(static_cast(C)); continue; } if (Position >= Text.size()) break; const char Escape = Text[Position++]; switch (Escape) { case '"': Out.push_back('"'); break; case '\\': Out.push_back('\\'); break; case '/': Out.push_back('/'); break; case 'b': Out.push_back('\b'); break; case 'f': Out.push_back('\f'); break; case 'n': Out.push_back('\n'); break; case 'r': Out.push_back('\r'); break; case 't': Out.push_back('\t'); break; case 'u': { std::uint32_t Code = 0; if (!Hex4(Code)) { Error = "invalid JSON unicode escape"; return false; } if (Code >= 0xd800 && Code <= 0xdbff) { if (Position + 2 > Text.size() || Text[Position++] != '\\' || Text[Position++] != 'u') { Error = "missing JSON low surrogate"; return false; } std::uint32_t Low = 0; if (!Hex4(Low) || Low < 0xdc00 || Low > 0xdfff) { Error = "invalid JSON low surrogate"; return false; } Code = 0x10000 + ((Code - 0xd800) << 10) + (Low - 0xdc00); } AppendUtf8(Out, Code); break; } default: Error = "invalid JSON escape"; return false; } } Error = "unterminated JSON string"; return false; } bool ReadValue(Value& Out, std::string& Error) { SkipSpace(); if (Position >= Text.size()) { Error = "unexpected end of JSON"; return false; } const char C = Text[Position]; if (C == '"') { std::string S; if (!ReadString(S, Error)) return false; Out.Data = std::move(S); return true; } if (C == '{') { ++Position; Value::Object O; SkipSpace(); if (Consume('}')) { Out.Data = std::move(O); return true; } for (;;) { std::string Key; if (!ReadString(Key, Error) || !Consume(':')) { if (Error.empty()) Error = "expected colon in JSON object"; return false; } Value V; if (!ReadValue(V, Error)) return false; O[std::move(Key)] = std::move(V); if (Consume('}')) break; if (!Consume(',')) { Error = "expected comma in JSON object"; return false; } } Out.Data = std::move(O); return true; } if (C == '[') { ++Position; Value::Array A; SkipSpace(); if (Consume(']')) { Out.Data = std::move(A); return true; } for (;;) { Value V; if (!ReadValue(V, Error)) return false; A.push_back(std::move(V)); if (Consume(']')) break; if (!Consume(',')) { Error = "expected comma in JSON array"; return false; } } Out.Data = std::move(A); return true; } auto Match = [&](std::string_view S) { if (Text.compare(Position, S.size(), S) == 0) { Position += S.size(); return true; } return false; }; if (Match("true")) { Out.Data = true; return true; } if (Match("false")) { Out.Data = false; return true; } if (Match("null")) { Out.Data = nullptr; return true; } const size_t Start = Position; if (C == '-') ++Position; while (Position < Text.size() && Text[Position] >= '0' && Text[Position] <= '9') ++Position; if (Position < Text.size() && Text[Position] == '.') { ++Position; while (Position < Text.size() && Text[Position] >= '0' && Text[Position] <= '9') ++Position; } if (Position < Text.size() && (Text[Position] == 'e' || Text[Position] == 'E')) { ++Position; if (Position < Text.size() && (Text[Position] == '+' || Text[Position] == '-')) ++Position; while (Position < Text.size() && Text[Position] >= '0' && Text[Position] <= '9') ++Position; } if (Start == Position) { Error = "invalid JSON value"; return false; } char* End = nullptr; const double Number = std::strtod(Text.c_str() + Start, &End); if (End != Text.c_str() + Position || !std::isfinite(Number)) { Error = "invalid JSON number"; return false; } Out.Data = Number; return true; } }; inline bool Load(const std::string& Path, Value& Out, std::string& Error) { std::ifstream Input(Path, std::ios::binary); if (!Input) { Error = "cannot open " + Path; return false; } std::string Text((std::istreambuf_iterator(Input)), std::istreambuf_iterator()); return Parser(std::move(Text)).Parse(Out, Error); } }