{"id":50312440,"url":"https://github.com/aurimasniekis/cpp-parcel","last_synced_at":"2026-05-28T22:01:34.267Z","repository":{"id":360635864,"uuid":"1222308622","full_name":"aurimasniekis/cpp-parcel","owner":"aurimasniekis","description":"Wrappable, wire-transferable C++23 value system with JSON serialization.","archived":false,"fork":false,"pushed_at":"2026-05-27T09:06:15.000Z","size":264,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-27T09:13:48.095Z","etag":null,"topics":["cell","cpp","cpp23","json"],"latest_commit_sha":null,"homepage":"https://aurimasniekis.github.io/cpp-parcel/","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aurimasniekis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-27T08:35:31.000Z","updated_at":"2026-05-27T09:06:21.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/aurimasniekis/cpp-parcel","commit_stats":null,"previous_names":["aurimasniekis/cpp-parcel"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/aurimasniekis/cpp-parcel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurimasniekis%2Fcpp-parcel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurimasniekis%2Fcpp-parcel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurimasniekis%2Fcpp-parcel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurimasniekis%2Fcpp-parcel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aurimasniekis","download_url":"https://codeload.github.com/aurimasniekis/cpp-parcel/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurimasniekis%2Fcpp-parcel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33627941,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cell","cpp","cpp23","json"],"created_at":"2026-05-28T22:01:33.348Z","updated_at":"2026-05-28T22:01:34.255Z","avatar_url":"https://github.com/aurimasniekis.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# parcel\n\nWrappable, wire-transferable C++23 value system with JSON serialization.\n\n[![CI](https://github.com/aurimasniekis/cpp-parcel/actions/workflows/ci.yml/badge.svg)](https://github.com/aurimasniekis/cpp-parcel/actions/workflows/ci.yml)\n[![Docs](https://github.com/aurimasniekis/cpp-parcel/actions/workflows/docs.yml/badge.svg)](https://aurimasniekis.github.io/cpp-parcel/)\n\n## What is parcel?\n\nA *cell* is a typed value that knows how to serialize itself to JSON and\nback. parcel ships a small family of cells — primitives, lists, maps,\nhash-maps, structs, unions — that all share one wire shape:\n`{\"k\": \u003ckind\u003e, \"v\": \u003cvalue\u003e, \"d\": \u003coptional display info\u003e}`. A `ParcelRegistry`\ndispatches incoming JSON back to the right cell type so heterogeneous\npayloads round-trip safely.\n\nIt is not an RPC framework. It just gives you the value layer: typed C++\nvalues on one side, self-describing JSON on the wire, and a registry that\nglues the two together.\n\n## Key features\n\n- Header-only, single umbrella include `\u003cparcel/parcel.h\u003e`.\n- Self-describing wire envelope (`{k, v, d}`) for every cell.\n- Built-in primitives, ordered \u0026 hash-backed maps, typed \u0026 heterogeneous\n  containers, tagged unions, and a CRTP `StructCell` base for your own\n  structs.\n- Strict JSON deserialization: every documented field is required and the\n  `\"k\"` tag is verified against the static `kind_id`.\n- Non-throwing `try_*` parsing surface returning `std::expected\u003cT,\n  ParcelError\u003e` (when `\u003cexpected\u003e` is available).\n- `std::format`, `std::hash`, `operator\u003c=\u003e`, `std::generator` walk\n  helpers, and `std::expected` integration.\n- `std::chrono`, `std::filesystem::path`, `std::array`, `std::deque`,\n  `std::list`, `std::set`, `std::unordered_map\u003cstd::string, T\u003e`,\n  `std::variant`, and `std::optional` adapters out of the box.\n- Doxygen reference, CMake + Meson builds, ASan/UBSan, clang-tidy and\n  coverage gates wired into the Makefile and CI.\n\n## Requirements\n\n- C++23 (GCC 14+, Clang 18+, Apple Clang).\n- CMake ≥ 3.25 *or* Meson ≥ 1.3.\n- `nlohmann_json` 3.12.0 and GoogleTest 1.15.2 are fetched automatically;\n  you do not need to install them.\n\n## Install\n\n### CMake\n\nparcel is header-only. Pull it into your CMake project with `FetchContent`,\npinned to a release tarball:\n\n```cmake\ninclude(FetchContent)\nFetchContent_Declare(\n        cpp-parcel\n        URL https://github.com/aurimasniekis/cpp-parcel/archive/refs/tags/v0.2.1.tar.gz\n        URL_HASH SHA256=d9c81134a53d589c477cdb2b8afc162eade74050f2f3d783e4a6d1a6237b3085\n)\nFetchContent_MakeAvailable(cpp-parcel)\n\nadd_executable(my_app main.cpp)\ntarget_link_libraries(my_app PRIVATE parcel::parcel)\n```\n\nA complete worked example, including the override pattern used for local\ndevelopment, lives in [`examples/consumers/fetch_content/`](examples/consumers/fetch_content/).\n\n### Meson\n\nparcel ships a `meson.build` so it can be consumed as a Meson subproject.\nDrop a wrap file in `subprojects/parcel.wrap`:\n\n```ini\n[wrap-file]\ndirectory       = cpp-parcel-0.2.1\nsource_url      = https://github.com/aurimasniekis/cpp-parcel/archive/refs/tags/v0.2.1.tar.gz\nsource_filename = cpp-parcel-0.2.1.tar.gz\nsource_hash     = d9c81134a53d589c477cdb2b8afc162eade74050f2f3d783e4a6d1a6237b3085\n\n[provide]\nparcel = parcel_dep\n```\n\nThen in your `meson.build`:\n\n```meson\nparcel_dep = dependency('parcel', version: '\u003e=0.2.1')\n\nexecutable('my_app', 'main.cpp', dependencies: [parcel_dep])\n```\n\n`nlohmann_json` is pulled in transitively via parcel's own wrap, the same\nway CMake's `FetchContent` pulls it for that build.\n\n## Hello world\n\nConstruct a primitive cell, serialize it, and read it back through the\nregistry. *(The exact snippet below lives at\n[`examples/hello.cpp`](examples/hello.cpp) and runs via `make example`;\nsee [`examples/primitive_demo.cpp`](examples/primitive_demo.cpp) for a\nlonger tour.)*\n\n```cpp\n#include \u003cparcel/parcel.h\u003e\n#include \u003ciostream\u003e\n\nint main() {\n    parcel::ParcelRegistry registry;             // ships with all builtins\n    parcel::I32Cell answer = 42;\n\n    auto wire     = answer.to_json();            // {\"k\":\"i32\",\"v\":42}\n    auto restored = registry.cell_from_json(wire);\n\n    std::cout \u003c\u003c restored-\u003ekind() \u003c\u003c \" = \"       // \"i32 = 42\"\n              \u003c\u003c restored \u003c\u003c '\\n';\n}\n```\n\n`\u003cparcel/parcel.h\u003e` is the only include you need for the public API.\nIt pulls in the umbrella registry, the std-adapter cells (chrono,\nfilesystem, hash maps), and the formatting and walking helpers.\n\n## Wire format\n\nEvery cell serializes to `{\"k\": \u003ckind\u003e, \"v\": \u003cvalue\u003e, \"d\": \u003cdisplay info\u003e}`. The\n`\"d\"` block is omitted when no display info is set. Inside a struct cell, every\nfield is itself a `{k, v}` cell:\n\n```json\n{\n  \"k\": \"s:person\",\n  \"v\": {\n    \"id\":   {\"k\": \"i32\",    \"v\": 1},\n    \"name\": {\"k\": \"string\", \"v\": \"Alice\"}\n  }\n}\n```\n\n128-bit integers are encoded as decimal strings because JSON has no native\ninteger type wider than 53 bits. Deserialization is **strict**: every\ndocumented field must be present and `\"k\"` is verified against the cell's\nstatic `kind_id`. Run `make docs` for the full reference.\n\n## Built-in cell types\n\nA default-constructed `ParcelRegistry` already contains every primitive,\nthe heterogeneous and typed list/map/hash-map families, plus the chrono\nand filesystem adapter cells. Pass a `BuiltinsOptions{}` to opt out of\nany of those four batches.\n\n### Primitives\n\n| C++ type        | cell alias   | wire kind | notes                                                  |\n|-----------------|--------------|-----------|--------------------------------------------------------|\n| `bool`          | `BoolCell`   | `bool`    |                                                        |\n| `char`          | `CharCell`   | `char`    | one-character JSON string                              |\n| `std::int8_t`   | `I8Cell`     | `i8`      |                                                        |\n| `std::int16_t`  | `I16Cell`    | `i16`     |                                                        |\n| `std::int32_t`  | `I32Cell`    | `i32`     |                                                        |\n| `std::int64_t`  | `I64Cell`    | `i64`     |                                                        |\n| `parcel::i128`  | `I128Cell`   | `i128`    | encoded as a decimal string (JSON has no 128-bit ints) |\n| `std::uint8_t`  | `U8Cell`     | `u8`      |                                                        |\n| `std::uint16_t` | `U16Cell`    | `u16`     |                                                        |\n| `std::uint32_t` | `U32Cell`    | `u32`     |                                                        |\n| `std::uint64_t` | `U64Cell`    | `u64`     |                                                        |\n| `parcel::u128`  | `U128Cell`   | `u128`    | encoded as a decimal string                            |\n| `float`         | `FloatCell`  | `f32`     |                                                        |\n| `double`        | `DoubleCell` | `f64`     |                                                        |\n| `std::string`   | `StringCell` | `string`  |                                                        |\n\nThe 128-bit primitives are gated on `COMMONS_HAS_INT128` (from commons) and are\npresent for any compiler that exposes `__int128`.\n\n### Containers\n\nFor each primitive `T` listed above, the corresponding aliases exist:\n\n| family             | example aliases                                             | wire kind   |\n|--------------------|-------------------------------------------------------------|-------------|\n| typed list         | `I32ListCell`, `StringListCell`, `BoolListCell`, …          | `l:\u003celem\u003e`  |\n| typed map          | `I32MapCell`, `StringMapCell`, `BoolMapCell`, …             | `m:\u003celem\u003e`  |\n| typed hash map     | `I32HashMapCell`, `StringHashMapCell`, `BoolHashMapCell`, … | `hm:\u003celem\u003e` |\n| heterogeneous list | `ListCell`                                                  | `l`         |\n| heterogeneous map  | `MapCell` (string keys, ordered)                            | `m`         |\n| heterogeneous hash | `HashMapCell` (string keys, unordered storage)              | `hm`        |\n\nHash-map cells use `std::unordered_map` storage. Iteration in storage is\nunspecified, but the wire form is canonical (sorted keys) so equal maps\nalways serialize identically.\n\n### Std-adapter cells\n\n| cell                  | wire kind          | wire shape                     |\n|-----------------------|--------------------|--------------------------------|\n| `SystemTimePointCell` | `time:sys_seconds` | i64 epoch seconds              |\n| `UnixMillisCell`      | `time:unix_ms`     | i64 epoch milliseconds         |\n| `TimestampMsCell`     | `time:unix_ms`     | alias of `UnixMillisCell`      |\n| `DurationMsCell`      | `time:ms`          | i64 milliseconds               |\n| `YmdCell`             | `time:ymd`         | ISO-8601 `\"YYYY-MM-DD\"` string |\n| `PathCell`            | `fs:path`          | UTF-8 portable path string     |\n\nUser-defined cells live in two more namespaces: structs under `s:`\n(e.g. `s:person`) and unions under `u:` with the alternatives joined by\ncommas (e.g. `u:i32,string`).\n\n## By example\n\nEach section quotes a tight version of the matching `examples/*.cpp`. Open\nthe file for the full runnable form.\n\n### Define a typed struct\n\nYou write the data in plain C++ and a small wrapper class teaches parcel\nhow to serialize it. `StructCell` is a CRTP base — *Curiously Recurring\nTemplate Pattern*, where the wrapper passes itself in as a template arg so\nthe base can call back into it. The third template arg is the bare kind\nid; parcel prepends `s:`, so the wire kind below is `\"s:person\"`.\n\n`std::optional\u003cT\u003e` becomes an optional field — omitted from the wire when\nempty. *(See [`examples/struct_demo.cpp`](examples/struct_demo.cpp).)*\n\n```cpp\nstruct Person {\n    std::int32_t id{};\n    std::string  name;\n    std::optional\u003cstd::string\u003e email;\n    std::vector\u003cstd::string\u003e   roles;\n};\n\nclass PersonCell : public parcel::StructCell\u003cPersonCell, Person, \"person\"\u003e {\npublic:\n    using StructCell::StructCell;\n\n    static auto field_descriptors() {\n        return parcel::FieldsBuilder\u003cPerson\u003e{}\n            .field\u003c\u0026Person::id\u003e(\"id\")\n            .field\u003c\u0026Person::name\u003e(\"name\")\n            .field\u003c\u0026Person::email\u003e(\"email\")   // optional\u003cstring\u003e — omitted when empty\n            .field\u003c\u0026Person::roles\u003e(\"roles\")   // vector\u003cstring\u003e   — typed list on the wire\n            .build();\n    }\n};\n```\n\n`FieldsBuilder` infers each field's cell type from its C++ type via\n`default_cell_for`. The set of supported defaults is documented under\n*Standard library interop* below.\n\n**Required statics on a `StructCell` subclass:**\n\n| static                | required? | what it does                                                           |\n|-----------------------|-----------|------------------------------------------------------------------------|\n| `kind_id`             | auto      | synthesized as `\"s:\" + StructId` — never declare it manually           |\n| `field_descriptors()` | yes       | returns the result of a `FieldsBuilder\u003cPayload\u003e{}.field\u003c…\u003e(…).build()` |\n| `display_info()`      | optional  | cell-level display info; defaults to an empty `parcel::DisplayInfo{}`  |\n| `allow_extra_fields`  | optional  | `true` opts into lenient deserialization; defaults to `false`          |\n\n### Capture extra struct fields\n\nWhen `Derived::allow_extra_fields` is `true`, unknown JSON keys are\nrouted through the registry and retained in `extras` (a\n`std::map\u003cstd::string, parcel::cell_t\u003e`). The on-the-wire round trip is\npreserved. *(See\n[`examples/struct_extras_demo.cpp`](examples/struct_extras_demo.cpp).)*\n\n```cpp\nclass FlexibleEventCell\n    : public parcel::StructCell\u003cFlexibleEventCell, FlexibleEvent, \"flexible_event\"\u003e {\npublic:\n    using StructCell::StructCell;\n    static constexpr bool allow_extra_fields = true;\n\n    static auto field_descriptors() {\n        return parcel::FieldsBuilder\u003cFlexibleEvent\u003e{}\n            .field\u003c\u0026FlexibleEvent::id\u003e(\"id\")\n            .build();\n    }\n};\n```\n\nWithout that flag, an unknown key throws — matching parcel's strict\ndeserialization stance.\n\n### Inferred field types and defaults\n\n`FieldsBuilder::field\u003cMemberPtr\u003e(key)` looks the cell type up via\n`parcel::default_cell_for\u003cFieldType\u003e` so most ordinary members need no\nexplicit cell argument. The default-cell resolver covers:\n\n- every primitive backed by a `PrimitiveCell`,\n- `std::vector\u003cT\u003e`, `std::array\u003cT, N\u003e`, `std::deque\u003cT\u003e`,\n  `std::list\u003cT\u003e`, and `std::set\u003cT\u003e` → `TypedListCell\u003c…\u003e`,\n- `std::map\u003cstd::string, T\u003e` → `TypedMapCell\u003c…\u003e`,\n- `std::unordered_map\u003cstd::string, T\u003e` → `TypedMapCell\u003c…\u003e` (sorted on\n  the wire),\n- `std::optional\u003cT\u003e` → the same wrapper as `T`, with optionality\n  handled by struct-field absence,\n- `std::variant\u003cTs...\u003e` → `UnionCell\u003cdefault_cell_for_t\u003cTs\u003e...\u003e`,\n- any user cell tagged with `PARCEL_DEFAULT_CELL`.\n\n*(See [`examples/defaults_demo.cpp`](examples/defaults_demo.cpp) and\n[`examples/std_interop_demo.cpp`](examples/std_interop_demo.cpp).)*\n\n### Make your own primitive cell\n\nAdding a new primitive is a short recipe: define a storage type, give it\nJSON conversions, derive `BaseCell`, declare a `kind_id`, and register\nit. *(See\n[`examples/custom_primitive_demo.cpp`](examples/custom_primitive_demo.cpp).)*\n\n```cpp\nstruct Uuid { std::uint64_t hi, lo; /* with to_hex / from_hex */ };\n\nvoid to_json  (parcel::json_t\u0026 j, Uuid const\u0026 u) { j = u.to_hex(); }\nvoid from_json(parcel::json_t const\u0026 j, Uuid\u0026 u) { u = Uuid::from_hex(j.get\u003cstd::string\u003e()); }\n\nclass UuidCell : public parcel::BaseCell\u003cUuidCell, Uuid\u003e {\n    using base_t = parcel::BaseCell\u003cUuidCell, Uuid\u003e;\npublic:\n    using base_t::base_t;\n    using base_t::operator=;\n\n    static constexpr std::string_view kind_id = \"uuid\";\n\n    std::string to_string() const override { return value.to_hex(); }\n\n    static parcel::cell_t from_json(parcel::json_t const\u0026 j,\n                                    parcel::ParcelRegistry const\u0026) {\n        auto v = base_t::cell_from_json\u003cUuid\u003e(j, kind_id);\n        auto cell = std::make_shared\u003cUuidCell\u003e(v);\n        base_t::absorb_display_info(j, cell);\n        return cell;\n    }\n\n    static parcel::cell_type_descriptor_t descriptor() {\n        static const auto d = std::make_shared\u003cparcel::SimpleCellTypeDescriptor\u003cUuidCell\u003e\u003e(\n            parcel::DisplayInfo{.name = \"Uuid\"});\n        return d;\n    }\n};\n\n// Lets FieldsBuilder and parcel::cell(uuid) pick UuidCell automatically.\nPARCEL_DEFAULT_CELL(UuidCell);\n```\n\n`PARCEL_DEFAULT_CELL(CellT)` is a one-line macro that specializes\n`parcel::default_cell_for\u003cCellT::storage_t\u003e`. Then\n`registry.register_kind(UuidCell::descriptor())` and `UuidCell` is a\nfully wire-capable cell on the same footing as `I32Cell` or `StringCell`.\n\n### The registry\n\nThe registry is what turns \"some JSON\" into \"the right cell\". A default-\nconstructed `ParcelRegistry` already contains every built-in primitive,\nlist, map, hash map, and the std-adapter cells; you only register your\nown kinds. Pass a `BuiltinsOptions{}` if you want a leaner registry —\nthe four flags (`primitives`, `collections`, `typed_collections`, `std`)\ntoggle each batch independently. *(See\n[`examples/registry_demo.cpp`](examples/registry_demo.cpp).)*\n\n```cpp\n// Default — every primitive + ListCell/MapCell/HashMapCell + typed variants\n//           + chrono and filesystem cells.\nparcel::ParcelRegistry registry;\n\n// Or: opt out of typed collections to shrink the registry.\nparcel::ParcelRegistry slim{{.typed_collections = false}};\n\n// Or: skip the std-adapter cells entirely.\nparcel::ParcelRegistry no_std{{.std = false}};\n\nregistry.register_kind(PersonCell::descriptor());\n\nconst std::vector\u003cparcel::json_t\u003e wire {\n    parcel::I32Cell{42}.to_json(),\n    parcel::StringCell{\"hello\"}.to_json(),\n    PersonCell{Person{.id = 1, .name = \"Alice\"}}.to_json(),\n};\nfor (auto const\u0026 j : wire) {\n    auto cell = registry.cell_from_json(j);\n    std::cout \u003c\u003c '[' \u003c\u003c cell-\u003ekind() \u003c\u003c \"] \" \u003c\u003c cell \u003c\u003c '\\n';\n}\n```\n\nThe registry also supports introspection and schema export:\n\n```cpp\nregistry.count();                                       // total kinds registered\nregistry.contains(\"s:person\");                          // bool\nregistry.kinds();                                       // vector\u003cstring_view\u003e\n\n// All struct kinds; all kinds backed by std::string.\nregistry.find_by_category(parcel::descriptor::CellCategory::Struct);\nregistry.find_by_storage\u003cstd::string\u003e();\n\n// Transitive schema closure — every kind reachable from \"s:person\".\nauto def = registry.define(\"s:person\");\nstd::cout \u003c\u003c def.to_json().dump(2) \u003c\u003c '\\n';\n```\n\nVariadic registration helpers are also available:\n\n```cpp\nregistry.register_cells\u003cPersonCell, AddressCell\u003e();        // by cell type\nregistry.register_kinds(PersonCell::descriptor(), ...);    // by descriptor\n```\n\n### Wrap any value with `parcel::cell(...)` or `Cell::of(...)`\n\nTwo convenience factories build `cell_t` (a.k.a. `std::shared_ptr\u003cICell\u003e`)\nwithout spelling out `std::make_shared`:\n\n```cpp\nauto a = parcel::cell(42);                  // -\u003e shared_ptr\u003cI32Cell\u003e      (inferred)\nauto b = parcel::cell(\"hi\");                // -\u003e shared_ptr\u003cStringCell\u003e   (const char* -\u003e string)\nauto c = parcel::cell(Person{.id = 1});     // -\u003e shared_ptr\u003cPersonCell\u003e   (via PARCEL_DEFAULT_CELL)\n\nauto d = parcel::I32Cell::of(7);            // -\u003e shared_ptr\u003cI32Cell\u003e      (explicit cell type)\nauto e = PersonCell::of(Person{.id = 2});   // -\u003e shared_ptr\u003cPersonCell\u003e   (forwards to ctor)\n\nauto u = parcel::I32Cell::unique(7);        // -\u003e unique_ptr\u003cI32Cell\u003e      (sole-owner sibling)\n```\n\n`parcel::cell(v)` looks the wrapper up via `default_cell_for\u003cT\u003e`; every\nbuilt-in primitive plus any cell registered with `PARCEL_DEFAULT_CELL` is\neligible. `Cell::of(args...)` skips the lookup and forwards straight to\n`std::make_shared\u003cCell\u003e`. `Cell::unique(args...)` is the\n`std::unique_ptr` equivalent for callers who want sole ownership. *(See\n[`examples/cell_handle_demo.cpp`](examples/cell_handle_demo.cpp) for\nmore on `cell_t` ownership.)*\n\n### Annotate cells with display info\n\nEvery cell can carry a small display-info block — `name`, `description`,\n`icon`, `color` — that travels with the value under `\"d\"`. Builders are\nimmutable: each returns a fresh cell. *(See\n[`examples/display_info_demo.cpp`](examples/display_info_demo.cpp).)*\n\n```cpp\nauto annotated = parcel::I32Cell::of(42)\n    -\u003ewith_name(\"Answer\")\n    -\u003ewith_description(\"To life, the universe, and everything\")\n    -\u003ewith_icon(\"star\")\n    -\u003ewith_color(\"#ffcc00\");\n\nstd::cout \u003c\u003c annotated-\u003eto_json().dump(2);\n// {\"k\":\"i32\",\"v\":42,\"d\":{\"name\":\"Answer\", ...}}\n```\n\n`with_display_info(DisplayInfo{...})` replaces the whole block at once\n(the accessor and builder are named for the `DisplayInfo` they carry; the\nwire key stays the terse `\"d\"`). Reading goes through\n`cell-\u003eoverridden_display_info()`, which returns a `std::optional\u003cDisplayInfo\u003e`.\nComparison and hashing both ignore display info — two cells with the same\n`k`/`v` but different `overridden_display_info()` are equivalent.\n\n### Lists \u0026 maps\n\nTwo flavours: typed (homogeneous, raw scalars on the wire) and generic\n(heterogeneous, full cells on the wire). The free helper\n`parcel::cell(x)` wraps any built-in type in the right cell. *(See\n[`examples/list_demo.cpp`](examples/list_demo.cpp),\n[`examples/map_demo.cpp`](examples/map_demo.cpp).)*\n\n```cpp\nparcel::I32ListCell ints{1, 2, 3};                   // kind = \"l:i32\"\nints.push_back(4);\n\nparcel::ListCell mixed{                              // kind = \"l\"\n    parcel::cell(42),\n    parcel::cell(\"hello\"),\n    parcel::cell(true),\n};\n\nparcel::StringMapCell tags{                          // kind = \"m:string\"\n    {\"role\", std::string{\"admin\"}},\n    {\"env\",  std::string{\"prod\"}},\n};\n```\n\n### Reuse fields across struct cells\n\nStruct cells can splice another struct's fields in, override one by key,\nor drop one. The wire stays flat — every field, inherited or not, lives\nat the top of the cell's `\"v\"` object. *(See\n[`examples/struct_inheritance_demo.cpp`](examples/struct_inheritance_demo.cpp).)*\n\n```cpp\nclass HomeAddressCell : public parcel::StructCell\u003cHomeAddressCell, HomeAddress, \"home_address\"\u003e {\npublic:\n    using StructCell::StructCell;\n    static auto field_descriptors() {\n        return parcel::FieldsBuilder\u003cHomeAddress\u003e{}\n            .extend\u003cStreetAddressCell\u003e()                // splice street + city\n            .remove_field(\"city\")                       // … but drop city\n            .field\u003c\u0026StreetAddress::street\u003e(\"street\")    // override street (last-wins)\n            .name(\"StreetOverridden\")\n            .field\u003c\u0026HomeAddress::label\u003e(\"label\")        // add a new field\n            .build();\n    }\n};\n```\n\n### Tagged unions\n\nA `UnionCell\u003cTs...\u003e` is a closed-set polymorphic cell — it holds exactly\none of a fixed list of alternatives. The wire `\"k\"` lists every\nalternative in template order (e.g. `\"u:i32,string,bool\"`) and the inner\n`\"v\"` is itself a `{k,v}` cell so the active alternative is always\nrecoverable. *(See [`examples/union_demo.cpp`](examples/union_demo.cpp)\nand [`examples/union_visit_demo.cpp`](examples/union_visit_demo.cpp).)*\n\n```cpp\nusing IntStrBool = parcel::UnionCell\u003cparcel::I32Cell,\n                                     parcel::StringCell,\n                                     parcel::BoolCell\u003e;\n\nparcel::ParcelRegistry registry;\nregistry.register_kind(IntStrBool::descriptor());\n\nIntStrBool u = std::int32_t{42};                  // active = I32Cell\nu = std::string{\"hello\"};                          // switch alternative\nu = false;                                         // … and again\n\nstd::cout \u003c\u003c u.active_kind() \u003c\u003c \" : \"             // \"bool : false\"\n          \u003c\u003c u.to_string() \u003c\u003c '\\n';\n\n// Wire shape: {\"k\":\"u:i32,string,bool\",\"v\":{\"k\":\"bool\",\"v\":false}}\nauto restored = registry.cell_from_json(u.to_json());\n\n// Visit pattern with overload sets:\nparcel::visit(parcel::Overload{\n    [](std::int32_t)        { /* … */ },\n    [](std::string const\u0026)  { /* … */ },\n    [](bool)                { /* … */ },\n}, u);\n```\n\n`u.get\u003cI\u003e()` retrieves by alternative index; `u.get\u003cS\u003e()` retrieves by\nthe underlying storage type; `u.get_if\u003cI\u003e()` / `u.get_if\u003cS\u003e()` return a\npointer (or `nullptr`) instead of throwing. Free `visit`, `get`, and\n`get_if` overloads mirror the member-function counterparts.\n\n### Errors and non-throwing parsing\n\nStrict deserialization throws on malformed input. When `\u003cexpected\u003e` is\navailable, every parsing entry point also has a non-throwing twin that\nreturns `std::expected\u003c…, parcel::ParcelError\u003e`. `ParcelError` carries\na coarse `code` (`InvalidJson`, `KindMismatch`, `UnknownKind`,\n`MissingField`, `TypeError`), a `message`, and (when relevant) the\noffending `kind` and `field`. *(See\n[`examples/error_handling_demo.cpp`](examples/error_handling_demo.cpp)\nand [`examples/format_io_demo.cpp`](examples/format_io_demo.cpp).)*\n\n```cpp\nauto cell = registry.try_cell_from_string(payload);\nif (!cell) {\n    std::cerr \u003c\u003c cell.error().to_string() \u003c\u003c '\\n';\n    return 1;\n}\n\nif (auto person = parcel::try_cell_cast\u003cPersonCell\u003e(*cell); person) {\n    use(*person);\n}\n```\n\nThe full non-throwing surface:\n\n| function                               | header                |\n|----------------------------------------|-----------------------|\n| `ParcelRegistry::try_cell_from_json`   | `\u003cparcel/registry.h\u003e` |\n| `ParcelRegistry::try_cell_from_string` | `\u003cparcel/registry.h\u003e` |\n| `parcel::try_cell_cast\u003cC\u003e`             | `\u003cparcel/cell.h\u003e`     |\n| `parcel::try_cell_from_stream`         | `\u003cparcel/json_io.h\u003e`  |\n| `parcel::try_cell_from_bytes`          | `\u003cparcel/json_io.h\u003e`  |\n\n### Library-style bases *(advanced)*\n\nFor library authors who want to ship a CRTP base that owns common fields\nand carves its own kind-id namespace, use `SelfStructCell` (the deriving\nclass is itself the payload) together with `id_join_lit_v` to compose\nthe prefix. Concrete subclasses then declare a tiny\n`event_field_descriptors` hook. *(See\n[`examples/intrusive_struct_demo.cpp`](examples/intrusive_struct_demo.cpp).)*\n\n```cpp\ntemplate \u003ctypename Self, parcel::fixed_string EventId\u003e\nclass BaseEvent : public parcel::SelfStructCell\u003cSelf\u003e {\npublic:\n    static constexpr std::string_view kind_id =\n        parcel::id_join_lit_v\u003c\"s:event:\", EventId\u003e;          // e.g. \"s:event:something\"\n\n    static auto field_descriptors() {\n        auto b = parcel::FieldsBuilder\u003cSelf\u003e{};\n        return Self::event_field_descriptors(b)\n            .template field\u003c\u0026BaseEvent::timestamp_\u003e(\"timestamp\")\n            .build();\n    }\nprotected:\n    std::int64_t timestamp_{};\n};\n\nclass SomethingEvent : public BaseEvent\u003cSomethingEvent, \"something\"\u003e {\npublic:\n    std::string  action;\n    std::int32_t weight{};\n\n    static auto\u0026 event_field_descriptors(parcel::FieldsBuilder\u003cSomethingEvent\u003e\u0026 b) {\n        return b.field\u003c\u0026SomethingEvent::action\u003e(\"action\")\n                .field\u003c\u0026SomethingEvent::weight\u003e(\"weight\");\n    }\n};\n```\n\nThis is heavier C++ than the rest — reach for it only when you genuinely\nwant a shared base across many struct cells.\n\n**Required statics on a `SelfStructCell` subclass:**\n\n| static                | required? | what it does                                                                |\n|-----------------------|-----------|-----------------------------------------------------------------------------|\n| `kind_id`             | yes       | declared by the deriving class (often via `id_join_lit_v` in the CRTP base) |\n| `field_descriptors()` | yes       | returns the result of a `FieldsBuilder\u003cDerived\u003e{}.field\u003c…\u003e(…).build()`      |\n| `display_info()`      | optional  | cell-level display info; defaults to an empty `parcel::DisplayInfo{}`       |\n| `allow_extra_fields`  | optional  | `true` opts into lenient deserialization; defaults to `false`               |\n\n### Optional adapters for downstream libraries\n\nIf you ship a library and want to offer parcel `Cell` wrappers *without*\nforcing every consumer to depend on parcel, ship them in a separate\nadapter header (e.g. `\u003cmylib/parcel.h\u003e`) gated on `__has_include`:\n\n```cpp\n// mylib/parcel.h\n#pragma once\n#if __has_include(\u003cparcel/parcel.h\u003e)\n    #include \u003cmylib/color.h\u003e\n    #include \u003cparcel/parcel.h\u003e\n    // ...your StructCell + PARCEL_DEFAULT_CELL declarations...\n    #define MYLIB_HAS_PARCEL 1\n#else\n    #define MYLIB_HAS_PARCEL 0\n#endif\n```\n\nConventions: namespace your wire kind ids (`\"mylib.color\"` not\n`\"color\"`) so multiple libraries coexist in one registry, and always\n`#define MYLIB_HAS_PARCEL` to `0` or `1` so consumers can `#if` on it\nwithout `-Wundef` warnings. A complete worked example —\n[`examples/consumers/optional_adapter/`](examples/consumers/optional_adapter/) —\nbuilds in both modes from one tree.\n\n## Standard library interop\n\nparcel ships C++23 adapter headers and free helpers that lean into the\nstandard library so you don't have to wrap every value yourself. All of\nthem are pulled in by `\u003cparcel/parcel.h\u003e`.\n\n### Comparison and hashing\n\nEvery cell supports `operator==` and `operator\u003c=\u003e`\n(`std::partial_ordering`, because some storage types — like `double`\nwith NaN — aren't totally ordered). `std::hash\u003cparcel::cell_t\u003e` and\n`std::hash\u003cparcel::ICell\u003e` are specialized too, so cells drop into\n`std::set` / `std::unordered_set`. Comparison and hashing both ignore\ndisplay info.\n\n### Type-safe casting\n\n```cpp\nparcel::cell_t any = registry.cell_from_json(j);\n\n// Throws on null or kind mismatch:\nauto i32 = parcel::cell_cast\u003cparcel::I32Cell\u003e(any);\n\n// Like std::optional — present iff the cast succeeds, no exception:\nif (auto v = parcel::as\u003cparcel::I32Cell\u003e(any)) { use(*v); }\n\n// Default fallback if missing:\nint port = parcel::value_or\u003cparcel::I32Cell\u003e(cfg, 8080);\n\n// std::expected variant:\nauto p = parcel::try_cell_cast\u003cPersonCell\u003e(any);\n```\n\n### Stream and byte JSON I/O — `\u003cparcel/json_io.h\u003e`\n\n`cell_from_stream`, `cell_from_bytes`, and `cell_to_stream` skip the\n`std::string` round-trip when reading/writing JSON. Each has a\nnon-throwing `try_*` counterpart that returns `std::expected`.\n\n### Tree walks — `\u003cparcel/walk.h\u003e`\n\n`parcel::walk_to_vector(root)` returns every `(json-pointer-path, cell)`\npair in a `ListCell` / `MapCell` tree, depth-first. When `\u003cgenerator\u003e`\nis available, `parcel::walk(root)` returns a truly lazy `std::generator`\nof the same shape — each pull advances exactly one node, so the tree is\nnever fully materialized. `StructCell` and `UnionCell` are leaves in\nboth walks; descend into struct fields explicitly via descriptor\nintrospection if you need to.\n\n### Ranges, spans, and `std::from_range`\n\n`TypedListCell` constructs from any `std::ranges::input_range` (via\n`std::from_range`) and exposes `as_span()` for read or write views over\nits storage. `TypedMapCell` constructs from a paired range and exposes\n`keys()` / `values()` views (also on `MapCell`).\n\n### `std::chrono` — `\u003cparcel/ext/chrono.h\u003e`\n\n`SystemTimePointCell`, `UnixMillisCell` (alias `TimestampMsCell`),\n`DurationMsCell`, and `YmdCell` cover the typical wire shapes\n(see *Std-adapter cells* above). All four are pre-registered when\n`BuiltinsOptions::std` is true (the default).\n\n### `std::filesystem::path` — `\u003cparcel/ext/filesystem.h\u003e`\n\n`PathCell` wraps `std::filesystem::path` as a portable UTF-8 string via\n`path::generic_string()`. Wire kind: `fs:path`. Pre-registered when\n`BuiltinsOptions::std` is true.\n\n### Hash-backed maps — `\u003cparcel/unordered_map.h\u003e`\n\n`TypedHashMapCell\u003cT\u003e` and `HashMapCell` are `std::unordered_map`-backed\nsiblings of `TypedMapCell` / `MapCell`, with wire kinds `hm:\u003celem\u003e` and\n`hm`. Iteration in storage is unspecified, but the wire form is\ncanonical (sorted keys) so two equal maps always serialize identically.\nThey are registered alongside the ordered maps when\n`BuiltinsOptions::collections` and `typed_collections` are on.\n\n### Heterogeneous helpers\n\n```cpp\nauto l = parcel::make_list(1, std::string(\"hi\"), true);   // ListCell\nauto m = parcel::make_map({{\"x\", parcel::cell(1)},\n                           {\"y\", parcel::cell(std::string(\"hi\"))}});\n```\n\n### `std::format` integration\n\nCells plug into `std::format`: `std::format(\"{}\", cell)` produces the\ncompact `to_string()` form, and `std::format(\"{:#}\", cell)` produces the\nmulti-line `to_formatted_string()` form. The same specializations cover\n`cell_t` (a null `cell_t` renders as `\"\u003cnull\u003e\"`):\n\n| spec    | output                                  |\n|---------|-----------------------------------------|\n| `{}`    | compact `to_string()`                   |\n| `{:#}`  | multi-line `to_formatted_string()`      |\n| `{:j}`  | compact JSON via `to_json().dump()`     |\n| `{:j2}` | pretty JSON via `to_json().dump(2)`     |\n| `{:k}`  | kind id only                            |\n\n`operator\u003c\u003c` for `std::ostream` uses the compact form.\n\nWhen `\u003cprint\u003e` is available (`__cpp_lib_print`), `parcel::print(...)` and\n`parcel::println(...)` thinly wrap `std::print` / `std::println` so cell\nvalues can flow into `stdout` with the same format specs above without\npulling `std::print` into every translation unit.\n\n## Project structure\n\n```\ninclude/parcel/         public API headers (parcel.h is the umbrella)\ninclude/parcel/ext/     std::chrono and std::filesystem adapters\nexamples/               runnable demos, one .cpp per topic\nexamples/consumers/     standalone downstream integration examples\ntests/                  GoogleTest suites (parcel_tests executable)\ncmake/                  warning, sanitizer, coverage, install helpers\ndocs/                   Doxyfile.in for `make docs`\nsubprojects/            Meson wraps for nlohmann_json and gtest\n```\n\n## Build \u0026 develop\n\nThe CMake build is the source of truth; the `Makefile` just memorizes the\ncommon invocations.\n\n### CMake options\n\n| option                      | default      | effect                                         |\n|-----------------------------|--------------|------------------------------------------------|\n| `PARCEL_BUILD_TESTS`        | top-level ON | Build `parcel_tests` and register with CTest   |\n| `PARCEL_BUILD_EXAMPLES`     | top-level ON | Build `parcel_*_demo` example targets          |\n| `PARCEL_BUILD_DOCS`         | OFF          | Add the `parcel_docs` Doxygen target           |\n| `PARCEL_ENABLE_CLANG_TIDY`  | OFF          | Run clang-tidy during the build                |\n| `PARCEL_ENABLE_SANITIZERS`  | OFF          | Compile with AddressSanitizer + UBSan          |\n| `PARCEL_ENABLE_COVERAGE`    | OFF          | Compile with Clang source-based coverage       |\n| `PARCEL_WARNINGS_AS_ERRORS` | top-level ON | `-Werror` / `/WX`                              |\n| `PARCEL_INSTALL`            | top-level ON | Generate install rules and CMake package files |\n\nConfigure presets in `CMakePresets.json`: `debug`, `release`,\n`relwithdebinfo`, `minsizerel`. Each has a matching build and test\npreset so `cmake --preset release \u0026\u0026 cmake --build --preset release \u0026\u0026\nctest --preset release` works out of the box.\n\n### Make targets\n\n| target          | what it does                                                   |\n|-----------------|----------------------------------------------------------------|\n| `make build`    | configure + build under `build/`                               |\n| `make test`     | configure + build + `ctest`                                    |\n| `make example`  | run `build/examples/parcel_hello`                              |\n| `make sanitize` | configure + build + test in `build-san/` with ASan + UBSan     |\n| `make tidy`     | configure + build in `build-tidy/` with clang-tidy             |\n| `make tidy-fix` | same as `tidy` but with `-DPARCEL_CLANG_TIDY_FIX=ON`           |\n| `make release`  | configure + build + test in `build-release/` (Release)         |\n| `make coverage` | configure + build + test in `build-coverage/`, emit HTML       |\n| `make docs`     | build the Doxygen reference into `build-docs/docs/html/`       |\n| `make format`   | `clang-format -i` over `include/`, `tests/`, `examples/`       |\n| `make ci`       | full pre-push gate (format + tidy + test + ASan + Release)     |\n\n`compile_commands.json` is exported automatically into the build\ndirectory for editor tooling.\n\n### Meson\n\n`meson.options` exposes `tests` and `examples` toggles (both default\n`false` for downstream consumers). To build everything locally:\n\n```\nmeson setup build-meson -Dtests=true -Dexamples=true\nmeson test  -C build-meson\n```\n\nWraps for `nlohmann_json` and `gtest` are pre-fetched under\n`subprojects/` so Meson can build offline.\n\n### Continuous integration\n\n`.github/workflows/ci.yml` runs five jobs on every push and pull\nrequest:\n\n- **build** — Ubuntu and macOS, Debug + Release, default + GCC-14 +\n  Clang-20.\n- **consumer** — builds `examples/consumers/fetch_content/` against the\n  in-tree checkout to catch downstream breakage.\n- **sanitizers** — ASan + UBSan run via `make sanitize`.\n- **clang-tidy** — `make tidy`.\n- **format** — `make format-check`.\n\n## Testing\n\nRun the full test suite with `make test` (or `ctest --test-dir build\n--output-on-failure`). The 11 GoogleTest suites under `tests/` cover:\n\n- `test_primitive.cpp` — every primitive cell, including 128-bit ints.\n- `test_list.cpp`, `test_map.cpp` — typed and heterogeneous containers.\n- `test_struct.cpp` — `StructCell`, optional/vector fields, inheritance,\n  `allow_extra_fields`.\n- `test_union.cpp` — `UnionCell`, active tracking, `get\u003cI\u003e` / `get\u003cS\u003e`.\n- `test_registry.cpp` — registry dispatch, introspection, schema export.\n- `test_display_info.cpp` — `DisplayInfo` and immutable `with_*` builders.\n- `test_compare.cpp` — `operator==`, `operator\u003c=\u003e`, `std::hash`.\n- `test_cell_helpers.cpp` — `parcel::cell()`, `Cell::of()`, `cell_cast`,\n  `as`, `value_or`.\n- `test_ergonomics.cpp` — `make_list` / `make_map`, ranges, `keys()` /\n  `values()`, variadic registration helpers.\n- `test_std_interop.cpp` — chrono cells, `PathCell`, hash-backed maps,\n  default-cell inference for `std::array` / `std::deque` / `std::list`\n  / `std::set`.\n- `test_parcel.cpp` — core wire format and round-trip.\n\n## Contributing\n\nContributions to the library are welcome! If you encounter any issues or have suggestions for\nimprovements,\nplease feel free to submit a pull request or open an issue on the project's repository.\n\n## License\n\nThis project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faurimasniekis%2Fcpp-parcel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faurimasniekis%2Fcpp-parcel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faurimasniekis%2Fcpp-parcel/lists"}