simple data struct serialization library for C++. With this library you can serialize and deserialize POD C++ structs directly to JSON or protobuf. When used together with etl library it doesn't need to allocate any memory, so its suitable for embedded environments (see extensions).
namespace PhoneBook
{
struct Person {
enum class PhoneType : int32_t {
MOBILE = 0,
HOME = 1,
WORK = 2,
};
struct PhoneNumber {
// phone number is always required
etl::string<16> 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
auto john = PhoneBook::Person{
.name = "John Doe",
.id = 1234,
.email = "jdoe@example.com",
};
//- serialize john to json-string
auto json = spb::json::serialize< std::string >( john );
//- deserialize john from json-string
auto person = spb::json::deserialize< PhoneBook::Person >( json );
//- serialize john to protobuf-vector
auto pb = spb::pb::serialize< std::vector< std::byte > >( john );
//- deserialize john from protobuf-vector
auto person2 = spb::pb::deserialize< PhoneBook::Person >( pb );
//- john == person == person2goal of this library is to make JSON and protobuf part of the C++ language itself.
There are literally a tons of JSON C++ libraries but most of them are designed in a way that the user needs to construct the json Object via some API and for serialization and deserialization there is a lot of boilerplate code like type/schema checking, to_json, from_json, macros... All this is needed to be done by the user, and it usually ends up with a conversion to some C++ struct.
spb works the other way around, from C++ struct to JSON or protobuf. With this approach user can focus only on the data, C++ struct, which is much more natural and spb will handle all the boring stuff like serialization/deserialization and type/schema checking.
spb is an alternative implementation of protobuf for C++. This is not an plugin for protoc but an replacement for protoc, so you don't need protobuf or protoc installed to use it. Serialization and deserialization to JSON or protobuf is compatible with protoc, in other words, data serialized with code generated by spb-protoc can be deserialized by code generated by protoc and vice versa.
- C++ compiler (at least C++20)
- cmake
- std library
- (optional) clang-format for code formatting
# add this repo to your project
add_subdirectory(external/spb-proto)
# compile proto files to C++
spb_protobuf_generate(PROTO_SRCS PROTO_HDRS ${CMAKE_SOURCE_DIR}/proto/person.proto)
# add generated files to your project
add_executable(myapp ${PROTO_SRCS} ${PROTO_HDRS})
# `spb-proto` is an interface library
# the main purpose is to update include path of `myapp`
target_link_libraries(myapp PUBLIC spb-proto)- define a schema for you data in a
person.protofile
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;
}- compile
person.protowithspb-protocintoperson.pb.handperson.pb.cc
spb_protobuf_generate(PROTO_SRCS PROTO_HDRS ${CMAKE_SOURCE_DIR}/proto/person.proto)observe the beautifully generated person.pb.h and person.pb.cc
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- use
Personstruct natively and de/serialize to/from json/pb
#include <person.pb.h>
auto john = PhoneBook::Person{
.name = "John Doe",
.id = 1234,
.email = "jdoe@example.com",
};
auto json = spb::json::serialize( john );
auto person = spb::json::deserialize< PhoneBook::Person >( json );
auto pb = spb::pb::serialize( john );
auto person2 = spb::pb::deserialize< PhoneBook::Person >( pb );
//- john == person == person2All generated messages (and enums) are using the following API include/spb/json.hpp and include/spb/pb.hpp
//- serialize message via writer (all other `serialize` are just wrappers around this one)
//- example: `auto serialized_size = spb::pb::serialize( message, my_writer );`
auto serialize( const auto & message, spb::io::writer on_write ) -> size_t;
//- return size in bytes of serialized message
//- example: `auto serialized_size = spb::pb::serialize_size( message );`
auto serialize_size( const auto & message ) -> size_t;
//- serialize message into container like std::string, std::vector, ...
//- example: `auto serialized_size = spb::pb::serialize( message, my_string );`
template < typename Message, spb::resizable_container Container >
auto serialize( const Message & message, Container & result ) -> size_t;
//- serialize message and return container like std::string, std::vector, ...
//- example: `auto my_string = spb::pb::serialize< std::string >( message );`
template < spb::resizable_container Container = std::string, typename Message >
auto serialize( const Message & message ) -> Container;
//- deserialize message from reader (all other `deserialize` are just wrappers around this one)
//- example: `spb::pb::deserialize( message, my_reader );`
void deserialize( auto & message, spb::io::reader on_read );
//- deserialize message from container like std::string, std::vector, ...
//- example: `spb::pb::deserialize( message, my_string );`
template < typename Message, spb::size_container Container >
void deserialize( Message & message, const Container & protobuf );
//- return deserialized message from container like std::string, std::vector, ...
//- example: `auto message = spb::pb::deserialize< Message >( my_string );`
template < typename Message, spb::size_container Container >
auto deserialize( const Container & protobuf ) -> Message;
//- return deserialized message from reader
//- example: `auto message = spb::pb::deserialize< Message >( my_reader );`
template < typename Message >
auto deserialize( spb::io::reader reader ) -> Message;API is prefixed with spb::json:: for json and spb::pb:: for protobuf,
template concepts spb::size_container and spb::resizable_container are defined in include/spb/concepts.h, spb::io::reader and spb::io::writer are user specified functions for IO, more info at include/io/io.hpp
| proto type | CPP type | GPB encoding |
|---|---|---|
bool |
bool |
varint |
float |
float |
4 bytes |
double |
double |
8 bytes |
int32 |
int32_t |
varint |
sint32 |
int32_t |
zig-zag varint |
uint32 |
uint32_t |
varint |
int64 |
int64_t |
varint |
sint64 |
int64_t |
zig-zag varint |
uint64 |
uint64_t |
varint |
fixed32 |
uint32_t |
4 bytes |
sfixed32 |
int32_t |
4 bytes |
fixed64 |
uint64_t |
8 bytes |
sfixed64 |
int64_t |
8 bytes |
string |
std::string |
utf8 string |
bytes |
std::vector< std::byte > |
base64 encoded in json |
| proto type modifier | CPP type modifier | Notes |
|---|---|---|
optional |
std::optional<Message> |
|
optional |
std::unique_ptr<Message> |
if there is cyclic dependency between messages ( A -> B, B -> A ) |
repeated |
std::vector<Message> |
See also extensions for user specific types and advanced usage.
navigate to the example directory.
- Make it work
- Make it right
- Make it fast
- parser for proto files (supported syntax:
proto2andproto3) - compile proto message to C++ data struct
- generate json de/serializer for generated C++ data struct (serialized json has to be compatible with GPB)
- generate protobuf de/serializer for generated C++ data struct (serialized pb has to be compatible with GPB)
- RPC is not implemented
- extend is not implemented