Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
!package.xml
*.yml
*.yaml
*.yas

!vcpkg.json
!.github/**/*.yml
Expand Down
17 changes: 16 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ option(REFLECTCPP_XML "Enable XML support" ${REFLECTCPP_ALL_FORMATS})
option(REFLECTCPP_TOML "Enable TOML support" ${REFLECTCPP_ALL_FORMATS})
option(REFLECTCPP_UBJSON "Enable UBJSON support" ${REFLECTCPP_ALL_FORMATS})
option(REFLECTCPP_YAML "Enable YAML support" ${REFLECTCPP_ALL_FORMATS})
option(REFLECTCPP_YAS "Enable yas support" ${REFLECTCPP_ALL_FORMATS})
option(REFLECTCPP_BOOST_SERIALIZATION "Enable Boost.Serialization support" ${REFLECTCPP_ALL_FORMATS})

option(REFLECTCPP_BUILD_BENCHMARKS "Build benchmarks" OFF)
Expand Down Expand Up @@ -58,6 +59,7 @@ if(REFLECTCPP_BUILD_BENCHMARKS)
set(REFLECTCPP_TOML ON CACHE BOOL "" FORCE)
set(REFLECTCPP_UBJSON ON CACHE BOOL "" FORCE)
set(REFLECTCPP_YAML ON CACHE BOOL "" FORCE)
set(REFLECTCPP_YAS ON CACHE BOOL "" FORCE)
set(REFLECTCPP_BOOST_SERIALIZATION ON CACHE BOOL "" FORCE)
endif()

Expand All @@ -79,7 +81,8 @@ if (
REFLECTCPP_XML OR
REFLECTCPP_TOML OR
REFLECTCPP_UBJSON OR
REFLECTCPP_YAML
REFLECTCPP_YAML OR
REFLECTCPP_YAS
)
# enable vcpkg per default if features other than JSON are required
set(REFLECTCPP_USE_VCPKG_DEFAULT ON)
Expand Down Expand Up @@ -166,6 +169,10 @@ if (REFLECTCPP_USE_VCPKG)
list(APPEND VCPKG_MANIFEST_FEATURES "yaml")
endif()

if (REFLECTCPP_YAS OR REFLECTCPP_CHECK_HEADERS)
list(APPEND VCPKG_MANIFEST_FEATURES "yas")
endif()

set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file")
endif ()

Expand Down Expand Up @@ -438,6 +445,14 @@ if (REFLECTCPP_BOOST_SERIALIZATION OR REFLECTCPP_CHECK_HEADERS)
target_link_libraries(reflectcpp PUBLIC Boost::serialization)
endif ()

if (REFLECTCPP_YAS OR REFLECTCPP_CHECK_HEADERS)
list(APPEND REFLECT_CPP_SOURCES
src/reflectcpp_yas.cpp
)
find_path(YAS_INCLUDE_DIRS "yas/abseil_types.hpp")
target_include_directories(reflectcpp PRIVATE ${YAS_INCLUDE_DIRS})
endif ()

