JSerializer

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, maps, and nested structures, while maintaining type safety and extreme performance.

Features

Example

#include <map>
#include <vector>
#include <unordered_map>

#include "jserializer.hpp"

auto main() -> int {
    auto serializer = ubn::JSerializer::create(        // Create a thread-safe serializer instance.
        [](const void* data, size_t size) -> int {     // Specify a write callback, write to stdout by
            if (!data || size == 0) { return 0; }      // default.
            return std::fwrite(data, 1, size, stdout); // Returns bytes written or negative error code.
        },
        [](int error_code) -> int {                    // Specify a flush callback, flush stdout.
            std::fwrite("\n", 1, 1, stdout);
            return std::fflush(stdout);                // Return 0 on success, negative error code on failure.
        });                                            // You can also bring your own buffer here.
    if (!serializer) { return -1; }                    // Check if the serializer was created successfully.

    std::string_view str = "Hello";
    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, flush
                                        // callback and wait callback here.
    writer[str] << str << ", World "    // Use [] to skip check for escape characters for key, and stream remain
                << 123 << "!";          // variables to the object using <<.
    writer("A \"Pie\"") << 3.141;       // Use () if you're not sure if the key is a valid string.
    writer["Cat"] += "Kawaii";          // Use += to skip escaping or floating point rounding, otherwise
                                        // use << to let writer handle them automatically.
    writer["Matrix"] << mat;            // Easily write a 2D vector as a JSON array.
    writer["Tensor"] << tsr;            // N-D STL container is also supported.
    writer << map;                      // Serialize a map as a JSON object, variants are supported as well.
    writer["Consistent"] += map;        // Also supports writing to a specified key with consistent representation.
    {
        auto writer_2 = writer["Nested"]();               // Create a nested object writer.
        writer_2["Isshō, bando shite kureru?"] << 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.
        const char* c_str = "world.execute(me);";
        {
            auto writer_3 = writer_2["Nested - 2"]();     // Endless nesting is supported.
            writer_3["ThisLine"] << __LINE__;             // Both integral and floating point value are supported.
            writer_3("\t Exec") += c_str;                 // Write a C-style string, assuming no escaping needed.
        }
        writer_2["Bytes"].writer<ubn::StringWriter>()     // Write raw bytes, this would call write callback directly
            .write(c_str, std::strlen(c_str));            // after the buffer is synchronized for maximum performance.
    }
    writer["Another"] << another_map; // Serialize another map with automatic escaping and number rounding, since the
                                      // 1st writer is still in scope.

    // 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 writing raw bytes 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.
    // You can also modify some predefinitions if needed:
    // - JS_LOG_LEVEL: JS_WARNING
    // - JS_DEFAULT_BUFFER_SIZE: 1024
    // - JS_DEFAULT_FMT_BUFFER_SIZE: 64
    // - JS_DEFAULT_FLOATING_POINT_PRECISION: 3

    return serializer->state(); // Should return 0 on success, otherwise a positive error code.
}

Outputs:

{"Hello":"Hello, World 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":{"Isshō, bando shite kureru?":true,"42":"Catching on, our paths unknown","BadNumber":null,"Nested - 2":{"ThisLine":112,"\t Exec":"world.execute(me);"},"Bytes":"world.execute(me);"},"Another":{"Key\\":"unsafe \rvalue\n"}}

Pretty-printed output:

{
  "Hello": "Hello, World 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": {
    "Isshō, bando shite kureru?": true,
    "42": "Catching on, our paths unknown",
    "BadNumber": null,
    "Nested - 2": {
      "ThisLine": 112,
      "\t Exec": "world.execute(me);"
    },
    "Bytes": "world.execute(me);"
  },
  "Another": {
    "Key\\": "unsafe \rvalue\n"
  }
}

License

JSerializer is released under the MIT License. See the LICENSE file for more information.