JSerializer is a single header C++ library for serializing data structures into JSON format, it provides a simple and efficient way to write various data types, including strings, numbers, variants, maps, and nested structures, while maintaining type safety (SFINAE) and extreme performance (SIMD).
- Simple & Efficient: Features a clean, intuitive API for serializing data structures with minimal boilerplate and maximum performance (SIMD).
- Stream-Oriented: Natively works with any writable output stream, from stdout to file or network sockets, integrating seamlessly into your I/O pipeline.
- Static Memory Allocation: Guarantees no dynamic memory allocation during serialization, making it ideal for latency-sensitive and real-time applications.
- Zero Runtime Overhead: Built for raw performance by avoiding RTTI, exceptions, and other runtime costs, ensuring a minimal footprint.
- Type-Safe: Provides a compile-time, type-safe interface that prevents data corruption and ensures output correctness by design.
- Header-Only & STL Compliant: A single-header, dependency-free library with out-of-the-box support for standard library containers. Just include and go.
#include <map>
#include <vector>
#include <unordered_map>
#include "jserializer.hpp"
using namespace ubn;
auto main() -> int {
auto serializer = JSerializer::create( // Create a serializer instance wraped in a smart pointer.
StdoutStream::write, // Default write callback, takes data pointer and size, returns the number
// of bytes written or a negative error code.
StdoutStream::flush, // Default flush callback, called with the error code of current instance, returns
// 0 or a positive number when successful, otherwise a negative error code.
nullptr, 0); // Optional data buffer and size, set nullptr to allocate buffer internally, in
// this case the default buffer size can be updated by setting a non-zero size.
if (!serializer) { return -1; } // Check if the serializer was created successfully.
std::string_view str = "你好";
const char* c_str = "world.execute(me);";
std::vector<std::vector<int>> mat = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
std::vector<std::vector<std::vector<float>>> tsr = { { { -0.57721f, 2.71828f }, { INFINITY, NAN } }, { { 5.67f, 6.626f }, { 7.0f, 8.314f } } };
std::map<std::string, std::variant<int, float>> map = { { "Nucleosynthesis", 0 }, { "Formation", 1.2025f }, { "Heat Death", -1 } };
std::unordered_map<std::string_view, const char*> another_map = { {"\vKey\\", "unsafe \rvalue\n" } };
auto writer = serializer->writer(); // Create a writer instance you can update the write callback and flush
// callback here.
writer[str] << str << ", 🌍 " // Add strings, use [] to skip the escape characters check for the key,
<< 123 << "!"; // with << the remain variables is automatically appended to the string.
writer("A \"Pie\"") << 3.141; // Use () if you're not sure whether key contains escape characters.
writer["Cat"] += "Kawaii"; // Use += to skip escaping or force a fixed-point notation, otherwise
// use << to let writer handle them automatically.
writer["Matrix"] << mat; // Easily write a 2D vector as a JSON array.
writer["Tensor"] << tsr; // Integrates N-D STL container support.
writer << map; // Serialize a map to current JSON object, variants are supported as well.
writer["Consistent"] += map; // Writing map to a specified object with consistent representation is fine.
{
auto writer_2 = writer["Nested"](); // Create a nested object writer.
writer_2["一生、バンドしてくれる?"] << true; // Write a boolean value.
writer_2[42] << "Catching on, our paths unknown"; // Interger key will atomatically convert to string.
writer_2["BadNumber"] << -INFINITY; // +-Inf/NaN will atomatically convert to null.
{
auto writer_3 = writer_2["Nested"](); // Endless nesting is supported, take it easy with loops.
writer_3["ThisLine"] << __LINE__; // Work with both integral and floating point values.
writer_3("\t Exec") += c_str; // Write a null-terminated C-style string without escape checking.
writer_3["Null"] << nullptr; // Write a null object.
}
writer_2["Bytes"].writer<StringWriter>() // Write string as raw bytes, this would invoke the write callback
.write(c_str, std::strlen(c_str)); // directly after the buffer is synchronized for maximum performance.
}
writer["Another"] << another_map; // Serialize another map with automatic escap checking and general number representation.
// While adding new elements to the JSON object, the serializer will automatically call write callback to
// write the data to the output stream when the buffer is full or raw bytes write method is called.
// The writers handle all type overloading automatically at compile time, there's almost no overhead, and
// the serializer will automatically flush the output stream when the last writer is destroyed.
// General number representation would use both fixed or scientific representation when formatting a number,
// integrals would always be fixed and floating point numbers would be formated to the nearest length to the limits.
// You can also modify some predefined macros if needed:
// JS_DEFAULT_BUFFER_SIZE: (1024 * 16) // Default buffer size for the serializer.
// JS_DEFAULT_FMT_BUFFER_SIZE: 64 // Default buffer size for the number formatter.
// JS_DEFAULT_FLOATING_POINT_PRECISION: 3 // Default length of general number representation for floating point numbers.
// JS_EXPERIMENTAL_SIMD: AUTO // Auto detect experimental SIMD support if not defined, requires parallelism TS v2.
// JS_DEFAULT_SIMD_ENABLE_LEN_FACTOR: 3 // Multiplied with SIMD vector size, determines the minimal length to use SIMD.
return serializer->state(); // Should return 0 on success, otherwise a positive error code (POSIX standard).
}
Outputs:
{"你好":"你好, 🌍 123!","A \"Pie\"":3.14,"Cat":"Kawaii","Matrix":[[1,2,3],[4,5,6],[7,8,9]],"Tensor":[[[-0.577,2.72],[null,null]],[[5.67,6.63],[7,8.31]]],"Formation":1.2,"Heat Death":-1,"Nucleosynthesis":0,"Consistent":{"Formation":1.2025,"Heat Death":-1,"Nucleosynthesis":0},"Nested":{"一生、バンドしてくれる?":true,"42":"Catching on, our paths unknown","BadNumber":null,"Nested":{"ThisLine":44,"\t Exec":"world.execute(me);","Null":null},"Bytes":"world.execute(me);"},"Another":{"\u000bKey\\":"unsafe \rvalue\n"}}
Pretty-printed output:
{
"你好": "你好, 🌍 123!",
"A \"Pie\"": 3.14,
"Cat": "Kawaii",
"Matrix": [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
],
"Tensor": [
[[-0.577, 2.72], [null, null]],
[[5.67, 6.63], [7, 8.31]]
],
"Formation": 1.2,
"Heat Death": -1,
"Nucleosynthesis": 0,
"Consistent": {
"Formation": 1.2025,
"Heat Death": -1,
"Nucleosynthesis": 0
},
"Nested": {
"一生、バンドしてくれる?": true,
"42": "Catching on, our paths unknown",
"BadNumber": null,
"Nested": {
"ThisLine": 44,
"\t Exec": "world.execute(me);",
"Null": null
},
"Bytes": "world.execute(me);"
},
"Another": {
"\u000bKey\\": "unsafe \rvalue\n"
}
}
JSerializer is released under the MIT License. See the LICENSE file for more information.