parcel 0.2.2
Wrappable, wire-transferable C++23 value system with JSON serialization
Loading...
Searching...
No Matches
unordered_map.h
Go to the documentation of this file.
1#pragma once
2
14#include <parcel/cell.h>
15#include <parcel/descriptor.h>
16#include <parcel/registry.h>
17
18#include <algorithm>
19#include <initializer_list>
20#include <map>
21#include <memory>
22#include <ranges>
23#include <stdexcept>
24#include <string>
25#include <string_view>
26#include <unordered_map>
27#include <utility>
28#include <vector>
29
30namespace parcel {
31
32template <typename K, typename V, typename H, typename E, typename A>
33struct detail::disables_basecell_compare<std::unordered_map<K, V, H, E, A>> : std::true_type {
34}; // namespace detail
35
36template <CellLike T>
37class TypedHashMapCell;
38
39template <CellLike T>
40class TypedHashMapCellTypeDescriptor final : public BaseCellTypeDescriptor<TypedHashMapCell<T>>,
41 public ISubTypes {
42public:
43 using cell_type = TypedHashMapCell<T>;
44 using base_t = BaseCellTypeDescriptor<TypedHashMapCell<T>>;
45
46 explicit TypedHashMapCellTypeDescriptor(DisplayInfo info) : base_t(std::move(info)) {}
47
48 [[nodiscard]] descriptor::CellCategory category() const override {
49 return descriptor::CellCategory::TypedMap;
50 }
51
52 [[nodiscard]] std::vector<std::string_view> sub_kinds() const override {
53 return {T::kind_id};
54 }
55
56 [[nodiscard]] cell_t cell_from_json(json_t const& j, ParcelRegistry const& reg) const override {
57 return TypedHashMapCell<T>::from_json(j, reg);
58 }
59
60 [[nodiscard]] json_t to_json() const override {
61 auto j = base_t::base_to_json();
62 j["element_kind"] = T::kind_id;
63 return j;
64 }
65};
66
73template <CellLike T>
75 : public BaseCell<TypedHashMapCell<T>, std::unordered_map<std::string, typename T::storage_t>> {
76 using base_t =
77 BaseCell<TypedHashMapCell<T>, std::unordered_map<std::string, typename T::storage_t>>;
78
79public:
80 using element_type = T;
81 using element_storage_t = typename T::storage_t;
82 using map_t = std::unordered_map<std::string, element_storage_t>;
83
84 using base_t::base_t;
85 using base_t::operator=;
86
87 TypedHashMapCell(std::initializer_list<std::pair<const std::string, element_storage_t>> elems)
88 : base_t(map_t(elems)) {}
89
90 [[nodiscard]] std::size_t size() const noexcept {
91 return this->value.size();
92 }
93 [[nodiscard]] bool empty() const noexcept {
94 return this->value.empty();
95 }
96
97 element_storage_t& operator[](std::string const& k) {
98 return this->value[k];
99 }
100 [[nodiscard]] element_storage_t const& at(std::string const& k) const {
101 return this->value.at(k);
102 }
103 [[nodiscard]] bool contains(std::string const& k) const {
104 return this->value.contains(k);
105 }
106 auto begin() noexcept {
107 return this->value.begin();
108 }
109 auto end() noexcept {
110 return this->value.end();
111 }
112 [[nodiscard]] auto begin() const noexcept {
113 return this->value.begin();
114 }
115 [[nodiscard]] auto end() const noexcept {
116 return this->value.end();
117 }
118
119 static constexpr std::string_view kind_id = prefixed_id_v<"hm:", T::kind_id>;
120
121 [[nodiscard]] std::string to_string() const override {
122 std::string out = "{";
123 bool first = true;
124 for (auto const& [k, vp] : sorted_view()) {
125 if (!first) {
126 out += ", ";
127 }
128 out += k;
129 out += ": ";
130 T wrapper(*vp);
131 out += wrapper.to_string();
132 first = false;
133 }
134 out += "}";
135 return out;
136 }
137
138 [[nodiscard]] json_t to_json() const override {
139 json_t obj = json_t::object();
140 for (auto const& [k, vp] : sorted_view()) {
141 T wrapper(*vp);
142 obj[std::string(k)] = wrapper.to_json().at(ICell::KEY_VALUE);
143 }
144 json_t j{
145 {ICell::KEY_KIND, kind_id},
146 {ICell::KEY_VALUE, std::move(obj)},
147 };
148 this->inject_display_info(j);
149 return j;
150 }
151
152 [[nodiscard]] std::partial_ordering compare(ICell const& other) const override {
153 if (const auto k_cmp = this->kind() <=> other.kind(); k_cmp != 0) {
154 return k_cmp;
155 }
156 const auto* o = dynamic_cast<TypedHashMapCell const*>(&other);
157 if (o == nullptr) {
158 return std::partial_ordering::unordered;
159 }
160 auto a = sorted_view();
161 auto b = o->sorted_view();
162 const std::size_t n = std::min(a.size(), b.size());
163 for (std::size_t i = 0; i < n; ++i) {
164 if (const auto kc = a[i].first <=> b[i].first; kc != 0) {
165 return kc;
166 }
167 T wa(*a[i].second);
168 T wb(*b[i].second);
169 if (const auto vc = wa.compare(wb); vc != 0) {
170 return vc;
171 }
172 }
173 return a.size() <=> b.size();
174 }
175
176 static cell_t from_json(json_t const& j, ParcelRegistry const& reg) {
177 if (!j.is_object()) {
178 throw InvalidJsonException("Expected JSON object for TypedHashMapCell",
179 std::string(kind_id));
180 }
181 const auto it_k = j.find(ICell::KEY_KIND);
182 if (it_k == j.end() || !it_k->is_string()) {
183 throw InvalidJsonException("TypedHashMapCell: missing/invalid 'k' (expected '" +
184 std::string(kind_id) + "')",
185 std::string(kind_id));
186 }
187 if (it_k->get<std::string_view>() != kind_id) {
188 throw KindMismatchException("TypedHashMapCell: missing/invalid 'k' (expected '" +
189 std::string(kind_id) + "')",
190 std::string(kind_id));
191 }
192 const auto it_v = j.find(ICell::KEY_VALUE);
193 if (it_v == j.end() || !it_v->is_object()) {
194 throw InvalidJsonException("TypedHashMapCell: missing/invalid 'v' (expected object)",
195 std::string(kind_id));
196 }
197
198 map_t elems;
199 for (auto const& [key, raw] : it_v->items()) {
200 json_t wrapped{{ICell::KEY_KIND, T::kind_id}, {ICell::KEY_VALUE, raw}};
201 cell_t v = T::from_json(wrapped, reg);
202 auto* typed = dynamic_cast<T*>(v.get());
203 if (typed == nullptr) {
204 throw TypeException("TypedHashMapCell: element from_json mismatch",
205 std::string(kind_id));
206 }
207 elems.emplace(key, std::move(typed->value));
208 }
209 auto cell = std::make_shared<TypedHashMapCell<T>>(std::move(elems));
211 return cell;
212 }
213
214 static cell_type_descriptor_t descriptor() {
215 static const auto d = std::make_shared<TypedHashMapCellTypeDescriptor<T>>(DisplayInfo{
216 .name = "HashMap",
217 .description = std::string("Hash-backed map of ") + std::string(T::kind_id),
218 });
219 return d;
220 }
221
222private:
223 [[nodiscard]] std::vector<std::pair<std::string_view, element_storage_t const*>>
224 sorted_view() const {
225 std::vector<std::pair<std::string_view, element_storage_t const*>> out;
226 out.reserve(this->value.size());
227 for (auto const& [k, v] : this->value) {
228 out.emplace_back(std::string_view(k), &v);
229 }
230 std::ranges::sort(out, {}, &std::pair<std::string_view, element_storage_t const*>::first);
231 return out;
232 }
233};
234
241class HashMapCell : public BaseCell<HashMapCell, std::unordered_map<std::string, cell_t>> {
243
244public:
245 using base_t::base_t;
246 using base_t::operator=;
247
248 HashMapCell(const std::initializer_list<std::pair<const std::string, cell_t>> elems)
249 : base_t(std::unordered_map<std::string, cell_t>(elems)) {}
250
251 [[nodiscard]] std::size_t size() const noexcept {
252 return this->value.size();
253 }
254 [[nodiscard]] bool empty() const noexcept {
255 return this->value.empty();
256 }
257
258 cell_t& operator[](std::string const& k) {
259 return this->value[k];
260 }
261 [[nodiscard]] cell_t const& at(std::string const& k) const {
262 return this->value.at(k);
263 }
264 [[nodiscard]] bool contains(std::string const& k) const {
265 return this->value.contains(k);
266 }
267
268 static constexpr std::string_view kind_id = "hm";
269
270 [[nodiscard]] std::string to_string() const override {
271 std::vector<std::string_view> keys;
272 keys.reserve(this->value.size());
273 for (const auto& k : this->value | std::views::keys) {
274 keys.emplace_back(k);
275 }
276 std::ranges::sort(keys);
277 std::string out = "{";
278 bool first = true;
279 for (auto k : keys) {
280 if (!first) {
281 out += ", ";
282 }
283 out += k;
284 out += ": ";
285 const auto& v = this->value.at(std::string(k));
286 out += v ? v->to_string() : "<null>";
287 first = false;
288 }
289 out += "}";
290 return out;
291 }
292
293 [[nodiscard]] json_t to_json() const override {
294 std::vector<std::string_view> keys;
295 keys.reserve(this->value.size());
296 for (const auto& k : this->value | std::views::keys) {
297 keys.emplace_back(k);
298 }
299 std::ranges::sort(keys);
300 json_t obj = json_t::object();
301 for (auto k : keys) {
302 const auto& v = this->value.at(std::string(k));
303 obj[std::string(k)] = v ? v->to_json() : json_t();
304 }
305 json_t j{
306 {ICell::KEY_KIND, kind_id},
307 {ICell::KEY_VALUE, std::move(obj)},
308 };
309 this->inject_display_info(j);
310 return j;
311 }
312
313 [[nodiscard]] cell_t clone() const override {
314 std::unordered_map<std::string, cell_t> copied;
315 for (auto const& [k, v] : this->value) {
316 copied.emplace(k, v ? v->clone() : nullptr);
317 }
318 auto out = std::make_shared<HashMapCell>(std::move(copied));
319 out->display_info_ = this->display_info_;
320 return out;
321 }
322
323 [[nodiscard]] std::size_t hash_value() const noexcept override {
324 std::size_t h = std::hash<std::string_view>{}(kind());
325 // Sort by key first so the hash is stable across insertion orders.
326 std::vector<std::string_view> keys;
327 keys.reserve(this->value.size());
328 for (const auto& k : this->value | std::views::keys) {
329 keys.emplace_back(k);
330 }
331 std::ranges::sort(keys);
332 for (const auto k : keys) {
333 const auto& el = this->value.at(std::string(k));
334 const std::size_t hk = std::hash<std::string_view>{}(k);
335 const std::size_t hv = el ? el->hash_value() : std::size_t{0};
336 h ^= hk + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
337 h ^= hv + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
338 }
339 return h;
340 }
341
342 [[nodiscard]] std::partial_ordering compare(ICell const& other) const override {
343 if (const auto k_cmp = this->kind() <=> other.kind(); k_cmp != 0) {
344 return k_cmp;
345 }
346 const auto* o = dynamic_cast<HashMapCell const*>(&other);
347 if (o == nullptr) {
348 return std::partial_ordering::unordered;
349 }
350 // Use a sorted projection so unordered storage compares deterministically.
351 std::map<std::string, cell_t> a(this->value.begin(), this->value.end());
352 std::map<std::string, cell_t> b(o->value.begin(), o->value.end());
353 auto it_a = a.begin();
354 auto it_b = b.begin();
355 while (it_a != a.end() && it_b != b.end()) {
356 if (const auto kc = it_a->first <=> it_b->first; kc != 0) {
357 return kc;
358 }
359 if (it_a->second && it_b->second) {
360 if (const auto vc = it_a->second->compare(*it_b->second); vc != 0) {
361 return vc;
362 }
363 } else if (!it_a->second && it_b->second) {
364 return std::partial_ordering::less;
365 } else if (it_a->second && !it_b->second) {
366 return std::partial_ordering::greater;
367 }
368 ++it_a;
369 ++it_b;
370 }
371 return a.size() <=> b.size();
372 }
373
374 static cell_t from_json(json_t const& j, ParcelRegistry const& reg) {
375 if (!j.is_object()) {
376 throw InvalidJsonException("Expected JSON object for HashMapCell",
377 std::string(kind_id));
378 }
379 const auto it_k = j.find(ICell::KEY_KIND);
380 if (it_k == j.end() || !it_k->is_string()) {
381 throw InvalidJsonException("HashMapCell: missing/invalid 'k' (expected 'hm')",
382 std::string(kind_id));
383 }
384 if (it_k->get<std::string_view>() != kind_id) {
385 throw KindMismatchException("HashMapCell: missing/invalid 'k' (expected 'hm')",
386 std::string(kind_id));
387 }
388 const auto it_v = j.find(ICell::KEY_VALUE);
389 if (it_v == j.end() || !it_v->is_object()) {
390 throw InvalidJsonException("HashMapCell: missing/invalid 'v' (expected object)",
391 std::string(kind_id));
392 }
393 std::unordered_map<std::string, cell_t> elems;
394 for (auto const& [k, raw] : it_v->items()) {
395 elems.emplace(k, raw.is_null() ? cell_t{} : reg.cell_from_json(raw));
396 }
397 auto cell = std::make_shared<HashMapCell>(std::move(elems));
399 return cell;
400 }
401
402 static cell_type_descriptor_t descriptor();
403};
404
411class HashMapCellTypeDescriptor final : public BaseCellTypeDescriptor<HashMapCell> {
412public:
413 using cell_type = HashMapCell;
415
416 explicit HashMapCellTypeDescriptor(DisplayInfo info) : base_t(std::move(info)) {}
417
418 [[nodiscard]] descriptor::CellCategory category() const override {
419 return descriptor::CellCategory::Map;
420 }
421
422 [[nodiscard]] cell_t cell_from_json(const json_t& j, const ParcelRegistry& reg) const override {
423 return HashMapCell::from_json(j, reg);
424 }
425};
426
427inline cell_type_descriptor_t HashMapCell::descriptor() {
428 static const auto d = std::make_shared<HashMapCellTypeDescriptor>(
429 DisplayInfo{.name = "HashMap", .description = "Hash-backed heterogeneous map of cells"});
430 return d;
431}
432
433} // namespace parcel
Core ICell interface, cell_t handle, BaseCell CRTP base, and CellLike concept.
std::shared_ptr< ICellTypeDescriptor > cell_type_descriptor_t
Shared handle to a runtime cell-type descriptor.
Definition cell.h:63
void to_json(json_t &j, T const &v)
ADL hook that lets nlohmann serialize any ICell-derived cell.
Definition cell.h:293
std::shared_ptr< ICell > cell_t
Shared handle to any ICell-derived value — the canonical cell pointer.
Definition cell.h:68
CRTP base that fills in the boilerplate of every cell-type descriptor.
Definition descriptor.h:165
json_t base_to_json() const
Common JSON skeleton (kind, display_info, category) reused by overrides.
Definition descriptor.h:202
Descriptor for the heterogeneous HashMapCell.
Definition unordered_map.h:411
descriptor::CellCategory category() const override
Coarse classification (primitive, list, struct, …).
Definition unordered_map.h:418
cell_t cell_from_json(const json_t &j, const ParcelRegistry &reg) const override
Construct a cell from JSON, dispatching to the concrete from_json.
Definition unordered_map.h:422
Heterogeneous string-keyed unordered_map of cell_t.
Definition unordered_map.h:241
cell_t clone() const override
Deep-copy this cell.
Definition unordered_map.h:313
std::size_t hash_value() const noexcept override
Equality-consistent hash that mirrors compare's display-info-insensitivity.
Definition unordered_map.h:323
json_t to_json() const override
Default JSON serialization for cells with JSON-convertible storage.
Definition unordered_map.h:293
std::partial_ordering compare(ICell const &other) const override
Default three-way comparison: kind first, then storage value.
Definition unordered_map.h:342
std::string to_string() const override
Render the cell's value as a compact human-readable string.
Definition unordered_map.h:270
JSON shape was wrong (missing/non-string k, missing v, etc.).
Definition error.h:158
Runtime catalog of cell-type descriptors, keyed by wire kind id.
Definition registry.h:115
Homogeneous string-keyed unordered_map of values of type T.
Definition unordered_map.h:75
json_t to_json() const override
Default JSON serialization for cells with JSON-convertible storage.
Definition unordered_map.h:138
std::partial_ordering compare(ICell const &other) const override
Default three-way comparison: kind first, then storage value.
Definition unordered_map.h:152
std::string to_string() const override
Render the cell's value as a compact human-readable string.
Definition unordered_map.h:121
comms::DisplayInfo DisplayInfo
Display info attached to a cell or descriptor — re-exported from comms::DisplayInfo.
Definition common.h:75
constexpr std::string_view prefixed_id_v
Convenience alias yielding the joined std::string_view.
Definition common.h:175
auto cell(T &&v)
Wrap a raw value into its default cell, returning a shared_ptr to the cell.
Definition defaults.h:192
Runtime cell-type descriptors and the schema-graph mix-ins (IHasFields, ISubTypes).
CellCategory
Coarse runtime classification of a cell type.
Definition descriptor.h:35
nlohmann::json json_t
Project-wide alias for nlohmann::json.
Definition json.h:19
ParcelRegistry, Definition, and BuiltinsOptions for polymorphic dispatch.
CRTP base providing default to_json / clone / kind plumbing on top of a storage type.
Definition cell.h:343
std::optional< DisplayInfo > display_info_
Optional display info; omitted from JSON when empty.
Definition cell.h:507
static void absorb_display_info(json_t const &j, Out &out)
Read "d" (if present) from a JSON object and assign it onto a cell.
Definition cell.h:499
std::unordered_map< std::string, T::storage_t > value
Held value of the cell.
Definition cell.h:348
void inject_display_info(json_t &j) const
Copy this cell's display info (if any) into the JSON object under "d".
Definition cell.h:486
static T cell_from_json(const json_t &j, const std::string_view expected_kind)
Validate a wrapped {"k","v"} JSON object and extract its "v" as T.
Definition cell.h:526
Polymorphic root of every parcel cell.
Definition cell.h:84
static constexpr std::string_view KEY_VALUE
JSON key for the value payload ("v").
Definition cell.h:88
virtual std::string_view kind() const =0
Wire-stable kind identifier for this cell.
static constexpr std::string_view KEY_KIND
JSON key for the kind id ("k").
Definition cell.h:86