serpent¶
Zero-copy JSON and BJData for C++23. Annotate a type once; it reads and writes in every format, in both directions.
struct [[= serpent::serializable {}]] reading {
std::uint32_t at;
double celsius;
};
auto text = json::encode(value); // std::string
auto bytes = bjdata::encode(value); // std::vector<std::byte>
auto a = json::decode<reading>(text); // std::optional<reading>
auto b = bjdata::decode<reading>(bytes);
That annotation is the whole definition. The compiler already knows what the fields are called, so nothing lists them again — and nothing can fall out of step with them when one is added.
Say more where you need to¶
struct [[= serpent::serializable {},
= serpent::naming { serpent::naming_style::snake_case }]] link_config {
[[= serpent::key("ip")]] std::string ipAddress;
[[= serpent::skip {}]] int cacheGeneration;
int mtuBytes; // becomes "mtu_bytes"
[[= serpent::defaulted {}]] int retries = 3; // older documents may omit it
};
Ten annotations cover renaming, omitting, naming rules, optional and required members, enumerations that are words rather than numbers, and variants that say what they are. See Annotations.
And the compiler checks what it can see
Two members that end up as the same key, an enumeration where two values are the same word,
required on a member that is required already — those are build errors naming what they
found, rather than documents that come out wrong.
Reading is lazy and allocates nothing¶
auto document = json::reader::over(text);
document["port"].as<int>(); // parsed on demand
for (const auto &host : document["hosts"].array()) { } // walked in place
Nothing is inflated into an object graph first. A reader is a small handle over bytes you already own, strings and spans point into them, and stopping early costs only what you read. See Reading.
At a glance¶
| Headers, and one source file | C++23, one dependency: cpp-unaligned. The source file is the bundled real-number writer, and can be turned off |
| Allocates | only where you ask it to; reading allocates nothing |
| Without exceptions | the whole non-throwing tier stays intact under -fno-exceptions |
| Types you do not own | described from outside, with no conversion code — here |
| Output | BJData as small or as quick as it can be, whichever you prefer |
Reflection needs GCC 16.1 with -freflection
No other released compiler has the whole C++26 feature set yet, and the annotation is a
parse error on one that does not — so a header shared between toolchains carries both
forms behind #if SERPENT_HAS_REFLECTION. Naming your fields once by hand is the other
form, and nothing else about the library changes. See
Requirements.
Where to go¶
- :material-rocket-launch: Getting started — build it, run something
- :material-tag-text: Annotations — everything a type can say about itself
- :material-package-variant: Types and conversions — what works already, and writing your own
- :material-book-open-variant: Reading and Writing — the API
- :material-file-tree: A document as a tree — when the shape is decided at run time
- :material-speedometer: Benchmarks — against four other libraries, wins and losses