set_target_properties(reflectcpp PROPERTIES LINKER_LANGUAGE CXX)
target_sources(reflectcpp PRIVATE ${REFLECT_CPP_SOURCES})
target_precompile_headers(reflectcpp PRIVATE [["rfl.hpp"]] <iostream> <string> <functional>)
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ The following table lists the serialization formats currently supported by refle
| UBJSON | [jsoncons](https://github.com/danielaparker/jsoncons)| >= 0.176.0 | BSL 1.0 | JSON-like binary format |
| XML | [pugixml](https://github.com/zeux/pugixml) | >= 1.14 | MIT | Textual format used in many legacy projects |
| YAML | [yaml-cpp](https://github.com/jbeder/yaml-cpp) | >= 0.8.0 | MIT | Textual format with an emphasis on readability |
| yas | [yas](https://github.com/niXman/yas) | >= 7.1.0 | BSL 1.0 | Very fast and compact serialization library |

Support for more serialization formats is in development. Refer to the [issues](https://github.com/getml/reflect-cpp/issues) for details.

Expand Down Expand Up @@ -163,6 +164,7 @@ rfl::msgpack::write(homer);
rfl::toml::write(homer);
rfl::ubjson::write(homer);
rfl::xml::write(homer);
rfl::yas::write(homer);

rfl::avro::read<Person>(avro_bytes);
rfl::bson::read<Person>(bson_bytes);
Expand All @@ -174,6 +176,7 @@ rfl::msgpack::read<Person>(msgpack_bytes);
rfl::toml::read<Person>(toml_string);
rfl::ubjson::read<Person>(ubjson_bytes);
rfl::xml::read<Person>(xml_string);
rfl::yas::read<Person>(yas_bytes);
```

### More Comprehensive Example
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ In summary, it is fair to say that reflect-cpp is among the fastest JSON librari

### How the different formats supported by reflect-cpp compare to each other

- flexbuffers and msgpack are the fastest two formats supported by reflect-cpp. They are particularly fast on datasets with a lot of numerical data, such as *canada*. flexbuffers is usually the fastest at reading data and msgpack is the fastest at writing.
- flexbuffers, msgpack and yas are the fastest three formats supported by reflect-cpp. They are particularly fast on datasets with a lot of numerical data, such as *canada*. flexbuffers is usually the fastest at reading data and msgpack is the fastest at writing.
- JSON is surprisingly fast. This is because reflect-cpp's JSON parser is built on top of yyjson, arguably the fastest JSON library for C or C++.
- The performance of CBOR is disappointing. We have tried swapping out the underlying library, TinyCBOR, for libcbor, but the results are similar.
- TOML and YAML are very slow when compared to the other formats. To be fair, these formats emphasize readability rather than speed.
Expand Down
12 changes: 12 additions & 0 deletions benchmarks/all/canada_read.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <rfl/toml.hpp>
#include <rfl/ubjson.hpp>
#include <rfl/yaml.hpp>
#include <rfl/yas.hpp>
#include <vector>

namespace canada_read {
Expand Down Expand Up @@ -247,6 +248,17 @@ static void BM_canada_read_reflect_cpp_yaml(benchmark::State &state) {
}
BENCHMARK(BM_canada_read_reflect_cpp_yaml);

static void BM_canada_read_reflect_cpp_yas(benchmark::State &state) {
const auto data = rfl::yas::write(load_data());
for (auto _ : state) {
const auto res = rfl::yas::read<FeatureCollection>(data);
if (!res) {
std::cout << res.error().what() << std::endl;
}
}
}
BENCHMARK(BM_canada_read_reflect_cpp_yas);

// ----------------------------------------------------------------------------

} // namespace canada_read
12 changes: 12 additions & 0 deletions benchmarks/all/canada_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <rfl/toml.hpp>
#include <rfl/ubjson.hpp>
#include <rfl/yaml.hpp>
#include <rfl/yas.hpp>
#include <vector>

namespace canada_write {
Expand Down Expand Up @@ -231,6 +232,17 @@ static void BM_canada_write_reflect_cpp_yaml(benchmark::State &state) {
}
BENCHMARK(BM_canada_write_reflect_cpp_yaml);

static void BM_canada_write_reflect_cpp_yas(benchmark::State &state) {
const auto data = load_data();
for (auto _ : state) {
const auto output = rfl::yas::write(data);
if (output.size() == 0) {
std::cout << "No output" << std::endl;
}
}
}
BENCHMARK(BM_canada_write_reflect_cpp_yas);

// ----------------------------------------------------------------------------

} // namespace canada_write
12 changes: 12 additions & 0 deletions benchmarks/all/licenses_read.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <rfl/ubjson.hpp>
#include <rfl/xml.hpp>
#include <rfl/yaml.hpp>
#include <rfl/yas.hpp>
#include <vector>

namespace licenses_read {
Expand Down Expand Up @@ -270,6 +271,17 @@ static void BM_licenses_read_reflect_cpp_yaml(benchmark::State &state) {
}
BENCHMARK(BM_licenses_read_reflect_cpp_yaml);

static void BM_licenses_read_reflect_cpp_yas(benchmark::State &state) {
const auto data = rfl::yas::write(load_data());
for (auto _ : state) {
const auto res = rfl::yas::read<Licenses>(data);
if (!res) {
std::cout << res.error().what() << std::endl;
}
}
}
BENCHMARK(BM_licenses_read_reflect_cpp_yas);

// ----------------------------------------------------------------------------

} // namespace licenses_read
12 changes: 12 additions & 0 deletions benchmarks/all/licenses_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <rfl/ubjson.hpp>
#include <rfl/xml.hpp>
#include <rfl/yaml.hpp>
#include <rfl/yas.hpp>
#include <vector>

namespace licenses_write {
Expand Down Expand Up @@ -270,6 +271,17 @@ static void BM_licenses_write_reflect_cpp_yaml(benchmark::State &state) {
}
BENCHMARK(BM_licenses_write_reflect_cpp_yaml);

static void BM_licenses_write_reflect_cpp_yas(benchmark::State &state) {
const auto data = load_data();
for (auto _ : state) {
const auto output = rfl::yas::write(data);
if (output.size() == 0) {
std::cout << "No output" << std::endl;
}
}
}
BENCHMARK(BM_licenses_write_reflect_cpp_yas);

// ----------------------------------------------------------------------------

} // namespace licenses_write
12 changes: 12 additions & 0 deletions benchmarks/all/person_read.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <rfl/ubjson.hpp>
#include <rfl/xml.hpp>
#include <rfl/yaml.hpp>
#include <rfl/yas.hpp>
#include <vector>
namespace person_read {

Expand Down Expand Up @@ -248,6 +249,17 @@ static void BM_person_read_reflect_cpp_yaml(benchmark::State &state) {
}
BENCHMARK(BM_person_read_reflect_cpp_yaml);

static void BM_person_read_reflect_cpp_yas(benchmark::State &state) {
const auto data = rfl::yas::write(load_data());
for (auto _ : state) {
const auto res = rfl::yas::read<Person>(data);
if (!res) {
std::cout << res.error().what() << std::endl;
}
}
}
BENCHMARK(BM_person_read_reflect_cpp_yas);

// ----------------------------------------------------------------------------

} // namespace person_read
12 changes: 12 additions & 0 deletions benchmarks/all/person_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <rfl/ubjson.hpp>
#include <rfl/xml.hpp>
#include <rfl/yaml.hpp>
#include <rfl/yas.hpp>
#include <vector>

namespace person_write {
Expand Down Expand Up @@ -249,6 +250,17 @@ static void BM_person_write_reflect_cpp_yaml(benchmark::State &state) {
}
BENCHMARK(BM_person_write_reflect_cpp_yaml);

static void BM_person_write_reflect_cpp_yas(benchmark::State &state) {
const auto data = load_data();
for (auto _ : state) {
const auto output = rfl::yas::write(data);
if (output.size() == 0) {
std::cout << "No output" << std::endl;
}
}
}
BENCHMARK(BM_person_write_reflect_cpp_yas);

// ----------------------------------------------------------------------------

} // namespace person_write
55 changes: 55 additions & 0 deletions docs/supported_formats/yas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# yas

For [yas](https://github.com/niXman/yas) support, you must also include the header `<rfl/yas.hpp>` and link to the yas library.
Furthermore, when compiling reflect-cpp, you need to pass `-DREFLECTCPP_YAS=ON` to cmake. If you are using vcpkg, there should be an appropriate feature that will abstract this away for you.

yas is a very fast and compact serialization library. reflect-cpp implements it as a schemaful binary format.

Note that even though yas is probably the fastest format supported by reflect-cpp, that speed comes at the cost of lack of backwards compatibility. If you change the structure of your data, you will not be able to read old files anymore. If you need backwards compatibility, consider using a different format like flexbuffers or msgpack, which are also supported by reflect-cpp. Both of these are very fast and compact as well, but they are schema-less and therefore more flexible when it comes to changing the structure of your data.

## Reading and writing

Suppose you have a struct like this:

```cpp
struct Person {
std::string first_name;
std::string last_name;
rfl::Timestamp<"%Y-%m-%d"> birthday;
std::vector<Person> children;
};
```

A `Person` can be serialized to a bytes vector like this:

```cpp
const auto person = Person{...};
const std::vector<char> bytes = rfl::yas::write(person);
```

You can parse bytes like this:

```cpp
const rfl::Result<Person> result = rfl::yas::read<Person>(bytes);
```

## Loading and saving

You can also load and save to disc using a very similar syntax:

```cpp
const rfl::Result<Person> result = rfl::yas::load<Person>("/path/to/file.bin");

const auto person = Person{...};
rfl::yas::save("/path/to/file.bin", person);
```

## Reading from and writing into streams

You can also read from and write into any `std::ostream` respectively.

```cpp
const auto person = Person{...};
rfl::yas::write(person, my_ostream);
```

13 changes: 13 additions & 0 deletions include/rfl/yas.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#ifndef RFL_YAS_HPP_
#define RFL_YAS_HPP_

#include "../rfl.hpp"
#include "yas/Parser.hpp"
#include "yas/Reader.hpp"
#include "yas/Writer.hpp"
#include "yas/load.hpp"
#include "yas/read.hpp"
#include "yas/save.hpp"
#include "yas/write.hpp"

#endif
58 changes: 58 additions & 0 deletions include/rfl/yas/Parser.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#ifndef RFL_YAS_PARSER_HPP_
#define RFL_YAS_PARSER_HPP_

#include "../Generic.hpp"
#include "../NamedTuple.hpp"
#include "../Tuple.hpp"
#include "../always_false.hpp"
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"

namespace rfl {
namespace parsing {

/// Yas requires us to explicitly set the number of fields in advance.
/// Because of that, we require all of the fields and then set them to nullptr,
/// if necessary.
template <class ProcessorsType, class... FieldTypes>
requires AreReaderAndWriter<yas::Reader, yas::Writer,
NamedTuple<FieldTypes...>>
struct Parser<yas::Reader, yas::Writer, NamedTuple<FieldTypes...>,
ProcessorsType>
: public NamedTupleParser<
yas::Reader, yas::Writer,
/*_ignore_empty_containers=*/false,
/*_all_required=*/true,
/*_no_field_names=*/ProcessorsType::no_field_names_, ProcessorsType,
FieldTypes...> {};

template <class ProcessorsType, class... Ts>
requires AreReaderAndWriter<yas::Reader, yas::Writer, rfl::Tuple<Ts...>>
struct Parser<yas::Reader, yas::Writer, rfl::Tuple<Ts...>, ProcessorsType>
: public TupleParser<yas::Reader, yas::Writer,
/*_ignore_empty_containers=*/false,
/*_all_required=*/true, ProcessorsType,
rfl::Tuple<Ts...>> {};

template <class ProcessorsType, class... Ts>
requires AreReaderAndWriter<yas::Reader, yas::Writer, std::tuple<Ts...>>
struct Parser<yas::Reader, yas::Writer, std::tuple<Ts...>, ProcessorsType>
: public TupleParser<yas::Reader, yas::Writer,
/*_ignore_empty_containers=*/false,
/*_all_required=*/true, ProcessorsType,
std::tuple<Ts...>> {};

} // namespace parsing
} // namespace rfl

namespace rfl {
namespace yas {

template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;

}
} // namespace rfl

#endif
Loading
Loading