parcel 0.2.2
Wrappable, wire-transferable C++23 value system with JSON serialization
Loading...
Searching...
No Matches
map.h
Go to the documentation of this file.
1#pragma once
2
15#include <parcel/cell.h>
16#include <parcel/descriptor.h>
17#include <parcel/registry.h>
18
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 <utility>
27#include <vector>
28
29namespace parcel {
30
31// std::map<K, V>::operator== eagerly instantiates V::operator==; suppress
32// BaseCell's default compare for map storage so cells whose value payloads
33// lack `==` still compile (TypedMapCell overrides compare()).
34template <typename K, typename V, typename C, typename A>
35struct detail::disables_basecell_compare<std::map<K, V, C, A>> : std::true_type {
36}; // namespace detail
37
38class ParcelRegistry;
39
40template <CellLike T>
41class TypedMapCell;
42
52template <CellLike T>
53class TypedMapCellTypeDescriptor final : public BaseCellTypeDescriptor<TypedMapCell<T>>,
54 public ISubTypes {
55public:
58
63 explicit TypedMapCellTypeDescriptor(DisplayInfo info) : base_t(std::move(info)) {}
64
66 [[nodiscard]] std::string_view element_kind() const {
67 return T::kind_id;
68 }
69
70 [[nodiscard]] descriptor::CellCategory category() const override {
71 return descriptor::CellCategory::TypedMap;
72 }
73
74 [[nodiscard]] std::vector<std::string_view> sub_kinds() const override {
75 return {T::kind_id};
76 }
77
78 [[nodiscard]] cell_t cell_from_json(json_t const& j, ParcelRegistry const& reg) const override {
79 return TypedMapCell<T>::from_json(j, reg);
80 }
81
82 [[nodiscard]] json_t to_json() const override {
83 auto j = base_t::base_to_json();
84 j["element_kind"] = T::kind_id;
85 return j;
86 }
87};
88
104template <CellLike T>
106 : public BaseCell<TypedMapCell<T>, std::map<std::string, typename T::storage_t>> {
107 using base_t = BaseCell<TypedMapCell<T>, std::map<std::string, typename T::storage_t>>;
108
109public:
110 using element_type = T;
111 using element_storage_t = typename T::storage_t;
112
113 using base_t::base_t;
114 using base_t::operator=;
115
120 TypedMapCell(std::initializer_list<std::pair<const std::string, element_storage_t>> elems)
121 : base_t(std::map<std::string, element_storage_t>(elems)) {}
122
126 template <std::ranges::input_range R>
127 requires requires(std::ranges::range_value_t<R> p) {
128 { p.first } -> std::convertible_to<std::string>;
129 { p.second } -> std::convertible_to<element_storage_t>;
130 }
131 TypedMapCell(std::from_range_t /*tag*/, R&& r)
132 : base_t(std::map<std::string, element_storage_t>(std::ranges::begin(r),
133 std::ranges::end(r))) {}
134
141 [[nodiscard]] auto keys() const noexcept {
142 return this->value | std::views::keys;
143 }
144
146 [[nodiscard]] auto values() const noexcept {
147 return this->value | std::views::values;
148 }
149
150 // ---- std::map pass-through API ---------------------------------------
151 // Transparent map: values are raw element_storage_t, not ICell wrappers.
152
153 using map_t = std::map<std::string, element_storage_t>;
154 using key_type = map_t::key_type;
155 using mapped_type = map_t::mapped_type;
156 using value_type = map_t::value_type;
157 using size_type = map_t::size_type;
158 using iterator = map_t::iterator;
159 using const_iterator = map_t::const_iterator;
160
161 [[nodiscard]] size_type size() const noexcept {
162 return this->value.size();
163 }
164 [[nodiscard]] bool empty() const noexcept {
165 return this->value.empty();
166 }
167
168 mapped_type& operator[](key_type const& k) {
169 return this->value[k];
170 }
171 mapped_type& operator[](key_type&& k) {
172 return this->value[std::move(k)];
173 }
174 mapped_type& at(key_type const& k) {
175 return this->value.at(k);
176 }
177 [[nodiscard]] mapped_type const& at(key_type const& k) const {
178 return this->value.at(k);
179 }
180
181 iterator find(key_type const& k) {
182 return this->value.find(k);
183 }
184 [[nodiscard]] const_iterator find(key_type const& k) const {
185 return this->value.find(k);
186 }
187 [[nodiscard]] bool contains(key_type const& k) const {
188 return this->value.contains(k);
189 }
190 [[nodiscard]] size_type count(key_type const& k) const {
191 return this->value.count(k);
192 }
193
194 iterator begin() noexcept {
195 return this->value.begin();
196 }
197 [[nodiscard]] const_iterator begin() const noexcept {
198 return this->value.begin();
199 }
200 [[nodiscard]] const_iterator cbegin() const noexcept {
201 return this->value.cbegin();
202 }
203 iterator end() noexcept {
204 return this->value.end();
205 }
206 [[nodiscard]] const_iterator end() const noexcept {
207 return this->value.end();
208 }
209 [[nodiscard]] const_iterator cend() const noexcept {
210 return this->value.cend();
211 }
212
213 std::pair<iterator, bool> insert(value_type const& v) {
214 return this->value.insert(v);
215 }
216 std::pair<iterator, bool> insert(value_type&& v) {
217 return this->value.insert(std::move(v));
218 }
219 template <typename... Args>
220 std::pair<iterator, bool> emplace(Args&&... args) {
221 return this->value.emplace(std::forward<Args>(args)...);
222 }
223 size_type erase(key_type const& k) {
224 return this->value.erase(k);
225 }
226 iterator erase(const_iterator pos) {
227 return this->value.erase(pos);
228 }
229 void clear() noexcept {
230 this->value.clear();
231 }
232 // ----------------------------------------------------------------------
233
244 static constexpr std::string_view kind_id = prefixed_id_v<"m:", T::kind_id>;
245
250 [[nodiscard]] std::string to_string() const override {
251 std::string out = "{";
252 bool first = true;
253 for (auto const& [key, el] : this->value) {
254 if (!first) {
255 out += ", ";
256 }
257 out += key;
258 out += ": ";
259 T wrapper(el);
260 out += wrapper.to_string();
261 first = false;
262 }
263 out += "}";
264 return out;
265 }
266
267 [[nodiscard]] json_t to_json() const override {
268 json_t obj = json_t::object();
269 for (auto const& [key, el] : this->value) {
270 T wrapper(el);
271 obj[key] = wrapper.to_json().at(ICell::KEY_VALUE);
272 }
273 json_t j{
275 {ICell::KEY_VALUE, std::move(obj)},
276 };
277 this->inject_display_info(j);
278 return j;
279 }
280
288 [[nodiscard]] std::partial_ordering compare(ICell const& other) const override {
289 if (const auto k_cmp = this->kind() <=> other.kind(); k_cmp != 0) {
290 return k_cmp;
291 }
292 const auto* o = dynamic_cast<TypedMapCell const*>(&other);
293 if (o == nullptr) {
294 return std::partial_ordering::unordered;
295 }
296 auto it_a = this->value.begin();
297 auto it_b = o->value.begin();
298 const auto end_a = this->value.end();
299 const auto end_b = o->value.end();
300 while (it_a != end_a && it_b != end_b) {
301 if (const auto kc = it_a->first <=> it_b->first; kc != 0) {
302 return kc;
303 }
304 T wa(it_a->second);
305 T wb(it_b->second);
306 if (const auto vc = wa.compare(wb); vc != 0) {
307 return vc;
308 }
309 ++it_a;
310 ++it_b;
311 }
312 return this->value.size() <=> o->value.size();
313 }
314
323 static cell_t from_json(json_t const& j, ParcelRegistry const& reg) {
324 if (!j.is_object()) {
325 throw InvalidJsonException("Expected JSON object for TypedMapCell",
326 std::string(kind_id));
327 }
328
329 const auto it_k = j.find(ICell::KEY_KIND);
330 if (it_k == j.end() || !it_k->is_string()) {
331 throw InvalidJsonException("TypedMapCell: missing/invalid 'k' (expected '" +
332 std::string(kind_id) + "')",
333 std::string(kind_id));
334 }
335 if (it_k->get<std::string_view>() != kind_id) {
336 throw KindMismatchException("TypedMapCell: missing/invalid 'k' (expected '" +
337 std::string(kind_id) + "')",
338 std::string(kind_id));
339 }
340
341 const auto it_v = j.find(ICell::KEY_VALUE);
342 if (it_v == j.end() || !it_v->is_object()) {
343 throw InvalidJsonException("TypedMapCell: missing/invalid 'v' (expected object)",
344 std::string(kind_id));
345 }
346
347 std::map<std::string, element_storage_t> elems;
348 for (auto const& [key, raw] : it_v->items()) {
349 json_t wrapped{{ICell::KEY_KIND, T::kind_id}, {ICell::KEY_VALUE, raw}};
350 cell_t v = T::from_json(wrapped, reg);
351 auto* typed = dynamic_cast<T*>(v.get());
352 if (typed == nullptr) {
353 throw TypeException(std::string("TypedMapCell: element from_json did not return ") +
354 std::string(T::kind_id),
355 std::string(kind_id));
356 }
357 elems.emplace(key, std::move(typed->value));
358 }
359 auto cell = std::make_shared<TypedMapCell<T>>(std::move(elems));
361 return cell;
362 }
363
369 static const auto d = std::make_shared<TypedMapCellTypeDescriptor<T>>(DisplayInfo{
370 .name = "Map",
371 .description = std::string("Homogeneous map of ") + std::string(T::kind_id),
372 });
373 return d;
374 }
375};
376
390class MapCell : public BaseCell<MapCell, std::map<std::string, cell_t>> {
392
393public:
394 using base_t::base_t;
395 using base_t::operator=;
396
401 MapCell(const std::initializer_list<std::pair<const std::string, cell_t>> elems)
402 : base_t(std::map<std::string, cell_t>(elems)) {}
403
405 [[nodiscard]] auto keys() const noexcept {
406 return this->value | std::views::keys;
407 }
408
410 [[nodiscard]] auto values() const noexcept {
411 return this->value | std::views::values;
412 }
413
414 // ---- std::map pass-through API ---------------------------------------
415
416 using map_t = std::map<std::string, cell_t>;
417 using key_type = map_t::key_type;
418 using mapped_type = map_t::mapped_type;
419 using value_type = map_t::value_type;
420 using size_type = map_t::size_type;
421 using iterator = map_t::iterator;
422 using const_iterator = map_t::const_iterator;
423
424 [[nodiscard]] size_type size() const noexcept {
425 return this->value.size();
426 }
427 [[nodiscard]] bool empty() const noexcept {
428 return this->value.empty();
429 }
430
431 mapped_type& operator[](key_type const& k) {
432 return this->value[k];
433 }
434 mapped_type& operator[](key_type&& k) {
435 return this->value[std::move(k)];
436 }
437 mapped_type& at(key_type const& k) {
438 return this->value.at(k);
439 }
440 [[nodiscard]] mapped_type const& at(key_type const& k) const {
441 return this->value.at(k);
442 }
443
444 iterator find(key_type const& k) {
445 return this->value.find(k);
446 }
447 [[nodiscard]] const_iterator find(key_type const& k) const {
448 return this->value.find(k);
449 }
450 [[nodiscard]] bool contains(key_type const& k) const {
451 return this->value.contains(k);
452 }
453 [[nodiscard]] size_type count(key_type const& k) const {
454 return this->value.count(k);
455 }
456
457 iterator begin() noexcept {
458 return this->value.begin();
459 }
460 [[nodiscard]] const_iterator begin() const noexcept {
461 return this->value.begin();
462 }
463 [[nodiscard]] const_iterator cbegin() const noexcept {
464 return this->value.cbegin();
465 }
466 iterator end() noexcept {
467 return this->value.end();
468 }
469 [[nodiscard]] const_iterator end() const noexcept {
470 return this->value.end();
471 }
472 [[nodiscard]] const_iterator cend() const noexcept {
473 return this->value.cend();
474 }
475
476 std::pair<iterator, bool> insert(value_type const& v) {
477 return this->value.insert(v);
478 }
479 std::pair<iterator, bool> insert(value_type&& v) {
480 return this->value.insert(std::move(v));
481 }
482 template <typename... Args>
483 std::pair<iterator, bool> emplace(Args&&... args) {
484 return this->value.emplace(std::forward<Args>(args)...);
485 }
486 size_type erase(key_type const& k) {
487 return this->value.erase(k);
488 }
489 iterator erase(const const_iterator pos) {
490 return this->value.erase(pos);
491 }
492 void clear() noexcept {
493 this->value.clear();
494 }
495 // ----------------------------------------------------------------------
496
498 static constexpr std::string_view kind_id = "m";
499
504 [[nodiscard]] std::string to_string() const override {
505 std::string out = "{";
506 bool first = true;
507 for (auto const& [key, el] : this->value) {
508 if (!first) {
509 out += ", ";
510 }
511 out += key;
512 out += ": ";
513 out += el ? el->to_string() : "<null>";
514 first = false;
515 }
516 out += "}";
517 return out;
518 }
519
520 [[nodiscard]] json_t to_json() const override {
521 json_t obj = json_t::object();
522 for (auto const& [key, el] : this->value) {
523 obj[key] = el ? el->to_json() : json_t();
524 }
525 json_t j{
527 {ICell::KEY_VALUE, std::move(obj)},
528 };
529 this->inject_display_info(j);
530 return j;
531 }
532
533 [[nodiscard]] cell_t clone() const override {
534 std::map<std::string, cell_t> copied;
535 for (auto const& [key, el] : this->value) {
536 copied.emplace(key, el ? el->clone() : nullptr);
537 }
538 auto out = std::make_shared<MapCell>(std::move(copied));
539 out->display_info_ = this->display_info_;
540 return out;
541 }
542
543 [[nodiscard]] std::size_t hash_value() const noexcept override {
544 std::size_t h = std::hash<std::string_view>{}(kind());
545 // std::map iterates in sorted key order, so the digest is canonical.
546 for (auto const& [key, el] : this->value) {
547 const std::size_t hk = std::hash<std::string>{}(key);
548 const std::size_t hv = el ? el->hash_value() : std::size_t{0};
549 h ^= hk + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
550 h ^= hv + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
551 }
552 return h;
553 }
554
561 [[nodiscard]] std::partial_ordering compare(ICell const& other) const override {
562 if (const auto k_cmp = this->kind() <=> other.kind(); k_cmp != 0) {
563 return k_cmp;
564 }
565 const auto* o = dynamic_cast<MapCell const*>(&other);
566 if (o == nullptr) {
567 return std::partial_ordering::unordered;
568 }
569 auto it_a = this->value.begin();
570 auto it_b = o->value.begin();
571 const auto end_a = this->value.end();
572 const auto end_b = o->value.end();
573 while (it_a != end_a && it_b != end_b) {
574 if (const auto kc = it_a->first <=> it_b->first; kc != 0) {
575 return kc;
576 }
577 if (it_a->second && it_b->second) {
578 if (const auto vc = it_a->second->compare(*it_b->second); vc != 0) {
579 return vc;
580 }
581 } else if (!it_a->second && it_b->second) {
582 return std::partial_ordering::less;
583 } else if (it_a->second && !it_b->second) {
584 return std::partial_ordering::greater;
585 }
586 ++it_a;
587 ++it_b;
588 }
589 return this->value.size() <=> o->value.size();
590 }
591
599 static cell_t from_json(json_t const& j, ParcelRegistry const& reg) {
600 if (!j.is_object()) {
601 throw InvalidJsonException("Expected JSON object for MapCell", std::string(kind_id));
602 }
603
604 const auto it_k = j.find(ICell::KEY_KIND);
605 if (it_k == j.end() || !it_k->is_string()) {
606 throw InvalidJsonException("MapCell: missing/invalid 'k' (expected 'm')",
607 std::string(kind_id));
608 }
609 if (it_k->get<std::string_view>() != kind_id) {
610 throw KindMismatchException("MapCell: missing/invalid 'k' (expected 'm')",
611 std::string(kind_id));
612 }
613
614 const auto it_v = j.find(ICell::KEY_VALUE);
615 if (it_v == j.end() || !it_v->is_object()) {
616 throw InvalidJsonException("MapCell: missing/invalid 'v' (expected object)",
617 std::string(kind_id));
618 }
619
620 std::map<std::string, cell_t> elems;
621 for (auto const& [key, raw] : it_v->items()) {
622 elems.emplace(key, raw.is_null() ? cell_t{} : reg.cell_from_json(raw));
623 }
624 auto cell = std::make_shared<MapCell>(std::move(elems));
626 return cell;
627 }
628
634};
635
642class MapCellTypeDescriptor final : public BaseCellTypeDescriptor<MapCell> {
643public:
644 using cell_type = MapCell;
646
647 explicit MapCellTypeDescriptor(DisplayInfo info) : base_t(std::move(info)) {}
648
649 [[nodiscard]] descriptor::CellCategory category() const override {
650 return descriptor::CellCategory::Map;
651 }
652
653 [[nodiscard]] cell_t cell_from_json(const json_t& j, const ParcelRegistry& reg) const override {
654 return MapCell::from_json(j, reg);
655 }
656};
657
659 static const auto d = std::make_shared<MapCellTypeDescriptor>(
660 DisplayInfo{.name = "Map", .description = "Heterogeneous map of cells"});
661 return d;
662}
663
664} // 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
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
JSON shape was wrong (missing/non-string k, missing v, etc.).
Definition error.h:158
k field was present but did not match the expected kind.
Definition error.h:168
Descriptor for the heterogeneous MapCell.
Definition map.h:642
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 map.h:653
descriptor::CellCategory category() const override
Coarse classification (primitive, list, struct, …).
Definition map.h:649
Heterogeneous string-keyed map of cell_t — wire kind "m".
Definition map.h:390
cell_t clone() const override
Deep-copy this cell.
Definition map.h:533
auto values() const noexcept
Lazy view over the cell values of the map.
Definition map.h:410
std::string to_string() const override
Render the map as {k1: v1, …}; null values appear as <null>.
Definition map.h:504
auto keys() const noexcept
Lazy view over the keys of the map.
Definition map.h:405
std::partial_ordering compare(ICell const &other) const override
Lexicographic three-way comparison over keys then values; ignores display info.
Definition map.h:561
static cell_t from_json(json_t const &j, ParcelRegistry const &reg)
Deserialize a MapCell from JSON.
Definition map.h:599
static cell_type_descriptor_t descriptor()
Cached descriptor for MapCell.
Definition map.h:658
std::size_t hash_value() const noexcept override
Equality-consistent hash that mirrors compare's display-info-insensitivity.
Definition map.h:543
static constexpr std::string_view kind_id
Wire kind id ("m").
Definition map.h:498
MapCell(const std::initializer_list< std::pair< const std::string, cell_t > > elems)
Construct from a brace-enclosed list of {key, cell} pairs.
Definition map.h:401
json_t to_json() const override
Default JSON serialization for cells with JSON-convertible storage.
Definition map.h:520
Runtime catalog of cell-type descriptors, keyed by wire kind id.
Definition registry.h:115
cell_t cell_from_json(json_t const &j) const
Deserialize any registered cell from JSON, dispatching by "k".
Definition registry.h:266
Typed value (cast, struct field, etc.) failed conversion.
Definition error.h:190
Descriptor for TypedMapCell<T>.
Definition map.h:54
std::string_view element_kind() const
Wire kind id of the map's value type.
Definition map.h:66
TypedMapCellTypeDescriptor(DisplayInfo info)
Construct with the given display info.
Definition map.h:63
json_t to_json() const override
Serialize the descriptor itself (kind + display info + category + extras).
Definition map.h:82
std::vector< std::string_view > sub_kinds() const override
Sub-kind ids reachable from this descriptor.
Definition map.h:74
cell_t cell_from_json(json_t const &j, ParcelRegistry const &reg) const override
Construct a cell from JSON, dispatching to the concrete from_json.
Definition map.h:78
descriptor::CellCategory category() const override
Coarse classification (primitive, list, struct, …).
Definition map.h:70
Homogeneous string-keyed map of values of type T.
Definition map.h:106
TypedMapCell(std::from_range_t, R &&r)
Construct from any input range whose elements are {key, value}-like.
Definition map.h:131
std::string to_string() const override
Render the map as {k1: v1, k2: v2} using each value's to_string.
Definition map.h:250
json_t to_json() const override
Default JSON serialization for cells with JSON-convertible storage.
Definition map.h:267
TypedMapCell(std::initializer_list< std::pair< const std::string, element_storage_t > > elems)
Construct from a brace-enclosed list of {key, value} pairs.
Definition map.h:120
auto keys() const noexcept
Lazy view over the keys of the map.
Definition map.h:141
auto values() const noexcept
Lazy view over the values of the map.
Definition map.h:146
static cell_type_descriptor_t descriptor()
Cached descriptor for this typed map.
Definition map.h:368
std::partial_ordering compare(ICell const &other) const override
Lexicographic three-way comparison over (key, value) pairs; ignores display info.
Definition map.h:288
static constexpr std::string_view kind_id
Wire kind id of this typed map ("m:" + T::kind_id).
Definition map.h:244
static cell_t from_json(json_t const &j, ParcelRegistry const &reg)
Deserialize a TypedMapCell<T> from JSON.
Definition map.h:323
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
std::string_view kind() const override
Definition cell.h:411
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::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
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
Mix-in declaring a descriptor refers to other registered cell kinds.
Definition descriptor.h:148