{"id":24600789,"url":"https://github.com/PavelKisliak/BitSerializer","last_synced_at":"2025-10-05T21:32:06.151Z","repository":{"id":189214129,"uuid":"680262632","full_name":"PavelKisliak/BitSerializer","owner":"PavelKisliak","description":"Multi-format serialization library (JSON, XML, YAML, CSV, MsgPack)","archived":false,"fork":false,"pushed_at":"2024-10-29T19:59:47.000Z","size":1226,"stargazers_count":7,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-10-29T20:12:29.012Z","etag":null,"topics":["csv","json","messagepack","msgpack","serialization","xml","yaml"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PavelKisliak.png","metadata":{"files":{"readme":"README.md","changelog":"History.md","contributing":null,"funding":null,"license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-18T18:41:01.000Z","updated_at":"2024-10-13T13:48:40.000Z","dependencies_parsed_at":null,"dependency_job_id":"abf4f0bc-4a2e-4d8d-aff1-952e05ee6008","html_url":"https://github.com/PavelKisliak/BitSerializer","commit_stats":null,"previous_names":["pavelkisliak/bitserializer"],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PavelKisliak%2FBitSerializer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PavelKisliak%2FBitSerializer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PavelKisliak%2FBitSerializer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PavelKisliak%2FBitSerializer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PavelKisliak","download_url":"https://codeload.github.com/PavelKisliak/BitSerializer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235448520,"owners_count":18991891,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csv","json","messagepack","msgpack","serialization","xml","yaml"],"created_at":"2025-01-24T14:01:33.220Z","updated_at":"2025-10-05T21:32:06.144Z","avatar_url":"https://github.com/PavelKisliak.png","language":"C++","readme":"# BitSerializer ![GitHub Release](https://img.shields.io/github/v/release/PavelKisliak/BitSerializer?color=blue) [![Vcpkg Version](https://img.shields.io/vcpkg/v/bitserializer?color=blue)](https://vcpkg.link/ports/bitserializer) [![Conan Center](https://img.shields.io/conan/v/bitserializer?color=blue)](https://conan.io/center/recipes/bitserializer) [![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](license.txt) [![Build Status](https://dev.azure.com/real0793/BitSerializer/_apis/build/status%2FGitHub-BitSerializer?branchName=master)](https://dev.azure.com/real0793/BitSerializer/_build/latest?definitionId=5\u0026branchName=master)\n\n___\n\n### Main features:\n- One common interface allows easy switching between formats JSON, XML, YAML, CSV and MsgPack.\n- Modular architecture lets you include only the serialization archives you need.\n- Compile-time validation of format rules (e.g. JSON allows primitives as roots, while CSV only allows arrays).\n- Functional serialization style similar to the Boost library.\n- Support loading named fields in any order with conditional logic to preserve model compatibility.\n- Customizable validation produces a detailed list of errors for deserialized values.\n- Post-load refiners transform deserialized data, for example, trimming strings or setting default values.¹\n- Seamless handling of optional and required fields, bypassing the need to use `std::optional`.\n- Configurable set of policies to control overflow and type mismatch errors.\n- Serialization support for almost all STD containers and types (including Unicode strings like `std::u16string`).\n- Enums can be serialized as integers or strings, giving you full control over representation.\n- Support serialization to memory, streams and files.\n- Full Unicode support with automatic detection and transcoding (except YAML).\n- A powerful [string conversion submodule](docs/bitserializer_convert.md) supports enums, classes, chrono types, and UTF encoding.\n\n ¹ New feature (not supported in the latest released version of BitSerializer v0.80, please use the master branch).\n\n#### Supported formats:\n| Component | Format | Encoding | Pretty format | Based on |\n| ------ | ------ | ------ |:------:| ------ |\n| [rapidjson-archive](docs/bitserializer_rapidjson.md) | JSON | UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE | ✅ | [RapidJson](https://github.com/Tencent/rapidjson) |\n| [pugixml-archive](docs/bitserializer_pugixml.md) | XML | UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE | ✅ | [PugiXml](https://github.com/zeux/pugixml) |\n| [rapidyaml-archive](docs/bitserializer_rapidyaml.md) | YAML | UTF-8 | N/A | [RapidYAML](https://github.com/biojppm/rapidyaml) |\n| [csv-archive](docs/bitserializer_csv.md) | CSV | UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE | N/A | Built-in |\n| [msgpack-archive](docs/bitserializer_msgpack.md) | MsgPack | Binary | N/A | Built-in |\n\n#### Requirements:\n - C++ 17 (VS 2019, GCC-8, CLang-8, AppleCLang-12).\n - Supported platforms: Windows, Linux, MacOS (x86, x64, arm32, arm64, arm64be¹).\n - JSON, XML and YAML archives are based on third-party libraries (there are plans to reduce dependencies).\n\n ¹ Versions of the RapidYaml base library less than v0.7.1 may be unstable on ARM architecture.\n\n#### Limitations:\n - Work without exceptions is not supported.\n\n___\n## Table of contents\n- [Hello world](#hello-world)\n- [Performance overview](#performance-overview)\n- [How to install](#how-to-install)\n- [Unicode support](#unicode-support)\n- [Serializing class](#serializing-class)\n- [Serializing base class](#serializing-base-class)\n- [Serializing third party class](#serializing-third-party-class)\n- [Serializing a class that represents an array](#serializing-a-class-that-represents-an-array)\n- [Serializing custom class representing a string](#serializing-custom-class-representing-a-string)\n- [Serializing enum types](#serializing-enum-types)\n- [Serializing to multiple formats](#serializing-to-multiple-formats)\n- [Serialization STD types](#serialization-std-types)\n- [Specifics of serialization STD map](#specifics-of-serialization-std-map)\n- [Serialization date and time](#serialization-date-and-time)\n- [Conditional loading and versioning](#conditional-loading-and-versioning)\n- [Serialization to streams and files](#serialization-to-streams-and-files)\n- [Error handling](#error-handling)\n- [Validation of deserialized values](#validation-of-deserialized-values)\n- [Post-load data refinement](#post-load-data-refinement)\n- [Compile-time format validation](#compile-time-format-validation)\n- [What else to read](#what-else-to-read)\n- [Thanks](#thanks)\n\n___\n\n### Hello world\nLet's get started with a traditional \"Hello world!\" example that demonstrates BitSerializer's serialization features, such as validation (e.g., email and phone number formats), post-load value refinement (trimming whitespace, case conversion, fallbacks), handling optional fields, and converting between formats (JSON to CSV).\nThe example highlights the flexibility of the library in handling different data types, including `std::chrono`, Unicode strings, and data integrity through required/optional constraints.\n```cpp\n#include \u003ciostream\u003e\n#include \"bitserializer/bit_serializer.h\"\n#include \"bitserializer/rapidjson_archive.h\"\n#include \"bitserializer/csv_archive.h\"\n#include \"bitserializer/types/std/vector.h\"\n#include \"bitserializer/types/std/chrono.h\"\n\nusing namespace BitSerializer;\nusing JsonArchive = BitSerializer::Json::RapidJson::JsonArchive;\nusing CsvArchive = BitSerializer::Csv::CsvArchive;\n\nstruct CUser\n{\n    // Mandatory fields\n    uint64_t Id = 0;\n    std::u16string Name;\n    std::chrono::system_clock::time_point Birthday;\n    std::string Email;\n    // Optional fields (maybe absent or `null` in the source JSON)\n    std::string PhoneNumber;\n    std::u32string NickName;\n    std::string Language;\n\n    template \u003cclass TArchive\u003e\n    void Serialize(TArchive\u0026 archive)\n    {\n        archive \u003c\u003c KeyValue(\"Id\", Id, Required());\n        // Using the `Required()` validator with a custom error message (can be ID of localization string)\n        archive \u003c\u003c KeyValue(\"Birthday\", Birthday, Required(\"Birthday is required\"));\n        archive \u003c\u003c KeyValue(\"Name\", Name, Required(), Validate::MaxSize(32));\n        archive \u003c\u003c KeyValue(\"Email\", Email, Required(), Refine::TrimWhitespace(), Validate::Email());\n        // Optional field (should be empty or contain a valid phone number)\n        archive \u003c\u003c KeyValue(\"PhoneNumber\", PhoneNumber, Refine::TrimWhitespace(), Validate::PhoneNumber());\n        archive \u003c\u003c KeyValue(\"NickName\", NickName);\n        // Use fallback value \"en\" if missing data\n        archive \u003c\u003c KeyValue(\"Language\", Language, Refine::ToLowerCase(), Fallback(\"en\"));\n    }\n};\n\nint main()  // NOLINT(bugprone-exception-escape)\n{\n    const char* sourceJson = R\"([\n{ \"Id\": 1, \"Birthday\": \"1998-05-15T00:00:00Z\", \"Name\": \"John Doe\", \"Email\": \"john.doe@example.com\", \"PhoneNumber\": \"+(123) 4567890\", \"NickName\": \"JD\" },\n{ \"Id\": 2, \"Birthday\": \"1993-08-20T00:00:00Z\", \"Name\": \"Alice Smith\", \"Email\": \"alice.smith@example.com\", \"PhoneNumber\": \"+(098) 765-43-21\", \"NickName\": \"Ali\" },\n{ \"Id\": 3, \"Birthday\": \"2001-03-10T00:00:00Z\", \"Name\": \"Ivan Petrov\", \"Email\": \"ivan.petrov@example.com\", \"PhoneNumber\": null, \"Language\": \"RU\" }\n])\";\n\n    // Load list of users from JSON\n    std::vector\u003cCUser\u003e users;\n    BitSerializer::LoadObject\u003cJsonArchive\u003e(users, sourceJson);\n\n    // Save to CSV\n    std::string csv;\n    BitSerializer::SaveObject\u003cCsvArchive\u003e(users, csv);\n\n    std::cout \u003c\u003c csv \u003c\u003c std::endl;\n    return EXIT_SUCCESS;\n}\n```\nExample output:\n```\nId,Birthday,Name,Email,PhoneNumber,NickName,Language\n1,1998-05-15T00:00:00.0000000Z,John Doe,john.doe@example.com,+(123) 4567890,JD,en\n2,1993-08-20T00:00:00.0000000Z,Alice Smith,alice.smith@example.com,+(098) 765-43-21,Ali,en\n3,2001-03-10T00:00:00.0000000Z,Ivan Petrov,ivan.petrov@example.com,,,ru\n```\nBitSerializer treats missing fields as optional by default, but you can enforce mandatory fields using the `Required()` validator. This approach eliminates the need for workarounds like using `std::optional`, simplifying your business logic by avoiding repetitive checks for value existence. The library's robust validation system collects all invalid fields during deserialization, enabling comprehensive error reporting (with localization support if needed). Additionally, BitSerializer ensures type safety by throwing exceptions for type mismatches or overflow errors, such as when deserializing values that exceed the capacity of the target type.\n\n### Performance overview\nBitSerializer prioritizes reliability and usability, but we understand that performance remains a critical factor for serialization libraries.\nThis chapter provides an overview of the performance characteristics of BitSerializer across various serialization formats, as well as comparative tests with the used third-party libraries.\n\n#### Key performance insights\n- Formats implemented natively in BitSerializer (MsgPack and CSV) demonstrate excellent performance due to their DOM-free architecture. This approach eliminates intermediate object tree construction, enabling direct serialization/deserialization to/from streams.\n- Formats relying on external libraries (RapidJSON, PugiXML, RapidYAML) show an average performance loss of ~5% compared to their native APIs. This minor trade-off is due to the unified BitSerializer abstraction layer, which provides consistent behavior across all supported formats.\n- All formats support non-linear loading of named fields, but maximum performance can be achieved when loading in the same order. This feature is important for compatibility and flexibility when working with complex models (e.g. for updating models).\n\n#### Comparing \"Parsers\" and \"Serializers\" library classes\nIt is important to note that comparing \"Serialization\" classes (like BitSerializer) with \"Parser\" classes (such as RapidJSON, NlohmannJson, PugiXML, or RapidYAML) may not always be entirely fair. These two categories of libraries differ fundamentally in their design and purpose:\n- **Parsers:** Typically operate on a DOM-based model, where the entire document is loaded into memory before processing. This approach is well-suited for tasks requiring extensive manipulation of the data structure but can introduce overhead during serialization and deserialization.\n- **Serializers:** Focus on streaming serialization, where data is processed incrementally without the need to build an intermediate DOM. This approach is generally faster and more memory-efficient but may lack some of the advanced manipulation features offered by parsers.\n\nIn this performance analysis, we have benchmarked BitSerializer against the base libraries it relies on (e.g., RapidJSON, PugiXML, and RapidYAML).\nThese libraries are primarily \"Parser\" classes, and the performance differences observed reflect the inherent trade-offs between DOM-based parsing and streaming serialization. \n\nWe understand that comparing \"Serialization\" classes with \"Parser\" classes might not always be equitable due to the fundamental differences in their nature (e.g., DOM vs. streaming serialization).\nHowever, this comparison provides valuable insights into how BitSerializer performs relative to the libraries it builds upon.\nIn the future, we plan to include benchmarks against other libraries from the \"Serializers\" class to offer a more direct comparison. \n\n#### Comparison of serialized data size\nIn addition to performance metrics, the size of the serialized output is another important factor to consider when choosing a serialization format.\nBelow is a comparison of the serialized output sizes (in bytes) for the same test model using different formats:\n\n![image info](benchmarks/archives/benchmark_results/serialization_output_size_chart.png)\n\nBinary formats like MsgPack produce significantly smaller outputs compared to text-based formats like JSON, XML, or YAML.\nThe CSV format is the most compact among all tested formats, making it an excellent choice for storage and transmission of tabular data.\n\n#### Performance test methodology\n- ***Metrics:*** To evaluate the performance of BitSerializer, we measure the number of fields processed per millisecond (`fields/ms`) during serialization and deserialization. This metric allows us to objectively compare the efficiency of different formats and libraries.\n- **Test model:** The [test model](benchmarks/archives/test_model.h) consists of an array of objects containing various data types compatible with all supported formats. This ensures a fair comparison of formats since the same data structure is used for all tests.\n\n#### Performance test results\n![image info](benchmarks/archives/benchmark_results/serialization_speed_chart.png)\n\nFor most applications, BitSerializer provides the optimal combination of reliability, feature completeness, and performance. Developers working with MsgPack/CSV will see best-in-class speeds, while users needing JSON/XML/YAML benefit from consistent performance with minimal overhead compared to format-specific libraries.\n\n### How to install\nSome archives (JSON, XML and YAML) require third-party libraries, but you can install only the ones which you need.\nThe easiest way is to use one of supported package managers, in this case, third-party libraries will be installed automatically.\nPlease follow [instructions](#what-else-to-read) for specific archives.\n\n#### VCPKG\nJust add BitSerializer to manifest file (`vcpkg.json`) in your project:\n```json\n{\n    \"dependencies\": [\n        {\n            \"name\": \"bitserializer\",\n            \"features\": [ \"rapidjson-archive\", \"pugixml-archive\", \"rapidyaml-archive\", \"csv-archive\", \"msgpack-archive\" ]\n        }\n    ]\n}\n```\nThe latest available version: [![Vcpkg Version](https://img.shields.io/vcpkg/v/bitserializer?color=blue)](https://vcpkg.link/ports/bitserializer)\n\nEnumerate features which you need, by default all are disabled. Use like as usual in the [Cmake](#how-to-use-with-cmake).\n\nAlternatively, you can install the library via the command line:\n```shell\n\u003e vcpkg install bitserializer[rapidjson-archive,pugixml-archive,rapidyaml-archive,csv-archive,msgpack-archive]\n```\nIn the square brackets enumerated all available formats, install only which you need.\n\n#### Conan 2\nThe recipe of BitSerializer is available on [Conan-center](https://github.com/conan-io/conan-center-index), just add BitSerializer to `conanfile.txt` in your project and enable archives which you need via options (by default all are disabled):\n```\n[requires]\nbitserializer/x.xx\n\n[options]\nbitserializer/*:with_rapidjson=True\nbitserializer/*:with_pugixml=True\nbitserializer/*:with_rapidyaml=True\nbitserializer/*:with_csv=True\nbitserializer/*:with_msgpack=True\n```\nReplace `x.xx` with the latest available version: [![Conan Center](https://img.shields.io/conan/v/bitserializer?color=blue)](https://conan.io/center/recipes/bitserializer)\n\n#### Installation via CMake on a Unix system\n```sh\n$ git clone https://github.com/PavelKisliak/BitSerializer.git\n$ # Enable only archives which you need (by default all are disabled)\n$ cmake bitserializer -B bitserializer/build -DBUILD_RAPIDJSON_ARCHIVE=ON -DBUILD_PUGIXML_ARCHIVE=ON -DBUILD_RAPIDYAML_ARCHIVE=ON -DBUILD_CSV_ARCHIVE=ON -DBUILD_MSGPACK_ARCHIVE=ON\n$ sudo cmake --build bitserializer/build --config Debug --target install\n$ sudo cmake --build bitserializer/build --config Release --target install\n```\nBy default, will be built a static library, add the CMake parameter `-DBUILD_SHARED_LIBS=ON` to build shared.\nYou will also need to install dev-packages of base libraries (CSV and MsgPack archives do not require any dependencies), currently available only `rapidjson-dev` and `libpugixml-dev`, the RapidYaml library needs to be compiled manually.\n\n\u003e [!IMPORTANT]\n\u003e Make sure your application and library are compiled with the same options (C++ standard, optimization flags, runtime type, etc.) to avoid binary incompatibility issues.\n\n#### How to use with CMake\n```cmake\nfind_package(bitserializer CONFIG REQUIRED)\n# Link only archives which you need\ntarget_link_libraries(${PROJECT_NAME} PRIVATE\n    BitSerializer::rapidjson-archive\n    BitSerializer::pugixml-archive\n    BitSerializer::rapidyaml-archive\n    BitSerializer::csv-archive\n    BitSerializer::msgpack-archive\n)\n```\n\n### Unicode support\nBitSerializer provides comprehensive Unicode support by enabling serialization of any `std::basic_string` type (e.g., `std::u8string`, `std::u16string`, `std::u32string`) while automatically handling transcoding to the target output format. You can also use any string type as keys, but keep in mind that transcoding incurs additional processing overhead. For optimal performance, prefer UTF-8 strings, as they are natively supported by all archives and minimize transcoding costs. \n\nThe example below demonstrates how BitSerializer seamlessly handles different string types and encodings: \n```cpp\nclass TestUnicodeClass\n{\npublic:\n    template \u003cclass TArchive\u003e\n    void Serialize(TArchive\u0026 archive)\n    {\n        // Serialize a UTF-8 string with key in UTF-16\n        archive \u003c\u003c KeyValue(u\"Utf16Key\", mUtf8StringValue);\n\n        // Serialize a UTF-16 string with key in UTF-32\n        archive \u003c\u003c KeyValue(U\"Utf32Key\", mUtf16StringValue);\n\n        // Serialize a UTF-32 string with key in UTF-8\n        archive \u003c\u003c KeyValue(u8\"Utf8Key\", mUtf32StringValue);\n    };\n\nprivate:\n    std::string mUtf8StringValue;       // UTF-8 encoded string\n    std::u16string mUtf16StringValue;   // UTF-16 encoded string\n    std::u32string mUtf32StringValue;   // UTF-32 encoded string\n};\n```\nThis flexibility allows you to work with various Unicode encodings without worrying about manual transcoding.\nHowever, for best results, use UTF-8 consistently unless your application specifically requires other encodings.\n\n### Serializing class\nThere are two ways to serialize a class:\n\n  * Internal public method `Serialize()` - good way for your own classes.\n  * External global function `SerializeObject()` - used for third party class (no access to sources).\n\nBelow example demonstrates how to implement internal serialization method:\n```cpp\n#include \"bitserializer/bit_serializer.h\"\n#include \"bitserializer/rapidjson_archive.h\"\n\nusing JsonArchive = BitSerializer::Json::RapidJson::JsonArchive;\n\nclass TestSimpleClass\n{\npublic:\n    TestSimpleClass()\n        : testBool(true)\n        , testString(L\"Hello world!\")\n    {\n        for (size_t i = 0; i \u003c 3; i++)\n        {\n            for (size_t k = 0; k \u003c 2; k++) {\n                testTwoDimensionArray[i][k] = i * 10 + k;\n            }\n        }\n    }\n\n    template \u003cclass TArchive\u003e\n    void Serialize(TArchive\u0026 archive)\n    {\n        using namespace BitSerializer;\n        archive \u003c\u003c KeyValue(\"TestBool\", testBool);\n        archive \u003c\u003c KeyValue(\"TestString\", testString);\n        archive \u003c\u003c KeyValue(\"TestTwoDimensionArray\", testTwoDimensionArray);\n    };\n\nprivate:\n    bool testBool;\n    std::wstring testString;\n    size_t testTwoDimensionArray[3][2];\n};\n\nint main()\n{\n    auto simpleObj = TestSimpleClass();\n    auto result = BitSerializer::SaveObject\u003cJsonArchive\u003e(simpleObj);\n    return 0;\n}\n```\nReturns result\n```json\n{\n    \"TestBool\": true,\n    \"TestString\": \"Hello world!\",\n    \"TestTwoDimensionArray\": [\n        [0, 1],\n        [10, 11],\n        [20, 21]\n    ]\n}\n```\nFor serializing a named object please use helper class `KeyValue` which takes `key` and `value` as constructor arguments. Usually the type of key is UTF-8 string, but you are free to use any other convertible type (`std::u16string`, `std::u32string` or any numeric types). For example, MsgPack archive has native support for numbers as keys, they will be converted to string when use with another archives. For get maximum performance, better to avoid any conversions.\n\n### Serializing base class\nTo serialize the base class, use the helper method `BaseObject()`, like as in the next example.\n```cpp\ntemplate \u003cclass TArchive\u003e\nvoid Serialize(TArchive\u0026 archive)\n{\n    archive \u003c\u003c BaseObject\u003cMyBaseClass\u003e(*this);\n    archive \u003c\u003c KeyValue(\"TestInt\", TestInt);\n};\n```\n\u003e [!NOTE]\n\u003e Version 0.75 and earlier support serialization of the base class only via the internal `Serialize()` method.\n\n### Serializing third party class\nAs alternative for internal `Serialize()` method also exists approach with defining global functions, it will be useful in next cases:\n\n - Sources of serializing class cannot be modified (for example from third party library).\n - When class represents list of some values (such as `std::vector`), see [next chapter](#serializing-class-that-represent-an-array).\n - When you strongly follow single responsibility principle and wouldn't like to include serialization code into class.\n\n\u003e [!NOTE]\n\u003e Internal `Serialize()` method has higher priority than global one (in v0.75 was a priority for the global function).\n\nYou need to implement `SerializeObject()` in the same namespace as the serializing class, or in `BitSerializer`:\n```cpp\nclass TestThirdPartyClass\n{\npublic:\n    TestThirdPartyClass(int x, int y) noexcept\n        : x(x), y(y)\n    { }\n\n    // Example of public property\n    int x;\n\n    // Example of property that is only accessible via a getter/setter\n    [[nodiscard]] int GetY() const noexcept { return y; }\n    void SetY(const int inY) noexcept { this-\u003ey = inY; }\n\nprivate:\n    int y;\n};\n\n// Serializes TestThirdPartyClass.\ntemplate\u003ctypename TArchive\u003e\nvoid SerializeObject(TArchive\u0026 archive, TestThirdPartyClass\u0026 testThirdPartyClass)\n{\n    // Serialize public property\n    archive \u003c\u003c KeyValue(\"x\", testThirdPartyClass.x);\n\n    // Serialize private property\n    if constexpr (TArchive::IsLoading())\n    {\n        int y = 0;\n        archive \u003c\u003c KeyValue(\"y\", y);\n        testThirdPartyClass.SetY(y);\n    }\n    else\n    {\n        const int y = testThirdPartyClass.GetY();\n        archive \u003c\u003c KeyValue(\"y\", y);\n    }\n}\n```\n[See full sample](samples/serialize_third_party_class/serialize_third_party_class.cpp)\n\n### Serializing a class that represents an array\nIn this chapter described how to serialize your own class that represent a list of values (similar to `std::vector`).\nFor this purpose, need to implement a global function `SerializeArray()` in the same namespace as the serializing class, or in `BitSerializer`.\n\nAdditionally, BitSerializer wants to know the number of elements in the list.\nThis is optional for a text archives like JSON, but mandatory for a binary archive like MsgPack since it stores the size prior the array elements.\nThe size of list can be obtained via one of the following ways:\n\n - Global function `size(const CMyArray\u0026)` in the same namespace as the serializing class (highest priority).\n - Standard class method `size()`.\n - By enumerating array elements using iterators (like as for `std::forward_list`).\n\nSo, in case if your class has a different signature for the size getter than `size()`, then you need to implement it as a global function.\n\n\u003e [!WARNING]\n\u003e In the previous version of BitSerializer v0.75, was incorrect detecting internal `size()` method (if it's not in the `std` namespace).\n\nPlease take a look at the following example:\n```cpp\n// Some custom array type\ntemplate \u003ctypename T\u003e\nclass CMyArray\n{\npublic:\n    CMyArray() = default;\n    CMyArray(std::initializer_list\u003cT\u003e initList)\n        : mArray(initList)\n    { }\n\n    [[nodiscard]] size_t GetSize() const noexcept { return mArray.size(); }\n    void Resize(size_t newSize) { mArray.resize(newSize); }\n\n    [[nodiscard]] const T\u0026 At(size_t index) const { return mArray.at(index); }\n    [[nodiscard]] T\u0026 At(size_t index) { return mArray.at(index); }\n\n    T\u0026 PushBack(T\u0026\u0026 value) { return mArray.emplace_back(std::forward\u003cT\u003e(value)); }\n\nprivate:\n    std::vector\u003cT\u003e mArray;\n};\n\n// Returns the size of the CMyArray.\ntemplate \u003cclass T\u003e\nsize_t size(const CMyArray\u003cT\u003e\u0026 cont) noexcept { return cont.GetSize(); }\n\n// Serializes CMyArray.\ntemplate \u003cclass TArchive, class TValue\u003e\nvoid SerializeArray(TArchive\u0026 arrayScope, CMyArray\u003cTValue\u003e\u0026 cont)\n{\n    if constexpr (TArchive::IsLoading())\n    {\n        // Resize container when approximate size is known\n        if (const auto estimatedSize = arrayScope.GetEstimatedSize(); estimatedSize != 0 \u0026\u0026 cont.GetSize() \u003c estimatedSize) {\n            cont.Resize(estimatedSize);\n        }\n\n        // Load\n        size_t loadedItems = 0;\n        for (; !arrayScope.IsEnd(); ++loadedItems)\n        {\n            TValue\u0026 value = (loadedItems \u003c cont.GetSize()) ? cont.At(loadedItems) : cont.PushBack({});\n            Serialize(arrayScope, value);\n        }\n        // Resize container for case when loaded items less than there are or were estimated\n        cont.Resize(loadedItems);\n    }\n    else\n    {\n        for (size_t i = 0; i \u003c cont.GetSize(); ++i)\n        {\n            Serialize(arrayScope, cont.At(i));\n        }\n    }\n}\n```\n[See full sample](samples/serialize_custom_array/serialize_custom_array.cpp)\n\nAdditional recommendations:\n - Don't clear arrays, prefer loading values into existing elements (for better performance).\n - Resize array before loading if estimated size is not zero (but please keep in mind that the actual size may vary).\n - For fixed size arrays, always check the size of the array and the elements actually loaded (throw an exception if they differ).\n - Use [std containers serialization implementation](include/bitserializer/types/std) as examples.\n\n### Serializing custom class representing a string\nMost frameworks/engines have their own implementation of the string type, and most likely you will want to add support for serializing these types.\nBitSerializer allows you to do this in a simple and efficient way by using `std::basic_string_view\u003c\u003e` as an intermediate type (supported any char type).\n\nLet's imagine that you would like to implement serialization of your own `std::string` alternative, which is called `CMyString`.\nFor this purpose you would need two global functions in the same namespace as the serializing class, or in `BitSerializer`:\n```cpp\ntemplate \u003cclass TArchive, typename TKey\u003e\nbool Serialize(TArchive\u0026 archive, TKey\u0026\u0026 key, CMyString\u0026 value);\n\ntemplate \u003cclass TArchive\u003e\nbool Serialize(TArchive\u0026 archive, CMyString\u0026 value);\n```\nThese two functions are necessary for serialization any type with and without **key** into the output archive.\nFor example, object in the JSON format, has named properties, but JSON-array can contain only values.\n\nAdditionally, you will need to implement string conversion methods (internal or global), please read more about ([convert sub-module](docs/bitserializer_convert.md)).\nThey will add support for using string types as keys, for example it will allow serialization of `std::map\u003cCMyString, int\u003e` where `CMyString` is used as a key.\n\nThis all looks a bit more complicated than serializing an object, but the code is pretty simple, please have a look at the example below:\n```cpp\n// Some custom string type\nclass CMyString\n{\npublic:\n    CMyString() = default;\n    CMyString(const char* str) : mString(str) { }\n\n    bool operator\u003c(const CMyString\u0026 rhs) const { return this-\u003emString \u003c rhs.mString; }\n\n    const char* data() const noexcept { return mString.data(); }\n    size_t size() const noexcept { return mString.size(); }\n\n    // Required methods for conversion from/to std::string (can be implemented as external functions)\n    std::string ToString() const { return mString; }\n    void FromString(std::string_view str) { mString = str; }\n\nprivate:\n    std::string mString;\n};\n\n// Serializes CMyString with key\ntemplate \u003cclass TArchive, typename TKey\u003e\nbool Serialize(TArchive\u0026 archive, TKey\u0026\u0026 key, CMyString\u0026 value)\n{\n    if constexpr (TArchive::IsLoading())\n    {\n        std::string_view stringView;\n        if (Detail::SerializeString(archive, std::forward\u003cTKey\u003e(key), stringView))\n        {\n            value.FromString(stringView);\n            return true;\n        }\n    }\n    else\n    {\n        std::string_view stringView(value.data(), value.size());\n        return Detail::SerializeString(archive, std::forward\u003cTKey\u003e(key), stringView);\n    }\n    return false;\n}\n\n// Serializes CMyString without key\ntemplate \u003cclass TArchive\u003e\nbool Serialize(TArchive\u0026 archive, CMyString\u0026 value)\n{\n    if constexpr (TArchive::IsLoading())\n    {\n        std::string_view stringView;\n        if (Detail::SerializeString(archive, stringView))\n        {\n            value.FromString(stringView);\n            return true;\n        }\n        return false;\n    }\n    else\n    {\n        std::string_view stringView(value.data(), value.size());\n        return Detail::SerializeString(archive, stringView);\n    }\n}\n\nint main()\n{\n    // Save list of custom strings to JSON\n    std::vector\u003cCMyString\u003e srcStrList = { \"Red\", \"Green\", \"Blue\" };\n    std::string jsonResult;\n    SerializationOptions serializationOptions;\n    serializationOptions.formatOptions.enableFormat = true;\n    BitSerializer::SaveObject\u003cJsonArchive\u003e(srcStrList, jsonResult, serializationOptions);\n    std::cout \u003c\u003c \"Saved JSON: \" \u003c\u003c jsonResult \u003c\u003c std::endl;\n\n    // Load JSON-object to std::map based on custom strings\n    std::map\u003cCMyString, CMyString\u003e mapResult;\n    const std::string srcJson = R\"({ \"Background\": \"Blue\", \"PenColor\": \"White\", \"PenSize\": \"3\", \"PenOpacity\": \"50\" })\";\n    BitSerializer::LoadObject\u003cJsonArchive\u003e(mapResult, srcJson);\n    std::cout \u003c\u003c std::endl \u003c\u003c \"Loaded map: \" \u003c\u003c std::endl;\n    for (const auto\u0026 val : mapResult)\n    {\n        std::cout \u003c\u003c \"\\t\" \u003c\u003c val.first.ToString() \u003c\u003c \": \" \u003c\u003c val.second.ToString() \u003c\u003c std::endl;\n    }\n\n    return 0;\n}\n```\n[See full sample](samples/serialize_custom_string/serialize_custom_string.cpp)\n\n### Serializing enum types\nEnum types can be serialized as integers or as strings, as you prefer.\nBy default, they serializing as strings, to serialize as integers, use the `EnumAsBin` wrapper:\n```cpp\narchive \u003c\u003c KeyValue(\"EnumValue\", EnumAsBin(enumValue));\n```\nTo be able to serialize `enum` types as string, you need to register a map with string equivalents in the your HEADER file.\n```cpp\n// file HttpMethods.h\n#pragma once\n#include \"bitserializer\\convert.h\"\n\nenum class HttpMethod {\n    Delete = 1,\n    Get = 2,\n    Head = 3\n};\n\nREGISTER_ENUM(HttpMethod, {\n    { HttpMethod::Delete,   \"delete\" },\n    { HttpMethod::Get,      \"get\" },\n    { HttpMethod::Head,     \"head\" }\n})\n\n// Optionally, you can declare stream operators (`\u003c\u003c` and `\u003e\u003e`) for the registered enum type\nDECLARE_ENUM_STREAM_OPS(HttpMethod)\n```\n\n### Serializing to multiple formats\nOne of the advantages of BitSerializer is the ability to serialize into multiple formats through a single interface. The following example shows how to save an object to JSON and XML:\n```cpp\nclass CPoint\n{\npublic:\n    CPoint(int x, int y)\n        : x(x), y(y)\n    { }\n\n    template \u003cclass TArchive\u003e\n    void Serialize(TArchive\u0026 archive)\n    {\n        archive \u003c\u003c KeyValue(\"x\", x);\n        archive \u003c\u003c KeyValue(\"y\", y);\n    }\n\n    int x, y;\n};\n\nint main()\n{\n    auto testObj = CPoint(100, 200);\n\n    const auto jsonResult = BitSerializer::SaveObject\u003cJsonArchive\u003e(testObj);\n    std::cout \u003c\u003c \"JSON: \" \u003c\u003c jsonResult \u003c\u003c std::endl;\n\n    const auto xmlResult = BitSerializer::SaveObject\u003cXmlArchive\u003e(testObj);\n    std::cout \u003c\u003c \"XML: \" \u003c\u003c xmlResult \u003c\u003c std::endl;\n    return 0;\n}\n```\nThe output result of this code:\n```\nJSON: {\"x\":100,\"y\":200}\nXML: \u003c?xml version=\"1.0\"?\u003e\u003croot\u003e\u003cx\u003e100\u003c/x\u003e\u003cy\u003e200\u003c/y\u003e\u003c/root\u003e\n```\nThe serialization code differs only in the template parameter -  **JsonArchive** and **XmlArchive**.\nBut here are some moments which need comments. As you can see in the XML was created node with name \"root\". This is auto generated name when it was not specified explicitly for root node. The library does this just to smooth out differences in the structure of formats. But you are free to set name of root node if needed:\n```cpp\nconst auto xmlResult = BitSerializer::SaveObject\u003cXmlArchive\u003e(KeyValue(\"Point\", testObj));\n```\nThe second thing which you would like to customize is default structure of output XML. In this example it does not looks good from XML perspective, as it has specific element for this purpose which known as \"attribute\". The BitSerializer also allow to customize the serialization behavior for different formats:\n```cpp\n    template \u003cclass TArchive\u003e\n    void Serialize(TArchive\u0026 archive)\n    {\n        // Serialize as attributes when archive type is XML\n        if constexpr (TArchive::archive_type == ArchiveType::Xml)\n        {\n            archive \u003c\u003c AttributeValue(\"x\", x);\n            archive \u003c\u003c AttributeValue(\"y\", y);\n        }\n        else\n        {\n            archive \u003c\u003c KeyValue(\"x\", x);\n            archive \u003c\u003c KeyValue(\"y\", y);\n        }\n    }\n```\nWith these changes, the result of this code will look like this:\n```\nJSON: {\"x\":100,\"y\":200}\nXML: \u003c?xml version=\"1.0\"?\u003e\u003cPoint x=\"100\" y=\"200\"/\u003e\n```\n[See full sample](samples/multiformat_customization/multiformat_customization.cpp)\n\n### Serialization STD types\nBitSerializer has built-in serialization for all STD containers and most other commonly used types. For add support of required STD type just need to include related header file.\n| Types  | Header |\n| ------ | ------ |\n| std::basic_string\u003c\u003e, std::pmr::basic_string\u003c\u003e | Part of the basic package |\n| std::byte | Part of the basic package |\n| std::atomic | #include \"bitserializer/types/std/atomic.h\" |\n| std::array | #include \"bitserializer/types/std/array.h\" |\n| std::vector, std::pmr::vector | #include \"bitserializer/types/std/vector.h\" |\n| std::deque, std::pmr::deque | #include \"bitserializer/types/std/deque.h\" |\n| std::bitset | #include \"bitserializer/types/std/bitset.h\" |\n| std::list, std::pmr::list | #include \"bitserializer/types/std/list.h\" |\n| std::forward_list, std::pmr::forward_list | #include \"bitserializer/types/std/forward_list.h\" |\n| std::queue, std::priority_queue | #include \"bitserializer/types/std/queue.h\" |\n| std::stack | #include \"bitserializer/types/std/stack.h\" |\n| std::set, std::multiset, std::pmr::set, std::pmr::multiset | #include \"bitserializer/types/std/set.h\" |\n| std::unordered_set, std::unordered_multiset,\u003cbr\u003estd::pmr::unordered_set, std::pmr::unordered_multiset | #include \"bitserializer/types/std/unordered_set.h\" |\n| std::map, std::multimap, std::pmr::map, std::pmr::multimap | #include \"bitserializer/types/std/map.h\" |\n| std::unordered_map, std::unordered_multimap,\u003cbr\u003estd::pmr::unordered_map, std::pmr::unordered_multimap | #include \"bitserializer/types/std/unordered_map.h\" |\n| std::valarray | #include \"bitserializer/types/std/valarray.h\" |\n| std::pair | #include \"bitserializer/types/std/pair.h\" |\n| std::tuple | #include \"bitserializer/types/std/tuple.h\" |\n| std::optional | #include \"bitserializer/types/std/optional.h\" |\n| std::unique_ptr, std::shared_ptr | #include \"bitserializer/types/std/memory.h\" |\n| std::chrono::time_point, std::chrono::time_point | #include \"bitserializer/types/std/chrono.h\" |\n| std::time_t | #include \"bitserializer/types/std/ctime.h\" |\n| std::filesystem::path | #include \"bitserializer/types/std/filesystem.h\" |\n\nFew words about serialization smart pointers. There is no any system footprints in output archive, for example empty smart pointer will be serialized as `NULL` type in JSON or in any other suitable way for other archive types. When an object is loading into an empty smart pointer, it will be created, and vice versa, when the loaded object is `NULL` or does not exist, the smart pointer will be reset. Polymorphism are not supported you should take care about such types by yourself.\n\n### Specifics of serialization STD map\nBitSerializer does not add any system information when saving the map, for example serialization to JSON would look like this:\n```cpp\nstd::map\u003cstd::string, int\u003e testMap = \n    { { \"One\", 1 }, { \"Two\", 2 }, { \"Three\", 3 }, { \"Four\", 4 }, { \"Five\", 5 } };\nauto jsonResult = BitSerializer::SaveObject\u003cJsonArchive\u003e(testMap);\n```\nReturns result\n```json\n{\n    \"Five\": 5,\n    \"Four\": 4,\n    \"One\": 1,\n    \"Three\": 3,\n    \"Two\": 2\n}\n```\n\nBelow is a more complex example, where loading a vector of maps from JSON.\n```json\n[{\n    \"One\": 1,\n    \"Three\": 3,\n    \"Two\": 2\n}, {\n    \"Five\": 5,\n    \"Four\": 4\n}]\n```\nCode:\n```cpp\nstd::vector\u003cstd::map\u003cstd::string, int\u003e\u003e testVectorOfMaps;\nconst std::string inputJson = R\"([{\"One\":1,\"Three\":3,\"Two\":2},{\"Five\":5,\"Four\":4}])\";\nBitSerializer::LoadObject\u003cJsonArchive\u003e(testVectorOfMaps, inputJson);\n```\n\nSince all of the most well-known text formats (such as JSON) allow only text keys, BitSerializer attempts to convert the map key to a string (except binary formats like MsgPack).\nOut of the box, the library supports all the fundamental types (e.g. `bool`, `int`, `float`) as well as some of the `std` ones (`filesystem::path`, `chrono::timepoint`, etc), but if you want to use your own type as the key, you need to implement the conversion to a string. There are several options with internal and external functions, see details [here](docs\\bitserializer_convert.md). For example, you can implement two internal methods in your type:\n```cpp\nclass YourCustomKey\n{\n    std::string ToString() const { }\n    void FromString(std::string_view str)\n}\n```\n\n### Serialization date and time\nThe ISO 8601 standard was chosen as the representation for the date, time and duration for text type of archives (JSON, XML, YAML, CSV). The MsgPack archive has its own compact time format. For enable serialization of the `std::chrono` and `time_t`,  just include these headers:\n```cpp\n#include \"bitserializer/types/std/chrono.h\"\n#include \"bitserializer/types/std/ctime.h\"\n```\n\nThe following table contains all supported types with examples of string representations:\n\n| Type | Format | Examples | References |\n| ------ | ------ | ------ | ------ |\n| `std::time_t` | YYYY-MM-DDThh:mm:ssZ | 1677-09-21T00:12:44Z\u003cbr\u003e2262-04-11T23:47:16Z | [ISO 8601/UTC](https://en.wikipedia.org/wiki/ISO_8601) |\n| `chrono::time_point` | [±]YYYY-MM-DDThh:mm:ss[.SSS]Z | 1872-01-01T04:55:32.021Z\u003cbr\u003e2262-04-11T23:47:16Z\u003cbr\u003e9999-12-31T23:59:59.999Z\u003cbr\u003e+12376-01-20T00:00:00Z\u003cbr\u003e-1241-06-23T00:00:00Z | [ISO 8601/UTC](https://en.wikipedia.org/wiki/ISO_8601)  |\n| `chrono::duration` | [±]PnWnDTnHnMnS | P125DT55M41S\u003cbr\u003ePT10H20.346S\u003cbr\u003eP10DT25M\u003cbr\u003eP35W5D | [ISO 8601/Duration](https://en.wikipedia.org/wiki/ISO_8601#Durations)  |\n\nTime point notes:\n- Only UTC representation is supported, fractions of a second are optional ([±]YYYY-MM-DDThh:mm:ss[.SSS]Z).\n- ISO-8601 doesn't specify precision for fractions of second, BitSerializer supports up to 9 digits, which is enough for values with nanosecond precision.\n- Both decimal separators (dot and comma) are supported for fractions of a second.\n- According to standard, to represent years before 0000 or after 9999 uses additional '-' or '+' sign.\n- The date range depends on the `std::chrono::duration` type, for example implementation of `system_clock` on Linux has range **1678...2262 years**.\n- Keep in mind that `std::chrono::system_clock` has time point with different duration on Windows and Linux, prefer to store time in custom `time_point` if you need predictable range (e.g. `time_point\u003csystem_clock, milliseconds\u003e`).\n- According to the C++20 standard, the EPOCH date for `system_clock` types is considered as *1970-01-01 00:00:00 UTC* excluding leap seconds.\n- For avoid mistakes, time points with **steady_clock**  type are not allowed due to floating EPOCH.\n- Allowed rounding only fractions of seconds, in all other cases an exception is thrown (according to `OverflowNumberPolicy`).\n\nDuration notes:\n- Supported a sign character at the start of the string (ISO 8601-2 extension).\n- Durations which contains years, month, or with base UTC (2003-02-15T00:00:00Z/P2M) are not allowed.\n- The decimal fraction supported only for seconds part, maximum 9 digits.\n- Both decimal separators (dot and comma) are supported for fractions of a second.\n- Allowed rounding only fractions of seconds, in all other cases an exception is thrown (according to `OverflowNumberPolicy`).\n\nSince `std::time_t` is equal to `int64_t`, need to use special wrapper `CTimeRef`, otherwise time will be serialized as number.\n```cpp\ntemplate \u003cclass TArchive\u003e\nvoid Serialize(TArchive\u0026 archive)\n{\n    archive \u003c\u003c KeyValue(\"Time\", CTimeRef(timeValue));\n}\n```\n\n### Conditional loading and versioning\nThe functional style of serialization used in BitSerializer has one advantage over the declarative one - you can write branches depending on the data.\nTo check the current serialization mode, use two static methods - `IsLoading()` and `IsSaving()`. As they are «constexpr», you will not have any overhead.\n```cpp\nclass Foo\n{\npublic:\n    template \u003cclass TArchive\u003e\n    void Serialize(TArchive\u0026 archive)\n    {\n        if constexpr (TArchive::IsLoading()) {\n            // Code which executes in loading mode\n        }\n        else {\n            // Code which executes in saving mode\n        }\n    }\n}\n```\nThis can be most useful when you need to support multiple versions of a model. By default, library does not add any system fields (like as a version of object), but it's not difficult to add version when you will need:\n```cpp\n// Old version of test object (no needs to keep old models, just as example)\nstruct TestUserV1\n{\n    std::string name;           // Deprecated, need to split to first and last name\n    uint8_t age{};\n    uint32_t lastOrderId{};     // Deprecated, need to remove\n\n    template \u003cclass TArchive\u003e\n    void Serialize(TArchive\u0026 archive)\n    {\n        archive \u003c\u003c KeyValue(\"name\", name, Required());\n        archive \u003c\u003c KeyValue(\"age\", age);\n        archive \u003c\u003c KeyValue(\"lastOrderId\", lastOrderId);\n    }\n};\n\n// Actual model\nstruct TestUser\n{\n    // Introduce version field\n    static constexpr int16_t CurrentVersion = 1;\n\n    std::string firstName;\n    std::string lastName;\n    uint8_t age{};\n    std::string country;\n\n    template \u003cclass TArchive\u003e\n    void Serialize(TArchive\u0026 archive)\n    {\n        // Load 'version' field if exists\n        int16_t version = TArchive::IsSaving() ? CurrentVersion : 0;\n        archive \u003c\u003c KeyValue(\"version\", version);\n\n        if constexpr (TArchive::IsLoading())\n        {\n            if (version == 0)\n            {\n                // Import name from old format\n                std::string name;\n                archive \u003c\u003c KeyValue(\"name\", name, Required());\n                const auto spacePos = name.find(' ');\n                firstName = name.substr(0, spacePos);\n                lastName = spacePos != std::string::npos ? name.substr(spacePos + 1) : \"\";\n            }\n            else\n            {\n                archive \u003c\u003c KeyValue(\"firstName\", firstName, Required());\n                archive \u003c\u003c KeyValue(\"lastName\", lastName, Required());\n            }\n        }\n        archive \u003c\u003c KeyValue(\"age\", age);\n        archive \u003c\u003c KeyValue(\"country\", country);\n    }\n};\n\nint main()\n{\n    // Save old version\n    std::vector\u003cTestUserV1\u003e oldUsers {\n        { \"John Smith\", 35, 1254 },\n        { \"Emily Roberts\", 27, 4546 },\n        { \"James Murphy\", 32, 10653 }\n    };\n    const auto archive = BitSerializer::SaveObject\u003cMsgPackArchive\u003e(oldUsers);\n\n    // Loading with import to new version\n    std::vector\u003cTestUser\u003e newUsers;\n    BitSerializer::LoadObject\u003cMsgPackArchive\u003e(newUsers, archive);\n\n    return 0;\n}\n```\n[See full sample](samples/versioning/versioning.cpp)\n\n### Serialization to streams and files\nAll archives in the BitSerializer support streams as well as serialization to files. In comparison to serialization to `std::string`, streams/files also supports UTF encodings.\nBitSerializer can detect encoding of input stream by BOM ([Byte order mark](https://en.wikipedia.org/wiki/Byte_order_mark)) and via data analysis, but last is only supported by RapidJson, PugiXml and CSV archives. The output encoding and BOM is configurable via `SerializationOptions`.\nThe following example shows how to save/load to `std::stream`:\n```cpp\nclass CPoint\n{\npublic:\n    CPoint() = default;\n    CPoint(int x, int y)\n        : x(x), y(y)\n    { }\n\n    template \u003cclass TArchive\u003e\n    void Serialize(TArchive\u0026 archive)\n    {\n        archive \u003c\u003c KeyValue(\"x\", x);\n        archive \u003c\u003c KeyValue(\"y\", y);\n    }\n\n    int x = 0, y = 0;\n};\n\nint main()\n{\n    auto testObj = CPoint(100, 200);\n\n    SerializationOptions serializationOptions;\n    serializationOptions.streamOptions.encoding = Convert::Utf::UtfType::Utf8;\n    serializationOptions.streamOptions.writeBom = false;\n\n    // Save to string stream\n    std::stringstream outputStream;\n    BitSerializer::SaveObject\u003cJsonArchive\u003e(testObj, outputStream, serializationOptions);\n    std::cout \u003c\u003c outputStream.str() \u003c\u003c std::endl;\n\n    // Load from string stream\n    CPoint loadedObj;\n    BitSerializer::LoadObject\u003cJsonArchive\u003e(loadedObj, outputStream);\n\n    assert(loadedObj.x == testObj.x \u0026\u0026 loadedObj.y == testObj.y);\n    return 0;\n}\n```\n[See full sample](samples/serialize_to_stream/serialize_to_stream.cpp)\n\nFor save/load to files, BitSerializer provides the following functions (which are just wrappers of serialization methods to streams):\n```cpp\ntemplate \u003ctypename TArchive, typename T, typename TString\u003e\nBitSerializer::SaveObjectToFile\u003cTArchive\u003e(T\u0026\u0026 object, TString\u0026\u0026 path, const SerializationOptions\u0026 serializationOptions = DefaultOptions, bool overwrite = false);\n\ntemplate \u003ctypename TArchive, typename T, typename TString\u003e\nBitSerializer::LoadObjectFromFile\u003cTArchive\u003e(T\u0026\u0026 object, TString\u0026\u0026 path, const SerializationOptions\u0026 serializationOptions = DefaultOptions);\n```\n\n\u003e [!NOTE]\n\u003e Note that the stream implementation must support the `seekg()` operation to load fields non-linearly.\n\n### Error handling\nFirst, let's list what are considered as errors and will throw exception:\n\n - Syntax errors in the input source (e.g. JSON)\n - When one or more user's validation rules were not passed\n - When a type from the archive (source format, like JSON) does not match to the target value (can be configured via `MismatchedTypesPolicy`)\n - When an enum type is not registered or its value is invalid\n - When size of target type is not enough for loading value (can be configured via `OverflowNumberPolicy`)\n - When target array with fixed size does not match the number of loading items\n - Invalid configuration in the `SerializationOptions`\n - Input/output file can't be opened for read/write\n - UTF encoding/decoding errors (can be configured via `UtfEncodingErrorPolicy`)\n - Unsupported UTF encoding\n\nBy default, any missed field in the input format (e.g. JSON) is not treated as an error, you can specify a default value using the `Fallback()` refiner or add the `Required()` validator if the field is mandatory.\n\u003e [!NOTE]\n\u003e In the previously released v0.80, loading a `null` (e.g. \"myValue\": null) value into an object or array (e.g. `std::optional\u003cCMyClass\u003e`) would throw an exception with error code `MismatchedTypes` (all archives except MsgPack and CSV).\n\nYou can handle `std::exception` just for log errors, but if you need to provide more detailed information to the user, you may need to handle the following exceptions:\n\n - `SerializationException` - base BitSerializer exception, contains `SerializationErrorCode`\n - `ParsingException` - contains information about line number or offset (depending on format type)\n - `ValidationException` - contains map of fields with validation errors\n\n```cpp\ntry\n{\n    int testInt;\n    BitSerializer::LoadObject\u003cJsonArchive\u003e(testInt, L\"10 ?\");\n}\ncatch (const BitSerializer::ParsingException\u0026 ex)\n{\n    // Parsing error: Malformed token\n    std::string message = ex.what();\n    size_t line = ex.Line;\n    size_t offset = ex.Offset;\n}\ncatch (const BitSerializer::ValidationException\u0026 ex)\n{\n    // Handle validation errors\n    const auto\u0026 validationErrors = ex.GetValidationErrors();\n}\ncatch (const std::exception\u0026 ex)\n{\n    // Handle any other errors\n    std::string message = ex.what();\n}\n```\n\n### Validation of deserialized values\nBitSerializer provides a flexible validation system that allows you to apply an arbitrary number of validation rules to named values.\nThe syntax is straightforward:\n```cpp\narchive \u003c\u003c KeyValue(\"testFloat\", testFloat, Required(), Validate::Range(-1.0f, 1.0f));\n```\nValidation errors are collected during deserialization and thrown as a `ValidationException` at the end of the deserialization. To handle validation errors:\n```cpp\ntry {\n    BitSerializer::LoadObject\u003cJsonArchive\u003e(user, json);\n}\ncatch (BitSerializer::ValidationException\u0026 ex) {\n    const auto\u0026 validationErrors = ex.GetValidationErrors();\n    // Process errors...\n}\n```\nBy default, the number of errors is unlimited, but you can configure this using `maxValidationErrors` in `SerializationOptions`.\nThe validation error map can be obtained by calling the `GetValidationErrors()` method from the exception object, it contains paths to fields with errors lists.\n\nThe default error message can be overridden (you can also pass string ID for further localization):\n```cpp\narchive \u003c\u003c KeyValue(\"Age\", mAge, Required(\"Age is required\"), Validate::Range(0, 150, \"Age must be between 0 and 150 (inclusive)\"));\n```\n\nThe following validators are available out-of-the-box:\n\n| Signature           | Description   |\n| ------------------- | --------------------- |\n| `Required(errorMessage = nullptr)`         | Ensures the field is present in the source data |\n| `Range(min, max, errorMessage = nullptr)`  | Validates value range for types that have `\u003c` and `\u003e` operators (for example, these could be types from `std::chrono`) |\n| `MinSize(minSize, errorMessage = nullptr)` | Ensures containers or strings meet minimum size requirements |\n| `MaxSize(maxSize, errorMessage = nullptr)` | Ensures containers or strings do not exceed maximum size |\n| `Email(errorMessage = nullptr)`            | Validates email format according to RFC standards (excluding quoted parts, comments, SMTPUTF8, and IP domains) |\n| `PhoneNumber(minDigits = 7, maxDigits = 15, isPlusRequired = true, errorMessage = nullptr)` | Validates phone numbers with configurable digit ranges and format requirements |\n\nAll validators are declared in the `BitSerializer::Validate` namespace, except `Required` which also has alias in the `BitSerializer`.\n\u003e [!NOTE]\n\u003e In the previously released v0.75, all validators were declared in the BitSerializer namespace.\n\nUsage example:\n```cpp\nusing namespace BitSerializer;\nusing JsonArchive = BitSerializer::Json::RapidJson::JsonArchive;\n\nclass UserModel\n{\npublic:\n    template \u003cclass TArchive\u003e\n    void Serialize(TArchive\u0026 archive)\n    {\n        archive \u003c\u003c KeyValue(\"Id\", mId, Required());\n        archive \u003c\u003c KeyValue(\"Age\", mAge, Required(\"Age is required\"), Validate::Range(0, 150, \"Age must be between 0 and 150 (inclusive)\"));\n        archive \u003c\u003c KeyValue(\"FirstName\", mFirstName, Required(), Validate::MaxSize(16));\n        archive \u003c\u003c KeyValue(\"LastName\", mLastName, Required(), Validate::MaxSize(16));\n        archive \u003c\u003c KeyValue(\"Email\", mEmail, Required(), Validate::Email());\n        // Custom validation with lambda\n        archive \u003c\u003c KeyValue(\"NickName\", mNickName, [](const std::string\u0026 value, bool isLoaded) -\u003e std::optional\u003cstd::string\u003e\n        {\n            // Loaded string should has text without spaces or should be NULL\n            if (!isLoaded || value.find_first_of(' ') == std::string::npos) {\n                return std::nullopt;\n            }\n            return \"Nickname must not contain spaces\";\n        });\n    }\n\nprivate:\n    uint64_t mId = 0;\n    uint16_t mAge = 0;\n    std::string mFirstName;\n    std::string mLastName;\n    std::string mEmail;\n    std::string mNickName;\n};\n\nint main()\n{\n    UserModel user;\n    const char* json = R\"({ \"Id\": 12420, \"Age\": 500, \"FirstName\": \"John Smith-Cotatonovich\", \"NickName\": \"Smith 2000\", \"Email\": \"smith 2000@mail.com\" })\";\n    try\n    {\n        BitSerializer::LoadObject\u003cJsonArchive\u003e(user, json);\n    }\n    catch (BitSerializer::ValidationException\u0026 ex)\n    {\n        const auto\u0026 validationErrors = ex.GetValidationErrors();\n        std::cout \u003c\u003c \"Validation errors: \" \u003c\u003c std::endl;\n        for (const auto\u0026 keyErrors : validationErrors)\n        {\n            std::cout \u003c\u003c \"Path: \" \u003c\u003c keyErrors.first \u003c\u003c std::endl;\n            for (const auto\u0026 err : keyErrors.second)\n            {\n                std::cout \u003c\u003c \"\\t\" \u003c\u003c err \u003c\u003c std::endl;\n            }\n        }\n    }\n    catch (std::exception\u0026 ex)\n    {\n        std::cout \u003c\u003c ex.what();\n    }\n\n    return EXIT_SUCCESS;\n}\n```\n[See full sample](samples/validation/validation.cpp)\n\nExecution output:\n```text\nValidation errors:\nPath: /Age\n        Age must be between 0 and 150 (inclusive)\nPath: /Email\n        Invalid email format\nPath: /FirstName\n        Size must not exceed 16\nPath: /LastName\n        Value is required\nPath: /NickName\n        Nickname must not contain spaces\n```\nReturned paths for invalid values is dependent to archive type, usually it's JSON Pointer (RFC 6901).\n\n### Post-load data refinement\n\u003e [!NOTE]\n\u003e New feature (not supported in the latest released version of BitSerializer v0.80, please use the master branch).\n\nIn addition to validators, BitSerializer also has the ability to transform deserialized values using specialized processors called \"Refiners\".\nThis feature is designed to ensure data quality and consistency by cleaning, normalizing, and providing default values for missing data.\nRefiners are applied to fields alongside validators using the familiar `KeyValue` syntax:\n```cpp\narchive \u003c\u003c KeyValue(\"Username\", mUsername,\n    Required(),\n    Refine::TrimWhitespace(),\n    Refine::ToLowerCase(),\n    Validate::MaxSize(32));\n```\n\nThe order of validators and refiners is crucial - they are processed from left to right.\nRefiners should typically be placed before validators that depend on the refined data:\n```cpp\n// ✅ Correct: Trim first, then validate\narchive \u003c\u003c KeyValue(\"Email\", mEmail,\n    Required(),\n    Refine::TrimWhitespace(),\n    Validate::Email());\n\n// ❌ Incorrect: Validate before trimming\narchive \u003c\u003c KeyValue(\"ApiEndpoint\", mApiEndpoint,\n    Required(),\n    Validate::Email(),\n    Refine::TrimWhitespace());  // Validation may fail due to trailing whitespace\n```\n\nAvailable refiners:\n\n| Refiner                  | Description   |\n| ------------------------ | ------------------------ |\n| `Fallback(defaultValue)` | Provides a default value when the field is missing or null |\n| `TrimWhitespace()`       | Removes leading and trailing whitespace from strings |\n| `ToLowerCase()`          | Converts ASCII letters to lowercase |\n| `ToUpperCase()`          | Converts ASCII letters to uppercase |\n\nAll refiners are declared in the `BitSerializer::Refine` namespace, except `Fallback` which also has alias in the `BitSerializer`.\nIt's quite easy to write your own refiner or use a lambda function (similar to validators).\n\n### Compile-time format validation\nBitSerializer performs format-specific validation during compilation, catching serialization errors before runtime by verifying your code against the actual constraints of the target output format. This will help you get immediate feedback, ensuring that your serialized data always conforms to the target format specification.\n```cpp\nint testNumber = 12345;\nstd::string outputData;\n\n// ✅ Correct: Json supports serialization number as root element\nBitSerializer::SaveObject\u003cJsonArchive\u003e(testNumber, outputData);\n\n// ❌ Invalid: CSV only supports array of objects, attempting to serialize a number will not compile:\n//   static_assert failed:\n//      'BitSerializer. The archive doesn't support serialize fundamental type without key on this level.'\nBitSerializer::SaveObject\u003cCsvArchive\u003e(testNumber, outputData);\n```\n\n### What else to read\nEach of the supported archives has its own page with details (installation, features, samples, etc.):\n- [JSON archive \"bitserializer-rapidjson\"](docs/bitserializer_rapidjson.md)\n- [XML archive \"bitserializer-pugixml\"](docs/bitserializer_pugixml.md)\n- [YAML archive \"bitserializer-rapidyaml\"](docs/bitserializer_rapidyaml.md)\n- [CSV archive \"bitserializer-csv\"](docs/bitserializer_csv.md)\n- [MsgPack archive \"bitserializer-msgpack\"](docs/bitserializer_msgpack.md)\n\nAdditionally, you may want to use the [string conversion submodule](docs/bitserializer_convert.md).\n\n### Thanks\n- Artsiom Marozau for developing an archive with support YAML.\n- Andrey Mazhyrau for help with cmake scripts, fix GCC and Linux related issues.\n- Alexander Stepaniuk for support and participation in technical discussions.\n- Evgeniy Gorbachov for help with implementation STD types serialization.\n- Mateusz Pusz for code review and useful advices.\n\n----\nMIT, Copyright (C) 2018-2025 by Pavel Kisliak, made in Belarus 🇧🇾\n","funding_links":[],"categories":["Serialization","Recently Updated"],"sub_categories":["[Who Wants to Be a Millionare](https://www.boardgamecapital.com/who-wants-to-be-a-millionaire-rules.htm)"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FPavelKisliak%2FBitSerializer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FPavelKisliak%2FBitSerializer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FPavelKisliak%2FBitSerializer/lists"}