#include "json.h" #include #include #include #include #include #include namespace dingosdk { Json::Json(std::initializer_list values) { const bool fields = std::all_of(values.begin(), values.end(), [](const Json& entry) { return entry.is_array() && entry.size() == 2 && entry[0].is_string(); }); if (fields) { data_ = Object{}; for (const auto& entry : values) items().emplace(entry[0].string(), entry[1]); } else data_ = Array(values); } bool Json::operator==(const Json& other) const { if (data_.index() == other.data_.index()) return data_ == other.data_; if (!is_number() || !other.is_number()) return false; if (is_number_float() || other.is_number_float()) return get() == other.get(); if (is_number_unsigned()) return std::cmp_equal(get(), other.get()); return std::cmp_equal(get(), other.get()); } bool Json::operator<(const Json& other) const { if (!is_number() || !other.is_number()) throw std::runtime_error("JSON ordering requires numbers"); if (is_number_float() || other.is_number_float()) return get() < other.get(); if (is_number_unsigned()) { if (other.is_number_unsigned()) return get() < other.get(); return std::cmp_less(get(), other.get()); } if (other.is_number_unsigned()) return std::cmp_less(get(), other.get()); return get() < other.get(); } namespace { struct ProfileHandler : rapidjson::BaseReaderHandler, ProfileHandler> { struct Frame { Json* value; std::string key; }; Json root; std::vector stack; JsonLimits limits; std::size_t events{}; explicit ProfileHandler(JsonLimits bounds) : limits(bounds) {} void event(std::size_t depth) { if (depth > limits.depth || ++events > limits.events) throw std::runtime_error("Local profile JSON is too complex"); } Json& add(Json value) { if (stack.empty()) { root = std::move(value); return root; } auto& frame = stack.back(); if (frame.value->is_object()) { auto& slot = (*frame.value)[frame.key]; slot = std::move(value); return slot; } frame.value->push_back(std::move(value)); return frame.value->at(frame.value->size() - 1); } bool scalar(Json value) { event(stack.size()); add(std::move(value)); return true; } bool Null() { return scalar(nullptr); } bool Bool(bool value) { return scalar(value); } bool Int(int value) { return scalar(value); } bool Uint(unsigned value) { return scalar(value); } bool Int64(std::int64_t value) { return scalar(value); } bool Uint64(std::uint64_t value) { return scalar(value); } bool Double(double value) { return scalar(value); } bool String(const char* text, rapidjson::SizeType length, bool) { return scalar(std::string(text, length)); } bool Key(const char* text, rapidjson::SizeType length, bool) { event(stack.size()); auto& frame = stack.back(); frame.key.assign(text, length); if (frame.value->contains(frame.key)) throw std::runtime_error("Duplicate local profile JSON key"); return true; } bool start(Json value) { event(stack.size()); auto& node = add(std::move(value)); stack.push_back({&node, {}}); return true; } bool StartObject() { return start(Json::object()); } bool StartArray() { return start(Json::array()); } bool end() { stack.pop_back(); event(stack.size()); return true; } bool EndObject(rapidjson::SizeType) { return end(); } bool EndArray(rapidjson::SizeType) { return end(); } }; template bool write_json(Writer& writer, const Json& value) { if (value.is_null()) return writer.Null(); if (value.is_boolean()) return writer.Bool(value.get()); if (value.is_number_unsigned()) return writer.Uint64(value.get()); if (value.is_number_integer()) return writer.Int64(value.get()); if (value.is_number_float()) { const auto number = value.get(); return std::isfinite(number) ? writer.Double(number) : writer.Null(); } if (value.is_string()) { const auto& text = value.string(); return writer.String(text.data(), static_cast(text.size())); } if (value.is_object()) { if (!writer.StartObject()) return false; for (const auto& [key, field] : value.items()) { if (!writer.Key(key.data(), static_cast(key.size())) || !write_json(writer, field)) return false; } return writer.EndObject(); } if (!writer.StartArray()) return false; for (const auto& item : value) if (!write_json(writer, item)) return false; return writer.EndArray(); } } Json Json::parse(std::string_view text, JsonLimits limits) { if (text.size() > limits.bytes) throw std::runtime_error("Local profile exceeds size limit"); if (text.starts_with("\xef\xbb\xbf")) text.remove_prefix(3); rapidjson::MemoryStream stream(text.empty() ? "" : text.data(), text.size()); rapidjson::Reader reader; ProfileHandler handler(limits); constexpr unsigned flags = rapidjson::kParseValidateEncodingFlag | rapidjson::kParseFullPrecisionFlag | rapidjson::kParseIterativeFlag; const auto result = reader.Parse(stream, handler); if (!result) throw std::runtime_error("Invalid JSON at byte " + std::to_string(result.Offset()) + ": " + rapidjson::GetParseError_En(result.Code())); // MemoryStream uses NUL as its sentinel. A literal NUL in a file must not // hide a second document or trailing garbage after an otherwise valid root. if (stream.Tell() != text.size()) throw std::runtime_error("Unexpected data after JSON root"); return std::move(handler.root); } std::string Json::dump(int indent) const { rapidjson::StringBuffer buffer; bool ok{}; if (indent < 0) { rapidjson::Writer, rapidjson::UTF8<>, rapidjson::CrtAllocator, rapidjson::kWriteValidateEncodingFlag> writer(buffer); ok = write_json(writer, *this); } else { rapidjson::PrettyWriter, rapidjson::UTF8<>, rapidjson::CrtAllocator, rapidjson::kWriteValidateEncodingFlag> writer(buffer); writer.SetIndent(' ', static_cast(indent)); ok = write_json(writer, *this); } if (!ok) throw std::runtime_error("Cannot encode JSON string as UTF-8"); return {buffer.GetString(), buffer.GetSize()}; } }