如何基于redis-cpp库实现Redis中哈希类型的RESP序列化与反序列化
Adding RESP Hash Support to redis-cpp
Got it, let's break down how to add hash type serialization/deserialization to your redis-cpp setup. First, a quick reminder: RESP doesn't have a dedicated hash marker. Redis represents hashes as arrays of alternating key-value pairs (so an array with an even number of elements). We'll model this in our code, matching the pattern of the existing simple_string and other types.
Step 1: Implement Serialization for Hash
Add this class to your serialization.h file, alongside the existing simple_string and other serialization types:
class hash final { public: // Use a vector of key-value pairs; value can be any serializable RESP type using KVPair = std::pair<std::string_view, std::variant<simple_string, bulk_string, integer>>; using value_type = std::vector<KVPair>; explicit hash(value_type kv_pairs) noexcept : kv_pairs_(std::move(kv_pairs)) {} void put(std::ostream &stream) { // Hash is serialized as an array with length = 2 * number of key-value pairs stream << detail::marker::array << kv_pairs_.size() * 2 << detail::marker::cr << detail::marker::lf; // Serialize each key-value pair (keys are always bulk strings per Redis conventions) for (const auto &[key, value] : kv_pairs_) { put(stream, bulk_string{key}); // Use std::visit to call the correct put method for the value type std::visit([&stream](const auto &val) { put(stream, val); }, value); } } private: value_type kv_pairs_; };
Step 2: Implement Deserialization for Hash
Add this class to your deserialization.h file, matching the structure of the existing deserialization types:
class hash final { public: explicit hash(std::istream &stream) { // First read the array length (hashes require even-length arrays) std::string len_str; std::getline(stream, len_str); len_str.pop_back(); // Remove trailing '\r' size_t arr_len = std::stoull(len_str); if (arr_len % 2 != 0) { throw std::invalid_argument( "[rediscpp::resp::deserialization::hash] Hash array must have even length." ); } kv_pairs_.reserve(arr_len / 2); for (size_t i = 0; i < arr_len; i += 2) { // Read the key (always a bulk string in Redis hashes) auto key = get<bulk_string>(stream); // Detect the value type and deserialize it char mark = get_mark(stream); switch (mark) { case detail::marker::simple_string: kv_pairs_.emplace_back(key.get(), get<simple_string>(stream)); break; case detail::marker::bulk_string: kv_pairs_.emplace_back(key.get(), get<bulk_string>(stream)); break; case detail::marker::integer: kv_pairs_.emplace_back(key.get(), get<integer>(stream)); break; default: throw std::invalid_argument( "[rediscpp::resp::deserialization::hash] Unsupported value type in hash." ); } } } [[nodiscard]] const auto& get() const noexcept { return kv_pairs_; } private: std::vector<std::pair<std::string_view, std::variant<simple_string, bulk_string, integer>>> kv_pairs_; };
Step 3: Update Your Test Data
Modify your make_sample_data() function to include a hash:
auto make_sample_data() { std::ostringstream stream; put(stream, resps::array{ resps::simple_string{"This is a simple string."}, resps::error_message{"This is an error message."}, resps::bulk_string{"This is a bulk string."}, resps::integer{100500}, resps::array{ resps::simple_string("This is a simple string in a nested array."), resps::bulk_string("This is a bulk string in a nested array.") }, // Add your test hash here resps::hash({ {"username", resps::bulk_string{"john_doe"}}, {"age", resps::integer{35}}, {"account_status", resps::simple_string{"active"}} }) }); return stream.str(); }
Step 4: Update Your Printing Logic
You'll need to extend your print_sample_data() function to handle hash values. Here's an example of how to add that support (add this to your existing visitor logic):
// Inside your print_sample_data's variant visitor if constexpr (std::is_same_v<std::decay_t<decltype(val)>, respds::hash>) { out << "Hash (" << val.get().size() << " entries):\n"; for (const auto &[key, value] : val.get()) { out << " " << key << ": "; // Recursively print the value std::visit([&out](const auto &v) { out << v.get(); }, value); out << "\n"; } }
Key Notes
- RESP Hash Representation: Hashes are always arrays under the hood—this is how Redis encodes both hash commands (like
HSET) and hash responses. - Type Safety: We use
std::variantto support multiple value types (simple strings, bulk strings, integers) in the hash. You can extend this to support more types if needed. - Validation: The deserialization code checks for even-length arrays, which is required for valid Redis hashes.
内容的提问来源于stack exchange,提问作者Georgia

