A lightweight C++20 library for Protocol Buffers and JSON serialization.
protoc, no Google toolchain, no external dependencies.proto files (proto2 or proto3).sprotoc tool to generate clean C++ source files (.pb.h + .pb.cc).// proto/person.proto, hand written
package PhoneBook;
message Person {
optional string name = 1;
optional int32 id = 2; // Unique ID number for this person.
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1; // phone number is always required
optional PhoneType type = 2;
}
// all registered phones
repeated PhoneNumber phones = 4;
}
# CMakeLists.txt, hand written
add_subdirectory(external/simple-protobuf) # or FetchContent
add_executable(myapp main.cpp proto/person.proto)
spb_protobuf_generate(TARGET myapp)
// proto/person.pb.h, generated
namespace PhoneBook
{
struct Person {
enum class PhoneType : int32_t {
MOBILE = 0,
HOME = 1,
WORK = 2,
};
struct PhoneNumber {
// phone number is always required
std::string number;
std::optional<PhoneType> type;
};
std::optional<std::string> name;
// Unique ID number for this person.
std::optional<int32_t> id;
std::optional<std::string> email;
// all registered phones
std::vector<PhoneNumber> phones;
};
} // namespace PhoneBook
// main.cpp, hand written
#include <iostream>
#include <proto/person.pb.h> // <- generated
int main() {
const auto john = phonebook::Person{
.name = "John Doe",
.id = 1234,
.email = "john@example.com",
.phones = {{.number = "123456789", .type = phonebook::Person::PhoneType::MOBILE}}
};
// JSON round-trip
const auto json = spb::json::serialize<std::string>(john);
std::cout << "JSON:\n" << json << "\n\n";
// Protobuf binary round-trip
const auto pb_bytes = spb::pb::serialize<std::vector<std::byte>>(john);
const auto decoded_json = spb::json::deserialize<phonebook::Person>(json);
const auto decoded_pb = spb::pb::deserialize<phonebook::Person>(pb_bytes);
// All equal: john == decoded_json == decoded_pb
}
protoc, no Google libs, no external dependencies - instead it uses its own proto-compiler called sprotoc.proto2/proto3 support (no editions)std::optional, std::vector, and enum class.zero heap allocations - only user data (dynamic strings/vectors) may allocate, which can be eliminated with for example ETL or static strings/vectors (std::array<...>)max_count for repeated fields and max_size for bytes/string| Proto | CPP type | Notes |
|---|---|---|
bool | bool | |
float/double | float/double | |
int32/sint32/sfixed32 | int32_t | |
fixed32/uint32 | uint32_t | |
int64/sint64/sfixed64 | int64_t | |
fixed64/uint64 | uint64_t | |
string | std::string | UTF-8 (Validated during de/serialize) |
bytes | std::vector<std::byte> | Base64 in JSON |
message | struct | |
enum | enum class | |
repeated | std::vector<MessageT> | |
optional | std::optional<MessageT> | Or std::unique_ptr<MessageT> for cycle dependencies |
map | std::map<KeyT, ValueT> | |
oneof | std::variant<std::monostate, ...> |
All types can be user-specified, see options.
See the example directory.
Fast as Google Protocol Buffers, tiny as nanopb
Measured on Linux/i7-8650U CPU @ 1.90GHz with GCC 16.1.1 -flto -O2 using nanobench.
See the benchmark directory for more details.
C++
95.6%
CMake
3.1%
A lightweight C++20 library for Protocol Buffers and JSON serialization.
protoc, no Google toolchain, no external dependencies.proto files (proto2 or proto3).sprotoc tool to generate clean C++ source files (.pb.h + .pb.cc).// proto/person.proto, hand written
package PhoneBook;
message Person {
optional string name = 1;
optional int32 id = 2; // Unique ID number for this person.
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1; // phone number is always required
optional PhoneType type = 2;
}
// all registered phones
repeated PhoneNumber phones = 4;
}
# CMakeLists.txt, hand written
add_subdirectory(external/simple-protobuf) # or FetchContent
add_executable(myapp main.cpp proto/person.proto)
spb_protobuf_generate(TARGET myapp)
// proto/person.pb.h, generated
namespace PhoneBook
{
struct Person {
enum class PhoneType : int32_t {
MOBILE = 0,
HOME = 1,
WORK = 2,
};
struct PhoneNumber {
// phone number is always required
std::string number;
std::optional<PhoneType> type;
};
std::optional<std::string> name;
// Unique ID number for this person.
std::optional<int32_t> id;
std::optional<std::string> email;
// all registered phones
std::vector<PhoneNumber> phones;
};
} // namespace PhoneBook
// main.cpp, hand written
#include <iostream>
#include <proto/person.pb.h> // <- generated
int main() {
const auto john = phonebook::Person{
.name = "John Doe",
.id = 1234,
.email = "john@example.com",
.phones = {{.number = "123456789", .type = phonebook::Person::PhoneType::MOBILE}}
};
// JSON round-trip
const auto json = spb::json::serialize<std::string>(john);
std::cout << "JSON:\n" << json << "\n\n";
// Protobuf binary round-trip
const auto pb_bytes = spb::pb::serialize<std::vector<std::byte>>(john);
const auto decoded_json = spb::json::deserialize<phonebook::Person>(json);
const auto decoded_pb = spb::pb::deserialize<phonebook::Person>(pb_bytes);
// All equal: john == decoded_json == decoded_pb
}
protoc, no Google libs, no external dependencies - instead it uses its own proto-compiler called sprotoc.proto2/proto3 support (no editions)std::optional, std::vector, and enum class.zero heap allocations - only user data (dynamic strings/vectors) may allocate, which can be eliminated with for example ETL or static strings/vectors (std::array<...>)max_count for repeated fields and max_size for bytes/string| Proto | CPP type | Notes |
|---|---|---|
bool | bool | |
float/double | float/double | |
int32/sint32/sfixed32 | int32_t | |
fixed32/uint32 | uint32_t | |
int64/sint64/sfixed64 | int64_t | |
fixed64/uint64 | uint64_t | |
string | std::string | UTF-8 (Validated during de/serialize) |
bytes | std::vector<std::byte> | Base64 in JSON |
message | struct | |
enum | enum class | |
repeated | std::vector<MessageT> | |
optional | std::optional<MessageT> | Or std::unique_ptr<MessageT> for cycle dependencies |
map | std::map<KeyT, ValueT> | |
oneof | std::variant<std::monostate, ...> |
All types can be user-specified, see options.
See the example directory.
Fast as Google Protocol Buffers, tiny as nanopb
Measured on Linux/i7-8650U CPU @ 1.90GHz with GCC 16.1.1 -flto -O2 using nanobench.
See the benchmark directory for more details.
C++
95.6%
CMake
3.1%