#include "rdp_worker/common/json.hpp" #include #include #include #include namespace rdp_worker::common { JsonValue::JsonValue() : value(nullptr) {} JsonValue::JsonValue(std::nullptr_t) : value(nullptr) {} JsonValue::JsonValue(bool input) : value(input) {} JsonValue::JsonValue(double input) : value(input) {} JsonValue::JsonValue(int input) : value(static_cast(input)) {} JsonValue::JsonValue(const char* input) : value(std::string(input)) {} JsonValue::JsonValue(std::string input) : value(std::move(input)) {} JsonValue::JsonValue(JsonArray input) : value(std::move(input)) {} JsonValue::JsonValue(JsonObject input) : value(std::move(input)) {} bool JsonValue::IsObject() const { return std::holds_alternative(value); } bool JsonValue::IsArray() const { return std::holds_alternative(value); } bool JsonValue::IsString() const { return std::holds_alternative(value); } bool JsonValue::IsBool() const { return std::holds_alternative(value); } bool JsonValue::IsNumber() const { return std::holds_alternative(value); } const JsonObject& JsonValue::AsObject() const { return std::get(value); } const JsonArray& JsonValue::AsArray() const { return std::get(value); } const std::string& JsonValue::AsString() const { return std::get(value); } bool JsonValue::AsBool() const { return std::get(value); } double JsonValue::AsNumber() const { return std::get(value); } namespace { void AppendUtf8(std::string& output, std::uint32_t codepoint) { if (codepoint <= 0x7F) { output.push_back(static_cast(codepoint)); } else if (codepoint <= 0x7FF) { output.push_back(static_cast(0xC0 | (codepoint >> 6))); output.push_back(static_cast(0x80 | (codepoint & 0x3F))); } else if (codepoint <= 0xFFFF) { output.push_back(static_cast(0xE0 | (codepoint >> 12))); output.push_back(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); output.push_back(static_cast(0x80 | (codepoint & 0x3F))); } else if (codepoint <= 0x10FFFF) { output.push_back(static_cast(0xF0 | (codepoint >> 18))); output.push_back(static_cast(0x80 | ((codepoint >> 12) & 0x3F))); output.push_back(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); output.push_back(static_cast(0x80 | (codepoint & 0x3F))); } else { throw std::runtime_error("invalid JSON unicode escape"); } } class Parser { public: explicit Parser(const std::string& input) : input_(input), index_(0) {} JsonValue Parse() { SkipWhitespace(); JsonValue value = ParseValue(); SkipWhitespace(); if (index_ != input_.size()) { throw std::runtime_error("unexpected trailing JSON data"); } return value; } private: JsonValue ParseValue() { SkipWhitespace(); if (Match("null")) { return JsonValue(nullptr); } if (Match("true")) { return JsonValue(true); } if (Match("false")) { return JsonValue(false); } if (Peek() == '"') { return JsonValue(ParseString()); } if (Peek() == '{') { return JsonValue(ParseObject()); } if (Peek() == '[') { return JsonValue(ParseArray()); } if (Peek() == '-' || std::isdigit(static_cast(Peek()))) { return JsonValue(ParseNumber()); } throw std::runtime_error("unexpected JSON token"); } JsonObject ParseObject() { Expect('{'); JsonObject object; SkipWhitespace(); if (Peek() == '}') { Advance(); return object; } while (true) { const std::string key = ParseString(); SkipWhitespace(); Expect(':'); object.emplace(key, ParseValue()); SkipWhitespace(); if (Peek() == '}') { Advance(); break; } Expect(','); } return object; } JsonArray ParseArray() { Expect('['); JsonArray array; SkipWhitespace(); if (Peek() == ']') { Advance(); return array; } while (true) { array.emplace_back(ParseValue()); SkipWhitespace(); if (Peek() == ']') { Advance(); break; } Expect(','); } return array; } std::string ParseString() { Expect('"'); std::string output; while (index_ < input_.size()) { const char current = input_[index_++]; if (current == '"') { return output; } if (current == '\\') { if (index_ >= input_.size()) { throw std::runtime_error("invalid JSON escape"); } const char escaped = input_[index_++]; switch (escaped) { case '"': case '\\': case '/': output.push_back(escaped); break; case 'b': output.push_back('\b'); break; case 'f': output.push_back('\f'); break; case 'n': output.push_back('\n'); break; case 'r': output.push_back('\r'); break; case 't': output.push_back('\t'); break; case 'u': AppendUtf8(output, ParseUnicodeEscape()); break; default: throw std::runtime_error("unsupported JSON escape"); } continue; } output.push_back(current); } throw std::runtime_error("unterminated JSON string"); } std::uint32_t ParseUnicodeEscape() { const std::uint32_t first = ParseHexQuad(); if (first >= 0xD800 && first <= 0xDBFF) { if (index_ + 1 >= input_.size() || input_[index_] != '\\' || input_[index_ + 1] != 'u') { throw std::runtime_error("invalid JSON unicode surrogate"); } index_ += 2; const std::uint32_t second = ParseHexQuad(); if (second < 0xDC00 || second > 0xDFFF) { throw std::runtime_error("invalid JSON unicode surrogate"); } return 0x10000 + (((first - 0xD800) << 10) | (second - 0xDC00)); } if (first >= 0xDC00 && first <= 0xDFFF) { throw std::runtime_error("invalid JSON unicode surrogate"); } return first; } std::uint32_t ParseHexQuad() { if (index_ + 4 > input_.size()) { throw std::runtime_error("invalid JSON unicode escape"); } std::uint32_t value = 0; for (int i = 0; i < 4; ++i) { const char ch = input_[index_++]; value <<= 4; if (ch >= '0' && ch <= '9') { value |= static_cast(ch - '0'); } else if (ch >= 'a' && ch <= 'f') { value |= static_cast(ch - 'a' + 10); } else if (ch >= 'A' && ch <= 'F') { value |= static_cast(ch - 'A' + 10); } else { throw std::runtime_error("invalid JSON unicode escape"); } } return value; } double ParseNumber() { const std::size_t start = index_; if (Peek() == '-') { Advance(); } while (std::isdigit(static_cast(Peek()))) { Advance(); } if (Peek() == '.') { Advance(); while (std::isdigit(static_cast(Peek()))) { Advance(); } } return std::stod(input_.substr(start, index_ - start)); } void SkipWhitespace() { while (index_ < input_.size() && std::isspace(static_cast(input_[index_]))) { ++index_; } } char Peek() const { if (index_ >= input_.size()) { return '\0'; } return input_[index_]; } void Advance() { if (index_ < input_.size()) { ++index_; } } void Expect(char expected) { SkipWhitespace(); if (Peek() != expected) { throw std::runtime_error("unexpected JSON character"); } Advance(); } bool Match(const char* keyword) { const std::size_t length = std::char_traits::length(keyword); if (input_.substr(index_, length) == keyword) { index_ += length; return true; } return false; } const std::string& input_; std::size_t index_; }; std::string Escape(const std::string& value) { std::ostringstream output; for (const char ch : value) { switch (ch) { case '"': output << "\\\""; break; case '\\': output << "\\\\"; break; case '\n': output << "\\n"; break; case '\r': output << "\\r"; break; case '\t': output << "\\t"; break; default: output << ch; break; } } return output.str(); } std::string SerializeInternal(const JsonValue& value) { if (std::holds_alternative(value.value)) { return "null"; } if (std::holds_alternative(value.value)) { return std::get(value.value) ? "true" : "false"; } if (std::holds_alternative(value.value)) { std::ostringstream output; output << std::get(value.value); return output.str(); } if (std::holds_alternative(value.value)) { return "\"" + Escape(std::get(value.value)) + "\""; } if (std::holds_alternative(value.value)) { std::ostringstream output; output << "["; const auto& array = std::get(value.value); for (std::size_t i = 0; i < array.size(); ++i) { if (i > 0) { output << ","; } output << SerializeInternal(array[i]); } output << "]"; return output.str(); } std::ostringstream output; output << "{"; const auto& object = std::get(value.value); bool first = true; for (const auto& [key, child] : object) { if (!first) { output << ","; } first = false; output << "\"" << Escape(key) << "\":" << SerializeInternal(child); } output << "}"; return output.str(); } } // namespace JsonValue ParseJson(const std::string& input) { return Parser(input).Parse(); } std::string SerializeJson(const JsonValue& value) { return SerializeInternal(value); } std::optional GetString(const JsonObject& object, const std::string& key) { auto iterator = object.find(key); if (iterator == object.end() || !iterator->second.IsString()) { return std::nullopt; } return iterator->second.AsString(); } std::optional GetBool(const JsonObject& object, const std::string& key) { auto iterator = object.find(key); if (iterator == object.end() || !iterator->second.IsBool()) { return std::nullopt; } return iterator->second.AsBool(); } std::optional GetNumber(const JsonObject& object, const std::string& key) { auto iterator = object.find(key); if (iterator == object.end() || !iterator->second.IsNumber()) { return std::nullopt; } return iterator->second.AsNumber(); } const JsonObject* GetObject(const JsonObject& object, const std::string& key) { auto iterator = object.find(key); if (iterator == object.end() || !iterator->second.IsObject()) { return nullptr; } return &iterator->second.AsObject(); } const JsonArray* GetArray(const JsonObject& object, const std::string& key) { auto iterator = object.find(key); if (iterator == object.end() || !iterator->second.IsArray()) { return nullptr; } return &iterator->second.AsArray(); } } // namespace rdp_worker::common