CppSerialization
February 28, 2026 ยท View on GitHub
C++ Serialization Library provides functionality to serialize/deserialize objects using different protocols such as Cap'n'Proto, FastBinaryEncoding, Flatbuffers, Protobuf, SimpleBinaryEncoding, zpp::bits, JSON.
Performance comparison based on the Domain model with one account, one wallet and three orders total size of 128 bytes:
| Protocol | Message size | Serialization time | Deserialization time |
|---|---|---|---|
| Cap'n'Proto | 208 bytes | 247 ns | 184 ns |
| FastBinaryEncoding | 234 bytes | 77 ns | 84 ns |
| FlatBuffers | 280 bytes | 272 ns | 81 ns |
| Protobuf | 120 bytes | 322 ns | 351 ns |
| SimpleBinaryEncoding | 138 bytes | 35 ns | 52 ns |
| zpp::bits | 130 bytes | 34 ns | 37 ns |
| JSON | 301 bytes | 696 ns | 291 ns |
CppSerialization API reference
Contents
- Features
- Requirements
- How to build?
- Domain model
- Cap'n'Proto serialization
- FastBinaryEncoding serialization
- FlatBuffers serialization
- Protobuf serialization
- SimpleBinaryEncoding serialization
- zpp::bits serialization
- JSON serialization
Features
- Cross platform (Linux, MacOS, Windows)
- Binary serialization using Cap'n'Proto library
- Binary serialization using FastBinaryEncoding library
- Binary serialization using FlatBuffers library
- Binary serialization using Protobuf library
- Binary serialization using SimpleBinaryEncoding library
- Binary serialization using zpp::bits library
- JSON serialization using RapidJSON library
Requirements
Optional:
How to build?
Linux: install required packages
sudo apt-get install -y binutils-dev uuid-dev
Install gil (git links) tool
pip3 install gil
Setup repository
git clone https://github.com/chronoxor/CppSerialization.git
cd CppSerialization
gil update
Linux
cd build
./unix.sh
MacOS
cd build
./unix.sh
Windows (Cygwin)
cd build
unix.bat
Windows (MSYS2)
cd build
unix.bat
Windows (MinGW)
cd build
mingw.bat
Windows (Visual Studio)
cd build
vs.bat
Domain model
The first step you should perform to use CppSerialization library is to provide a domain model (aka business objects). Domain model is a set of structures or classes that related to each other and might be aggregated in some hierarchy.
There is an example domain model which describes Account-Balance-Orders relation of some abstract trading platform:
#include <string>
#include <vector>
namespace TradeProto {
enum class OrderSide : uint8_t
{
BUY,
SELL
};
enum class OrderType : uint8_t
{
MARKET,
LIMIT,
STOP
};
struct Order
{
int Id;
char Symbol[10];
OrderSide Side;
OrderType Type;
double Price;
double Volume;
Order() : Order(0, "<\?\?\?>", OrderSide::BUY, OrderType::MARKET, 0.0, 0.0) {}
Order(int id, const std::string& symbol, OrderSide side, OrderType type, double price, double volume)
{
Id = id;
std::memcpy(Symbol, symbol.c_str(), std::min(symbol.size() + 1, sizeof(Symbol)));
Side = side;
Type = type;
Price = price;
Volume = volume;
}
};
struct Balance
{
char Currency[10];
double Amount;
Balance() : Balance("<\?\?\?>", 0.0) {}
Balance(const std::string& currency, double amount)
{
std::memcpy(Currency, currency.c_str(), std::min(currency.size() + 1, sizeof(Currency)));
Amount = amount;
}
};
struct Account
{
int Id;
std::string Name;
Balance Wallet;
std::vector<Order> Orders;
Account() : Account(0, "<\?\?\?>", "<\?\?\?>", 0.0) {}
Account(int id, const char* name, const char* currency, double amount) : Wallet(currency, amount)
{
Id = id;
Name = name;
}
};
} // namespace TradeProto
The next step you should provide serialization methods for the domain model.
Cap'n'Proto serialization
Cap'n'Proto serialization is based on Cap'n'Proto library.
Cap'n'Proto schema
Cap'n'Proto serialization starts with describing a model schema. For our domain model the schema will be the following:
# Unique file ID, generated by 'capnp id'
@0xd4b6e00623bed170;
using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("Trade::capnproto");
enum OrderSide
{
buy @0;
sell @1;
}
enum OrderType
{
market @0;
limit @1;
stop @2;
}
struct Order
{
id @0 : Int32;
symbol @1 : Text;
side @2 : OrderSide;
type @3 : OrderType;
price @4 : Float64 = 0.0;
volume @5 : Float64 = 0.0;
}
struct Balance
{
currency @0 : Text;
amount @1 : Float64 = 0.0;
}
struct Account
{
id @0 : Int32;
name @1 : Text;
wallet @2 : Balance;
orders @3 : List(Order);
}
Cap'n'Proto schema compilation
The next step is a schema compilation using 'capnpc' utility which will create a generated code for required programming language.
The following command will create a C++ generated code:
capnp compile -I capnproto/c++/src -oc++ trade.capnp
It is possible to use capnp_generate_cpp() in CMakeLists.txt to generate code using 'cmake' utility:
capnp_generate_cpp(CAPNP_HEADERS CAPNP_SOURCES trade.capnp)
As the result 'trade.capnp.h' and 'trade.capnp.c++' files will be generated.
Cap'n'Proto serialization methods
Finally you should extend your domain model with a Cap'n'Proto serialization methods:
#include "capnp/serialize.h"
#include "capnproto/trade.capnp.h"
#include <algorithm>
namespace TradeProto {
struct Order
{
...
// Cap'n'Proto serialization
void Serialize(Trade::capnproto::Order::Builder& builder)
{
builder.setId(Id);
builder.setSymbol(Symbol);
builder.setSide((Trade::capnproto::OrderSide)Side);
builder.setType((Trade::capnproto::OrderType)Type);
builder.setPrice(Price);
builder.setVolume(Volume);
}
void Deserialize(const Trade::capnproto::Order::Reader& reader)
{
Id = reader.getId();
std::string symbol = reader.getSymbol();
std::memcpy(Symbol, symbol.c_str(), std::min(symbol.size() + 1, sizeof(Symbol)));
Side = (OrderSide)reader.getSide();
Type = (OrderType)reader.getType();
Price = reader.getPrice();
Volume = reader.getVolume();
}
...
};
struct Balance
{
...
// Cap'n'Proto serialization
void Serialize(Trade::capnproto::Balance::Builder& builder)
{
builder.setCurrency(Currency);
builder.setAmount(Amount);
}
void Deserialize(const Trade::capnproto::Balance::Reader& reader)
{
std::string currency = reader.getCurrency();
std::memcpy(Currency, currency.c_str(), std::min(currency.size() + 1, sizeof(Currency)));
Amount = reader.getAmount();
}
...
};
struct Account
{
...
// Cap'n'Proto serialization
void Serialize(Trade::capnproto::Account::Builder& builder)
{
builder.setId(Id);
builder.setName(Name);
auto wallet = builder.initWallet();
Wallet.Serialize(wallet);
auto orders = builder.initOrders((unsigned)Orders.size());
unsigned index = 0;
for (auto& order : Orders)
{
auto o = orders[index++];
order.Serialize(o);
}
}
void Deserialize(const Trade::capnproto::Account::Reader& reader)
{
Id = reader.getId();
Name = reader.getName().cStr();
Wallet.Deserialize(reader.getWallet());
Orders.clear();
for (auto o : reader.getOrders())
{
Order order;
order.Deserialize(o);
Orders.emplace_back(order);
}
}
...
};
} // namespace TradeProto
Cap'n'Proto example
Here comes the usage example of Cap'n'Proto serialize/deserialize functionality:
#include "../proto/trade.h"
#include <iostream>
int main(int argc, char** argv)
{
// Create a new account with some orders
TradeProto::Account account(1, "Test", "USD", 1000);
account.Orders.emplace_back(TradeProto::Order(1, "EURUSD", TradeProto::OrderSide::BUY, TradeProto::OrderType::MARKET, 1.23456, 1000));
account.Orders.emplace_back(TradeProto::Order(2, "EURUSD", TradeProto::OrderSide::SELL, TradeProto::OrderType::LIMIT, 1.0, 100));
account.Orders.emplace_back(TradeProto::Order(3, "EURUSD", TradeProto::OrderSide::BUY, TradeProto::OrderType::STOP, 1.5, 10));
// Serialize the account to the Cap'n'Proto buffer
capnp::MallocMessageBuilder output;
Trade::capnproto::Account::Builder builder = output.initRoot<Trade::capnproto::Account>();
account.Serialize(builder);
kj::VectorOutputStream buffer;
writeMessage(buffer, output);
// Show original and Cap'n'Proto serialized sizes
std::cout << "Original size: " << account.size() << std::endl;
std::cout << "Cap'n'Proto size: " << buffer.getArray().size() << std::endl;
// Deserialize the account from the Cap'n'Proto buffer
kj::ArrayInputStream array(buffer.getArray());
capnp::InputStreamMessageReader input(array);
TradeProto::Account deserialized;
deserialized.Deserialize(input.getRoot<Trade::capnproto::Account>());
// Show account content
std::cout << std::endl;
std::cout << "Account.Id = " << deserialized.Id << std::endl;
std::cout << "Account.Name = " << deserialized.Name << std::endl;
std::cout << "Account.Wallet.Currency = " << deserialized.Wallet.Currency << std::endl;
std::cout << "Account.Wallet.Amount = " << deserialized.Wallet.Amount << std::endl;
for (auto& order : deserialized.Orders)
{
std::cout << "Account.Order => Id: " << order.Id
<< ", Symbol: " << order.Symbol
<< ", Side: " << (int)order.Side
<< ", Type: " << (int)order.Type
<< ", Price: " << order.Price
<< ", Volume: " << order.Volume
<< std::endl;
}
return 0;
}
Output of the example is the following:
Original size: 128
Cap'n'Proto size: 208
Account.Id = 1
Account.Name = Test
Account.Wallet.Currency = USD
Account.Wallet.Amount = 1000
Account.Order => Id: 1, Symbol: EURUSD, Side: 0, Type: 0, Price: 1.23456, Volume: 1000
Account.Order => Id: 2, Symbol: EURUSD, Side: 1, Type: 1, Price: 1, Volume: 100
Account.Order => Id: 3, Symbol: EURUSD, Side: 0, Type: 2, Price: 1.5, Volume: 10
Cap'n'Proto performance
Cap'n'Proto serialization performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.136 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:05:35 2025
UTC timestamp: Wed Jul 16 17:05:35 2025
===============================================================================
Benchmark: Cap'n'Proto-Serialize
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: Cap'n'Proto-Serialize
Average time: 247 ns/op
Minimal time: 244 ns/op
Maximal time: 254 ns/op
Total time: 4.762 s
Total operations: 19242126
Total bytes: 4.484 GiB
Operations throughput: 4040127 ops/s
Bytes throughput: 961.717 MiB/s
Custom values:
MessageSize: 208
OriginalSize: 128
===============================================================================
Cap'n'Proto deserialization performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.116 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:06:37 2025
UTC timestamp: Wed Jul 16 17:06:37 2025
===============================================================================
Benchmark: Cap'n'Proto-Deserialize
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: Cap'n'Proto-Deserialize
Average time: 184 ns/op
Minimal time: 183 ns/op
Maximal time: 184 ns/op
Total time: 4.959 s
Total operations: 26913647
Total bytes: 6.262 GiB
Operations throughput: 5426756 ops/s
Bytes throughput: 1.267 GiB/s
Custom values:
MessageSize: 208
OriginalSize: 128
===============================================================================
FastBinaryEncoding serialization
FastBinaryEncoding serialization is based on FastBinaryEncoding library.
FastBinaryEncoding schema
FastBinaryEncoding serialization starts with describing a model schema. For our domain model the schema will be the following:
package trade
enum OrderSide : byte
{
buy;
sell;
}
enum OrderType : byte
{
market;
limit;
stop;
}
struct Order
{
[key] int32 id;
string symbol;
OrderSide side;
OrderType type;
double price = 0.0;
double volume = 0.0;
}
struct Balance
{
[key] string currency;
double amount = 0.0;
}
struct Account
{
[key] int32 id;
string name;
Balance wallet;
Order[] orders;
}
FastBinaryEncoding schema compilation
The next step is a schema compilation using 'fbec' utility which will create a generated code for required programming language.
The following command will create a C++ generated code:
fbec --cpp --input=trade.fbe --output=.
It is possible to use add_custom_command() in CMakeLists.txt to generate code using 'cmake' utility:
add_custom_command(TARGET example POST_BUILD COMMAND fbec --cpp --input=trade.fbe --output=.)
As the result 'fbe.h' and 'trade.h' files will be generated.
FastBinaryEncoding serialization methods
Finally you should extend your domain model with a FastBinaryEncoding serialization methods:
#include "fbe/trade_models.h"
#include <algorithm>
namespace TradeProto {
struct Order
{
...
// FastBinaryEncoding serialization
template <class TBuffer>
void Serialize(FBE::FieldModel<TBuffer, trade::Order>& model)
{
size_t model_begin = model.set_begin();
model.id.set(Id);
model.symbol.set(Symbol);
model.side.set((trade::OrderSide)Side);
model.type.set((trade::OrderType)Type);
model.price.set(Price);
model.volume.set(Volume);
model.set_end(model_begin);
}
template <class TBuffer>
void Deserialize(const FBE::FieldModel<TBuffer, trade::Order>& model)
{
size_t model_begin = model.get_begin();
model.id.get(Id);
model.symbol.get(Symbol);
trade::OrderSide side;
model.side.get(side);
Side = (OrderSide)side;
trade::OrderType type;
model.type.get(type);
Type = (OrderType)type;
model.price.get(Price);
model.volume.get(Volume);
model.get_end(model_begin);
}
...
};
struct Balance
{
...
// FastBinaryEncoding serialization
template <class TBuffer>
void Serialize(FBE::FieldModel<TBuffer, trade::Balance>& model)
{
size_t model_begin = model.set_begin();
model.currency.set(Currency);
model.amount.set(Amount);
model.set_end(model_begin);
}
template <class TBuffer>
void Deserialize(const FBE::FieldModel<TBuffer, trade::Balance>& model)
{
size_t model_begin = model.get_begin();
model.currency.get(Currency);
model.amount.get(Amount);
model.get_end(model_begin);
}
...
};
struct Account
{
...
// FastBinaryEncoding serialization
template <class TBuffer>
void Serialize(FBE::FieldModel<TBuffer, trade::Account>& model)
{
size_t model_begin = model.set_begin();
model.id.set(Id);
model.name.set(Name);
Wallet.Serialize(model.wallet);
auto order_model = model.orders.resize(Orders.size());
for (auto& order : Orders)
{
order.Serialize(order_model);
order_model.fbe_shift(order_model.fbe_size());
}
model.set_end(model_begin);
}
template <class TBuffer>
void Deserialize(const FBE::FieldModel<TBuffer, trade::Account>& model)
{
size_t model_begin = model.get_begin();
model.id.get(Id);
model.name.get(Name);
Wallet.Deserialize(model.wallet);
Orders.clear();
for (size_t i = 0; i < model.orders.size(); ++i)
{
Order order;
order.Deserialize(model.orders[i]);
Orders.emplace_back(order);
}
model.get_end(model_begin);
}
...
};
} // namespace TradeProto
FastBinaryEncoding example
Here comes the usage example of FastBinaryEncoding serialize/deserialize functionality:
#include "../proto/trade.h"
#include <iostream>
int main(int argc, char** argv)
{
// Create a new account with some orders
TradeProto::Account account(1, "Test", "USD", 1000);
account.Orders.emplace_back(TradeProto::Order(1, "EURUSD", TradeProto::OrderSide::BUY, TradeProto::OrderType::MARKET, 1.23456, 1000));
account.Orders.emplace_back(TradeProto::Order(2, "EURUSD", TradeProto::OrderSide::SELL, TradeProto::OrderType::LIMIT, 1.0, 100));
account.Orders.emplace_back(TradeProto::Order(3, "EURUSD", TradeProto::OrderSide::BUY, TradeProto::OrderType::STOP, 1.5, 10));
// Serialize the account to the FBE buffer
FBE::trade::AccountModel<FBE::WriteBuffer> writer;
size_t model_begin = writer.create_begin();
account.Serialize(writer.model);
size_t serialized = writer.create_end(model_begin);
assert(writer.verify() && "Model is broken!");
// Show original and FBE serialized sizes
std::cout << "Original size: " << account.size() << std::endl;
std::cout << "FBE size: " << serialized << std::endl;
// Deserialize the account from the FBE buffer
TradeProto::Account deserialized;
FBE::trade::AccountModel<FBE::ReadBuffer> reader;
reader.attach(writer.buffer());
assert(reader.verify() && "Model is broken!");
deserialized.Deserialize(reader.model);
// Show account content
std::cout << std::endl;
std::cout << "Account.Id = " << deserialized.Id << std::endl;
std::cout << "Account.Name = " << deserialized.Name << std::endl;
std::cout << "Account.Wallet.Currency = " << deserialized.Wallet.Currency << std::endl;
std::cout << "Account.Wallet.Amount = " << deserialized.Wallet.Amount << std::endl;
for (auto& order : deserialized.Orders)
{
std::cout << "Account.Order => Id: " << order.Id
<< ", Symbol: " << order.Symbol
<< ", Side: " << (int)order.Side
<< ", Type: " << (int)order.Type
<< ", Price: " << order.Price
<< ", Volume: " << order.Volume
<< std::endl;
}
return 0;
}
Output of the example is the following:
Original size: 128
FBE size: 234
Account.Id = 1
Account.Name = Test
Account.Wallet.Currency = USD
Account.Wallet.Amount = 1000
Account.Order => Id: 1, Symbol: EURUSD, Side: 0, Type: 0, Price: 1.23456, Volume: 1000
Account.Order => Id: 2, Symbol: EURUSD, Side: 1, Type: 1, Price: 1, Volume: 100
Account.Order => Id: 3, Symbol: EURUSD, Side: 0, Type: 2, Price: 1.5, Volume: 10
FastBinaryEncoding performance
FastBinaryEncoding serialization performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.179 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:08:14 2025
UTC timestamp: Wed Jul 16 17:08:14 2025
===============================================================================
Benchmark: FastBinaryEncoding-Serialize
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: FastBinaryEncoding-Serialize
Average time: 77 ns/op
Minimal time: 77 ns/op
Maximal time: 81 ns/op
Total time: 5.204 s
Total operations: 66877702
Total bytes: 17.501 GiB
Operations throughput: 12849579 ops/s
Bytes throughput: 3.369 GiB/s
Custom values:
MessageSize: 234
OriginalSize: 128
===============================================================================
FastBinaryEncoding deserialization performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.194 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:10:03 2025
UTC timestamp: Wed Jul 16 17:10:03 2025
===============================================================================
Benchmark: FastBinaryEncoding-Deserialize
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: FastBinaryEncoding-Deserialize
Average time: 84 ns/op
Minimal time: 82 ns/op
Maximal time: 85 ns/op
Total time: 5.271 s
Total operations: 62074518
Total bytes: 16.239 GiB
Operations throughput: 11775531 ops/s
Bytes throughput: 3.081 GiB/s
Custom values:
MessageSize: 234
OriginalSize: 128
===============================================================================
FlatBuffers serialization
FlatBuffers serialization is based on FlatBuffers library.
FlatBuffers schema
FlatBuffers serialization starts with describing a model schema. For our domain model the schema will be the following:
namespace Trade.flatbuf;
enum OrderSide : byte
{
buy,
sell
}
enum OrderType : byte
{
market,
limit,
stop
}
table Order
{
id : int;
symbol : string;
side : OrderSide;
type : OrderType;
price : double = 0.0;
volume : double = 0.0;
}
table Balance
{
currency : string;
amount : double = 0.0;
}
table Account
{
id : int;
name : string;
wallet : Balance;
orders : [Order];
}
root_type Account;
FlatBuffers schema compilation
The next step is a schema compilation using 'flatc' utility which will create a generated code for required programming language.
The following command will create a C++ generated code:
flatc --cpp --scoped-enums -o . trade.fbs
It is possible to use add_custom_command() in CMakeLists.txt to generate code using 'cmake' utility:
add_custom_command(TARGET example POST_BUILD COMMAND flatc --cpp --scoped-enums -o . trade.fbs)
As the result 'domain_generated.h' file will be generated.
FlatBuffers serialization methods
Finally you should extend your domain model with a FlatBuffers serialization methods:
#include "flatbuffers/trade_generated.h"
#include <algorithm>
namespace TradeProto {
struct Order
{
...
// FlatBuffers serialization
flatbuffers::Offset<Trade::flatbuf::Order> Serialize(flatbuffers::FlatBufferBuilder& builder)
{
return Trade::flatbuf::CreateOrderDirect(builder, Id, Symbol, (Trade::flatbuf::OrderSide)Side, (Trade::flatbuf::OrderType)Type, Price, Volume);
}
void Deserialize(const Trade::flatbuf::Order& value)
{
Id = value.id();
std::string symbol = value.symbol()->str();
std::memcpy(Symbol, symbol.c_str(), std::min(symbol.size() + 1, sizeof(Symbol)));
Side = (OrderSide)value.side();
Type = (OrderType)value.type();
Price = value.price();
Volume = value.volume();
}
...
};
struct Balance
{
...
// FlatBuffers serialization
flatbuffers::Offset<Trade::flatbuf::Balance> Serialize(flatbuffers::FlatBufferBuilder& builder)
{
return Trade::flatbuf::CreateBalanceDirect(builder, Currency, Amount);
}
void Deserialize(const Trade::flatbuf::Balance& value)
{
std::string currency = value.currency()->str();
std::memcpy(Currency, currency.c_str(), std::min(currency.size() + 1, sizeof(Currency)));
Amount = value.amount();
}
...
};
struct Account
{
...
// FlatBuffers serialization
flatbuffers::Offset<Trade::flatbuf::Account> Serialize(flatbuffers::FlatBufferBuilder& builder)
{
auto wallet = Wallet.Serialize(builder);
std::vector<flatbuffers::Offset<Trade::flatbuf::Order>> orders;
for (auto& order : Orders)
orders.emplace_back(order.Serialize(builder));
return Trade::flatbuf::CreateAccountDirect(builder, Id, Name.c_str(), wallet, &orders);
}
void Deserialize(const Trade::flatbuf::Account& value)
{
Id = value.id();
Name = value.name()->str();
Wallet.Deserialize(*value.wallet());
Orders.clear();
for (auto o : *value.orders())
{
Order order;
order.Deserialize(*o);
Orders.emplace_back(order);
}
}
...
};
} // namespace TradeProto
FlatBuffers example
Here comes the usage example of FlatBuffers serialize/deserialize functionality:
#include "../proto/trade.h"
#include <iostream>
int main(int argc, char** argv)
{
// Create a new account with some orders
TradeProto::Account account(1, "Test", "USD", 1000);
account.Orders.emplace_back(TradeProto::Order(1, "EURUSD", TradeProto::OrderSide::BUY, TradeProto::OrderType::MARKET, 1.23456, 1000));
account.Orders.emplace_back(TradeProto::Order(2, "EURUSD", TradeProto::OrderSide::SELL, TradeProto::OrderType::LIMIT, 1.0, 100));
account.Orders.emplace_back(TradeProto::Order(3, "EURUSD", TradeProto::OrderSide::BUY, TradeProto::OrderType::STOP, 1.5, 10));
// Serialize the account to the FlatBuffer buffer
flatbuffers::FlatBufferBuilder builder;
builder.Finish(account.Serialize(builder));
// Show original and FlatBuffer serialized sizes
std::cout << "Original size: " << account.size() << std::endl;
std::cout << "FlatBuffer size: " << builder.GetSize() << std::endl;
// Deserialize the account from the FlatBuffer buffer
TradeProto::Account deserialized;
deserialized.Deserialize(*Trade::flatbuf::GetAccount(builder.GetBufferPointer()));
// Show account content
std::cout << std::endl;
std::cout << "Account.Id = " << deserialized.Id << std::endl;
std::cout << "Account.Name = " << deserialized.Name << std::endl;
std::cout << "Account.Wallet.Currency = " << deserialized.Wallet.Currency << std::endl;
std::cout << "Account.Wallet.Amount = " << deserialized.Wallet.Amount << std::endl;
for (auto& order : deserialized.Orders)
{
std::cout << "Account.Order => Id: " << order.Id
<< ", Symbol: " << order.Symbol
<< ", Side: " << (int)order.Side
<< ", Type: " << (int)order.Type
<< ", Price: " << order.Price
<< ", Volume: " << order.Volume
<< std::endl;
}
return 0;
}
Output of the example is the following:
Original size: 128
FlatBuffer size: 280
Account.Id = 1
Account.Name = Test
Account.Wallet.Currency = USD
Account.Wallet.Amount = 1000
Account.Order => Id: 1, Symbol: EURUSD, Side: 0, Type: 0, Price: 1.23456, Volume: 1000
Account.Order => Id: 2, Symbol: EURUSD, Side: 1, Type: 1, Price: 1, Volume: 100
Account.Order => Id: 3, Symbol: EURUSD, Side: 0, Type: 2, Price: 1.5, Volume: 10
FlatBuffers performance
FlatBuffers serialization performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.270 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:11:16 2025
UTC timestamp: Wed Jul 16 17:11:16 2025
===============================================================================
Benchmark: FlatBuffers-Serialize
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: FlatBuffers-Serialize
Average time: 272 ns/op
Minimal time: 272 ns/op
Maximal time: 273 ns/op
Total time: 4.947 s
Total operations: 18131562
Total bytes: 5.689 GiB
Operations throughput: 3664742 ops/s
Bytes throughput: 1.150 GiB/s
Custom values:
MessageSize: 280
OriginalSize: 128
===============================================================================
FlatBuffers deserialization performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.323 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:13:23 2025
UTC timestamp: Wed Jul 16 17:13:23 2025
===============================================================================
Benchmark: FlatBuffers-Deserialize
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: FlatBuffers-Deserialize
Average time: 81 ns/op
Minimal time: 77 ns/op
Maximal time: 81 ns/op
Total time: 5.320 s
Total operations: 65603873
Total bytes: 20.541 GiB
Operations throughput: 12329743 ops/s
Bytes throughput: 3.878 GiB/s
Custom values:
MessageSize: 280
OriginalSize: 128
===============================================================================
Protobuf serialization
Protobuf serialization is based on Protobuf library.
Protobuf schema
Protobuf serialization starts with describing a model schema. For our domain model the schema will be the following:
syntax = "proto3";
package Trade.protobuf;
enum OrderSide
{
buy = 0;
sell = 1;
}
enum OrderType
{
market = 0;
limit = 1;
stop = 2;
}
message Order
{
int32 id = 1;
string symbol = 2;
OrderSide side = 3;
OrderType type = 4;
double price = 5;
double volume = 6;
}
message Balance
{
string currency = 1;
double amount = 2;
}
message Account
{
int32 id = 1;
string name = 2;
Balance wallet = 3;
repeated Order orders = 4;
}
Protobuf schema compilation
The next step is a schema compilation using 'protoc' utility which will create a generated code for required programming language.
The following command will create a C++ generated code:
protoc --proto_path=. --cpp_out=. trade.proto
It is possible to use add_custom_command() in CMakeLists.txt to generate code using 'cmake' utility:
add_custom_command(TARGET example POST_BUILD COMMAND protoc --proto_path=. --cpp_out=. trade.proto)
As the result 'trade.pb.h' and 'trade.pb.cc' files will be generated.
Protobuf serialization methods
Finally you should extend your domain model with a FlatBuffers serialization methods:
#include "protobuf/trade.pb.h"
#include <algorithm>
namespace TradeProto {
struct Order
{
...
// Protobuf serialization
Trade::protobuf::Order& Serialize(Trade::protobuf::Order& value)
{
value.set_id(Id);
value.set_symbol(Symbol);
value.set_side((Trade::protobuf::OrderSide)Side);
value.set_type((Trade::protobuf::OrderType)Type);
value.set_price(Price);
value.set_volume(Volume);
return value;
}
void Deserialize(const Trade::protobuf::Order& value)
{
Id = value.id();
std::string symbol = value.symbol();
std::memcpy(Symbol, symbol.c_str(), std::min(symbol.size() + 1, sizeof(Symbol)));
Side = (OrderSide)value.side();
Type = (OrderType)value.type();
Price = value.price();
Volume = value.volume();
}
...
};
struct Balance
{
...
// Protobuf serialization
Trade::protobuf::Balance& Serialize(Trade::protobuf::Balance& value)
{
value.set_currency(Currency);
value.set_amount(Amount);
return value;
}
void Deserialize(const Trade::protobuf::Balance& value)
{
std::string currency = value.currency();
std::memcpy(Currency, currency.c_str(), std::min(currency.size() + 1, sizeof(Currency)));
Amount = value.amount();
}
...
};
struct Account
{
...
// Protobuf serialization
Trade::protobuf::Account& Serialize(Trade::protobuf::Account& value)
{
value.set_id(id);
value.set_name(Name);
value.set_allocated_wallet(&Wallet.Serialize(*value.wallet().New(value.GetArena())));
for (auto& order : Orders)
order.Serialize(*value.add_orders());
return value;
}
void Deserialize(const Trade::protobuf::Account& value)
{
Id = value.id();
Name = value.name();
Wallet.Deserialize(value.wallet());
Orders.clear();
for (int i = 0; i < value.orders_size(); ++i)
{
Order order;
order.Deserialize(value.orders(i));
Orders.emplace_back(order);
}
}
...
};
} // namespace TradeProto
Protobuf example
Here comes the usage example of Protobuf serialize/deserialize functionality:
#include "../proto/trade.h"
#include <iostream>
int main(int argc, char** argv)
{
// Create a new account with some orders
TradeProto::Account account(1, "Test", "USD", 1000);
account.Orders.emplace_back(TradeProto::Order(1, "EURUSD", TradeProto::OrderSide::BUY, TradeProto::OrderType::MARKET, 1.23456, 1000));
account.Orders.emplace_back(TradeProto::Order(2, "EURUSD", TradeProto::OrderSide::SELL, TradeProto::OrderType::LIMIT, 1.0, 100));
account.Orders.emplace_back(TradeProto::Order(3, "EURUSD", TradeProto::OrderSide::BUY, TradeProto::OrderType::STOP, 1.5, 10));
// Serialize the account to the Protobuf buffer
Trade::protobuf::Account output;
account.Serialize(output);
auto buffer = output.SerializeAsString();
// Show original and Protobuf serialized sizes
std::cout << "Original size: " << account.size() << std::endl;
std::cout << "Protobuf size: " << buffer.size() << std::endl;
// Deserialize the account from the Protobuf buffer
Trade::protobuf::Account input;
input.ParseFromString(buffer);
TradeProto::Account deserialized;
deserialized.Deserialize(input);
// Show account content
std::cout << std::endl;
std::cout << "Account.Id = " << deserialized.Id << std::endl;
std::cout << "Account.Name = " << deserialized.Name << std::endl;
std::cout << "Account.Wallet.Currency = " << deserialized.Wallet.Currency << std::endl;
std::cout << "Account.Wallet.Amount = " << deserialized.Wallet.Amount << std::endl;
for (auto& order : deserialized.Orders)
{
std::cout << "Account.Order => Id: " << order.Id
<< ", Symbol: " << order.Symbol
<< ", Side: " << (int)order.Side
<< ", Type: " << (int)order.Type
<< ", Price: " << order.Price
<< ", Volume: " << order.Volume
<< std::endl;
}
// Delete all global objects allocated by Protobuf
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
Output of the example is the following:
Original size: 128
Protobuf size: 120
Account.Id = 1
Account.Name = Test
Account.Wallet.Currency = USD
Account.Wallet.Amount = 1000
Account.Order => Id: 1, Symbol: EURUSD, Side: 0, Type: 0, Price: 1.23456, Volume: 1000
Account.Order => Id: 2, Symbol: EURUSD, Side: 1, Type: 1, Price: 1, Volume: 100
Account.Order => Id: 3, Symbol: EURUSD, Side: 0, Type: 2, Price: 1.5, Volume: 10
Protobuf performance
Protobuf serialization performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.339 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:15:26 2025
UTC timestamp: Wed Jul 16 17:15:26 2025
===============================================================================
Benchmark: Protobuf-Serialize
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: Protobuf-Serialize
Average time: 322 ns/op
Minimal time: 322 ns/op
Maximal time: 323 ns/op
Total time: 4.989 s
Total operations: 15458464
Total bytes: 2.074 GiB
Operations throughput: 3098338 ops/s
Bytes throughput: 425.503 MiB/s
Custom values:
MessageSize: 120
OriginalSize: 128
===============================================================================
Protobuf deserialization performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.248 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:16:37 2025
UTC timestamp: Wed Jul 16 17:16:37 2025
===============================================================================
Benchmark: Protobuf-Deserialize
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: Protobuf-Deserialize
Average time: 351 ns/op
Minimal time: 351 ns/op
Maximal time: 352 ns/op
Total time: 4.959 s
Total operations: 14111610
Total bytes: 1.913 GiB
Operations throughput: 2845506 ops/s
Bytes throughput: 390.789 MiB/s
Custom values:
MessageSize: 120
OriginalSize: 128
===============================================================================
SimpleBinaryEncoding serialization
SimpleBinaryEncoding serialization is based on SimpleBinaryEncoding library.
SimpleBinaryEncoding schema
SimpleBinaryEncoding serialization starts with describing a model schema. For our domain model the schema will be the following:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe" package="sbe" id="1" version="1" semanticVersion="5.2" description="Trade schema" byteOrder="littleEndian">
<types>
<composite name="messageHeader" description="Message identifiers and length of message root">
<type name="blockLength" primitiveType="uint16"/>
<type name="templateId" primitiveType="uint16"/>
<type name="schemaId" primitiveType="uint16"/>
<type name="version" primitiveType="uint16"/>
</composite>
<composite name="groupSizeEncoding" description="Repeating group dimensions">
<type name="blockLength" primitiveType="uint16"/>
<type name="numInGroup" primitiveType="uint16"/>
</composite>
<composite name="varStringEncoding">
<type name="length" primitiveType="uint32" maxValue="1073741824"/>
<type name="varData" primitiveType="uint8" length="0" characterEncoding="UTF-8"/>
</composite>
</types>
<types>
<enum name="OrderSide" encodingType="uint8">
<validValue name="buy">0</validValue>
<validValue name="sell">1</validValue>
</enum>
<enum name="OrderType" encodingType="uint8">
<validValue name="market">0</validValue>
<validValue name="limit">1</validValue>
<validValue name="stop">2</validValue>
</enum>
<composite name="Order">
<type name="id" primitiveType="int32"/>
<type name="symbol" primitiveType="char" length="10" characterEncoding="UTF-8"/>
<ref name="side" type="OrderSide"/>
<ref name="type" type="OrderType"/>
<type name="price" primitiveType="double"/>
<type name="volume" primitiveType="double"/>
</composite>
<composite name="Balance">
<type name="currency" primitiveType="char" length="10" characterEncoding="UTF-8"/>
<type name="amount" primitiveType="double"/>
</composite>
<type name="AccountId" primitiveType="int32"/>
</types>
<sbe:message name="Account" id="1">
<field name="id" id="1" type="AccountId"/>
<field name="wallet" id="2" type="Balance"/>
<group name="orders" id="3" dimensionType="groupSizeEncoding">
<field name="order" id="4" type="Order"/>
</group>
<data name="name" id="5" type="varStringEncoding"/>
</sbe:message>
</sbe:messageSchema>
SimpleBinaryEncoding schema compilation
The next step is a schema compilation using 'sbe' utility which will create a generated code for required programming language.
The following command will create a C++ generated code:
java -Dsbe.target.language=cpp -jar sbe-all-1.29.0.jar trade.sbe.xml
As the result required C++ header files will be generated.
SimpleBinaryEncoding serialization methods
Finally you should extend your domain model with a SimpleBinaryEncoding serialization methods:
#include "fbe/trade_models.h"
#include <algorithm>
namespace TradeProto {
struct Order
{
...
// SimpleBinaryEncoding serialization
void Serialize(sbe::Order& model)
{
model.id(Id);
model.putSymbol(Symbol);
model.side((sbe::OrderSide::Value)Side);
model.type((sbe::OrderType::Value)Type);
model.price(Price);
model.volume(Volume);
}
void Deserialize(sbe::Order& model)
{
Id = model.id();
model.getSymbol(Symbol, sizeof(Symbol));
Side = (OrderSide)model.side();
Type = (OrderType)model.type();
Price = model.price();
Volume = model.volume();
}
...
};
struct Balance
{
...
// SimpleBinaryEncoding serialization
void Serialize(sbe::Balance& model)
{
model.putCurrency(Currency);
model.amount(Amount);
}
void Deserialize(sbe::Balance& model)
{
model.getCurrency(Currency, sizeof(Currency));
Amount = model.amount();
}
...
};
struct Account
{
...
// SimpleBinaryEncoding serialization
void Serialize(sbe::Account& model)
{
model.id(Id);
model.putName(Name);
Wallet.Serialize(model.wallet());
auto orders = model.ordersCount((uint16_t)Orders.size());
for (auto& order : Orders)
order.Serialize(orders.next().order());
}
void Deserialize(sbe::Account& model)
{
Id = model.id();
Name = model.getNameAsString();
Wallet.Deserialize(model.wallet());
Orders.clear();
auto orders = model.orders();
for (int i = 0; i < orders.count(); ++i)
{
Order order;
order.Deserialize(orders.next().order());
Orders.emplace_back(order);
}
}
...
};
} // namespace TradeProto
SimpleBinaryEncoding example
Here comes the usage example of SimpleBinaryEncoding serialize/deserialize functionality:
#include "../proto/trade.h"
#include <iostream>
int main(int argc, char** argv)
{
// Create a new account with some orders
TradeProto::Account account(1, "Test", "USD", 1000);
account.Orders.emplace_back(TradeProto::Order(1, "EURUSD", TradeProto::OrderSide::BUY, TradeProto::OrderType::MARKET, 1.23456, 1000));
account.Orders.emplace_back(TradeProto::Order(2, "EURUSD", TradeProto::OrderSide::SELL, TradeProto::OrderType::LIMIT, 1.0, 100));
account.Orders.emplace_back(TradeProto::Order(3, "EURUSD", TradeProto::OrderSide::BUY, TradeProto::OrderType::STOP, 1.5, 10));
// Serialize the account to the SBE buffer
char buffer[1024];
sbe::MessageHeader header;
header.wrap(buffer, 0, 1, sizeof(buffer))
.blockLength(sbe::Account::sbeBlockLength())
.templateId(sbe::Account::sbeTemplateId())
.schemaId(sbe::Account::sbeSchemaId())
.version(sbe::Account::sbeSchemaVersion());
sbe::Account message;
message.wrapForEncode(buffer, header.encodedLength(), sizeof(buffer));
account.Serialize(message);
// Show original and SBE serialized sizes
std::cout << "Original size: " << account.size() << std::endl;
std::cout << "SBE size: " << header.encodedLength() + message.encodedLength() << std::endl;
// Deserialize the account from the SBE buffer
header.wrap(buffer, 0, 1, sizeof(buffer));
int actingVersion = header.version();
int actingBlockLength = header.blockLength();
message.wrapForDecode(buffer, header.encodedLength(), actingBlockLength, actingVersion, sizeof(buffer));
TradeProto::Account deserialized;
deserialized.Deserialize(message);
// Show account content
std::cout << std::endl;
std::cout << "Account.Id = " << deserialized.Id << std::endl;
std::cout << "Account.Name = " << deserialized.Name << std::endl;
std::cout << "Account.Wallet.Currency = " << deserialized.Wallet.Currency << std::endl;
std::cout << "Account.Wallet.Amount = " << deserialized.Wallet.Amount << std::endl;
for (auto& order : deserialized.Orders)
{
std::cout << "Account.Order => Id: " << order.Id
<< ", Symbol: " << order.Symbol
<< ", Side: " << (int)order.Side
<< ", Type: " << (int)order.Type
<< ", Price: " << order.Price
<< ", Volume: " << order.Volume
<< std::endl;
}
return 0;
}
Output of the example is the following:
Original size: 128
SBE size: 138
Account.Id = 1
Account.Name = Test
Account.Wallet.Currency = USD
Account.Wallet.Amount = 1000
Account.Order => Id: 1, Symbol: EURUSD, Side: 0, Type: 0, Price: 1.23456, Volume: 1000
Account.Order => Id: 2, Symbol: EURUSD, Side: 1, Type: 1, Price: 1, Volume: 100
Account.Order => Id: 3, Symbol: EURUSD, Side: 0, Type: 2, Price: 1.5, Volume: 10
SimpleBinaryEncoding performance
SimpleBinaryEncoding serialization performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.311 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:17:49 2025
UTC timestamp: Wed Jul 16 17:17:49 2025
===============================================================================
Benchmark: SimpleBinaryEncoding-Serialize
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: SimpleBinaryEncoding-Serialize
Average time: 35 ns/op
Minimal time: 35 ns/op
Maximal time: 35 ns/op
Total time: 5.926 s
Total operations: 168709597
Total bytes: 26.020 GiB
Operations throughput: 28467195 ops/s
Bytes throughput: 4.399 GiB/s
Custom values:
MessageSize: 138
OriginalSize: 128
===============================================================================
SimpleBinaryEncoding deserialization performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.261 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:19:22 2025
UTC timestamp: Wed Jul 16 17:19:22 2025
===============================================================================
Benchmark: SimpleBinaryEncoding-Deserialize
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: SimpleBinaryEncoding-Deserialize
Average time: 52 ns/op
Minimal time: 52 ns/op
Maximal time: 52 ns/op
Total time: 5.432 s
Total operations: 104359134
Total bytes: 16.097 GiB
Operations throughput: 19211002 ops/s
Bytes throughput: 2.985 GiB/s
Custom values:
MessageSize: 138
OriginalSize: 128
===============================================================================
zpp::bits serialization
zpp::bits serialization is based on zpp::bits library.
zpp::bits serialization methods
Finally you should extend your domain model with a zpp::bits serialization methods:
#include "fbe/trade_models.h"
#include <algorithm>
namespace TradeProto {
struct Order
{
...
// zpp::bits serialization
using serialize = zpp::bits::members<6>;
...
};
struct Balance
{
...
// zpp::bits serialization
using serialize = zpp::bits::members<2>;
...
};
struct Account
{
...
// zpp::bits serialization
using serialize = zpp::bits::members<4>;
...
};
} // namespace TradeProto
zpp::bits example
Here comes the usage example of zpp::bits serialize/deserialize functionality:
#include "../proto/trade.h"
#include <iostream>
int main(int argc, char** argv)
{
// Create a new account with some orders
TradeProto::Account account(1, "Test", "USD", 1000);
account.Orders.emplace_back(TradeProto::Order(1, "EURUSD", TradeProto::OrderSide::BUY, TradeProto::OrderType::MARKET, 1.23456, 1000));
account.Orders.emplace_back(TradeProto::Order(2, "EURUSD", TradeProto::OrderSide::SELL, TradeProto::OrderType::LIMIT, 1.0, 100));
account.Orders.emplace_back(TradeProto::Order(3, "EURUSD", TradeProto::OrderSide::BUY, TradeProto::OrderType::STOP, 1.5, 10));
// Serialize the account to the zpp::bits buffer
auto [buffer, out] = zpp::bits::data_out();
(void) out(account);
// Show original and zpp::bits serialized sizes
std::cout << "Original size: " << account.size() << std::endl;
std::cout << "zpp::bits size: " << buffer.size() << std::endl;
// Deserialize the account from the zpp::bits buffer
TradeProto::Account deserialized;
(void) zpp::bits::in{buffer}(deserialized);
// Show account content
std::cout << std::endl;
std::cout << "Account.Id = " << deserialized.Id << std::endl;
std::cout << "Account.Name = " << deserialized.Name << std::endl;
std::cout << "Account.Wallet.Currency = " << deserialized.Wallet.Currency << std::endl;
std::cout << "Account.Wallet.Amount = " << deserialized.Wallet.Amount << std::endl;
for (const auto& order : deserialized.Orders)
{
std::cout << "Account.Order => Id: " << order.Id
<< ", Symbol: " << order.Symbol
<< ", Side: " << (int)order.Side
<< ", Type: " << (int)order.Type
<< ", Price: " << order.Price
<< ", Volume: " << order.Volume
<< std::endl;
}
return 0;
}
Output of the example is the following:
Original size: 128
zpp::bits size: 130
Account.Id = 1
Account.Name = Test
Account.Wallet.Currency = USD
Account.Wallet.Amount = 1000
Account.Order => Id: 1, Symbol: EURUSD, Side: 0, Type: 0, Price: 1.23456, Volume: 1000
Account.Order => Id: 2, Symbol: EURUSD, Side: 1, Type: 1, Price: 1, Volume: 100
Account.Order => Id: 3, Symbol: EURUSD, Side: 0, Type: 2, Price: 1.5, Volume: 10
zpp::bits performance
zpp::bits serialization performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.142 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:38:54 2025
UTC timestamp: Wed Jul 16 17:38:54 2025
===============================================================================
Benchmark: zpp::bits-Serialize
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: zpp::bits-Serialize
Average time: 34 ns/op
Minimal time: 33 ns/op
Maximal time: 35 ns/op
Total time: 5.790 s
Total operations: 168487238
Total bytes: 24.490 GiB
Operations throughput: 29095978 ops/s
Bytes throughput: 4.232 GiB/s
Custom values:
MessageSize: 130
OriginalSize: 128
===============================================================================
zpp::bits deserialization performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.159 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:39:52 2025
UTC timestamp: Wed Jul 16 17:39:52 2025
===============================================================================
Benchmark: zpp::bits-Deserialize
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: zpp::bits-Deserialize
Average time: 37 ns/op
Minimal time: 36 ns/op
Maximal time: 37 ns/op
Total time: 5.652 s
Total operations: 152512801
Total bytes: 22.161 GiB
Operations throughput: 26982853 ops/s
Bytes throughput: 3.942 GiB/s
Custom values:
MessageSize: 130
OriginalSize: 128
===============================================================================
JSON serialization
JSON serialization is based on RapidJSON library.
JSON serialization methods
Finally you should extend your domain model with a JSON serialization methods:
#include "serialization/json/serializer.h"
#include "serialization/json/deserializer.h"
namespace TradeProto {
struct Order
{
...
// JSON serialization
template<typename OutputStream>
void Serialize(CppSerialization::JSON::Serializer<OutputStream>& serializer)
{
serializer.StartObject();
serializer.Pair("id", Id);
serializer.Pair("symbol", Symbol);
serializer.Pair("side", (int)Side);
serializer.Pair("type", (int)Type);
serializer.Pair("price", Price);
serializer.Pair("volume", Volume);
serializer.EndObject();
}
template<typename JSON>
void Deserialize(const JSON& json)
{
using namespace CppSerialization::JSON;
Deserializer::Find(json, "id", Id);
Deserializer::Find(json, "symbol", Symbol);
int side = 0; Deserializer::Find(json, "side", side); Side = (OrderSide)side;
int type = 0; Deserializer::Find(json, "type", type); Type = (OrderType)type;
Deserializer::Find(json, "price", Price);
Deserializer::Find(json, "volume", Volume);
}
...
};
struct Balance
{
...
// JSON serialization
template<typename OutputStream>
void Serialize(CppSerialization::JSON::Serializer<OutputStream>& serializer)
{
serializer.StartObject();
serializer.Pair("currency", Currency);
serializer.Pair("amount", Amount);
serializer.EndObject();
}
template<typename JSON>
void Deserialize(const JSON& json)
{
using namespace CppSerialization::JSON;
Deserializer::Find(json, "currency", Currency);
Deserializer::Find(json, "amount", Amount);
}
...
};
struct Account
{
...
// JSON serialization
template<typename OutputStream>
void Serialize(CppSerialization::JSON::Serializer<OutputStream>& serializer)
{
serializer.StartObject();
serializer.Pair("id", Id);
serializer.Pair("name", Name);
serializer.Key("wallet");
Wallet.Serialize(serializer);
serializer.Key("orders");
serializer.StartArray();
for (auto& order : Orders)
order.Serialize(serializer);
serializer.EndArray();
serializer.EndObject();
}
template<typename JSON>
void Deserialize(const JSON& json)
{
using namespace CppSerialization::JSON;
Deserializer::Find(json, "id", Id);
Deserializer::Find(json, "name", Name);
Deserializer::FindObject(json, "wallet", [this](const Value::ConstObject& object)
{
Wallet.Deserialize(object);
});
Orders.clear();
Deserializer::FindArray(json, "orders", [this](const Value& item)
{
Order order;
order.Deserialize(item);
Orders.emplace_back(order);
});
}
...
};
} // namespace TradeProto
JSON example
Here comes the usage example of JSON serialize/deserialize functionality:
#include "../proto/trade.h"
#include "serialization/json/parser.h"
#include <iostream>
int main(int argc, char** argv)
{
// Create a new account with some orders
TradeProto::Account account(1, "Test", "USD", 1000);
account.Orders.emplace_back(TradeProto::Order(1, "EURUSD", TradeProto::OrderSide::BUY, TradeProto::OrderType::MARKET, 1.23456, 1000));
account.Orders.emplace_back(TradeProto::Order(2, "EURUSD", TradeProto::OrderSide::SELL, TradeProto::OrderType::LIMIT, 1.0, 100));
account.Orders.emplace_back(TradeProto::Order(3, "EURUSD", TradeProto::OrderSide::BUY, TradeProto::OrderType::STOP, 1.5, 10));
// Serialize the account to the JSON buffer
CppSerialization::JSON::StringBuffer buffer;
CppSerialization::JSON::Serializer<CppSerialization::JSON::StringBuffer> serializer(buffer);
account.Serialize(serializer);
// Show original and JSON serialized sizes
std::cout << "Original size: " << account.size() << std::endl;
std::cout << "JSON content: " << buffer.GetString() << std::endl;
std::cout << "JSON size: " << buffer.GetSize() << std::endl;
// Parse JSON string
CppSerialization::JSON::Document json = CppSerialization::JSON::Parser::Parse(buffer.GetString());
// Deserialize the account from the JSON buffer
TradeProto::Account deserialized;
deserialized.Deserialize(json);
// Show account content
std::cout << std::endl;
std::cout << "Account.Id = " << deserialized.Id << std::endl;
std::cout << "Account.Name = " << deserialized.Name << std::endl;
std::cout << "Account.Wallet.Currency = " << deserialized.Wallet.Currency << std::endl;
std::cout << "Account.Wallet.Amount = " << deserialized.Wallet.Amount << std::endl;
for (auto& order : deserialized.Orders)
{
std::cout << "Account.Order => Id: " << order.Id
<< ", Symbol: " << order.Symbol
<< ", Side: " << (int)order.Side
<< ", Type: " << (int)order.Type
<< ", Price: " << order.Price
<< ", Volume: " << order.Volume
<< std::endl;
}
return 0;
}
Output of the example is the following:
Original size: 128
JSON content: {"id":1,"name":"Test","wallet":{"currency":"USD","amount":1000.0},"orders":[{"id":1,"symbol":"EURUSD","side":0,"type":0,"price":1.23456,"volume":1000.0},{"id":2,"symbol":"EURUSD","side":1,"type":1,"price":1.0,"volume":100.0},{"id":3,"symbol":"EURUSD","side":0,"type":2,"price":1.5,"volume":10.0}]}
JSON size: 297
Account.Id = 1
Account.Name = Test
Account.Wallet.Currency = USD
Account.Wallet.Amount = 1000
Account.Order => Id: 1, Symbol: EURUSD, Side: 0, Type: 0, Price: 1.23456, Volume: 1000
Account.Order => Id: 2, Symbol: EURUSD, Side: 1, Type: 1, Price: 1, Volume: 100
Account.Order => Id: 3, Symbol: EURUSD, Side: 0, Type: 2, Price: 1.5, Volume: 10
JSON performance
JSON serialization performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.041 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:29:18 2025
UTC timestamp: Wed Jul 16 17:29:18 2025
===============================================================================
Benchmark: JSON-Serialize
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: JSON-Serialize
Average time: 696 ns/op
Minimal time: 695 ns/op
Maximal time: 710 ns/op
Total time: 4.932 s
Total operations: 7078563
Total bytes: 2.357 GiB
Operations throughput: 1435099 ops/s
Bytes throughput: 487.794 MiB/s
Custom values:
MessageSize: 297
OriginalSize: 128
===============================================================================
JSON document parsing performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.154 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:30:48 2025
UTC timestamp: Wed Jul 16 17:30:48 2025
===============================================================================
Benchmark: JSON-Parse
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: JSON-Parse
Average time: 771 ns/op
Minimal time: 771 ns/op
Maximal time: 779 ns/op
Total time: 4.914 s
Total operations: 6372779
Total bytes: 2.118 GiB
Operations throughput: 1296734 ops/s
Bytes throughput: 440.764 MiB/s
Custom values:
MessageSize: 297
===============================================================================
JSON deserialization performance of the provided domain model is the following:
===============================================================================
CppBenchmark report. Version 1.0.5.0
===============================================================================
CPU architecture: Apple M1 Pro
CPU logical cores: 10
CPU physical cores: 10
CPU clock speed: 2.400 GHz
CPU Hyper-Threading: disabled
RAM total: 32.000 GiB
RAM free: 1.240 GiB
===============================================================================
OS version: 24.5.0
OS bits: 64-bit
Process bits: 64-bit
Process configuration: release
Local timestamp: Wed Jul 16 19:31:50 2025
UTC timestamp: Wed Jul 16 17:31:50 2025
===============================================================================
Benchmark: JSON-Deserialize
Attempts: 5
Duration: 5 seconds
-------------------------------------------------------------------------------
Phase: JSON-Deserialize
Average time: 291 ns/op
Minimal time: 291 ns/op
Maximal time: 292 ns/op
Total time: 4.924 s
Total operations: 16884318
Total bytes: 77.297 MiB
Operations throughput: 3428637 ops/s
Bytes throughput: 15.711 MiB/s
Custom values:
MessageSize: 297
OriginalSize: 128
===============================================================================