Skip to content

Commit

Permalink
Added CBOR support; #53
Browse files Browse the repository at this point in the history
  • Loading branch information
liuzicheng1987 committed Jan 26, 2024
1 parent 5a34c9b commit f2d9779
Show file tree
Hide file tree
Showing 86 changed files with 2,334 additions and 23 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ jobs:
- name: Run test
run: |
g++ --version
cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release
cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_CBOR=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build -j 4
./build/tests/bson/reflect-cpp-bson-tests
./build/tests/cbor/reflect-cpp-cbor-tests
./build/tests/flexbuffers/reflect-cpp-flexbuffers-tests
./build/tests/json/reflect-cpp-json-tests
./build/tests/xml/reflect-cpp-xml-tests
Expand Down Expand Up @@ -52,9 +53,10 @@ jobs:
CXX: clang++
run: |
clang --version
cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release
cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_CBOR=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build -j 4
./build/tests/bson/reflect-cpp-bson-tests
./build/tests/cbor/reflect-cpp-cbor-tests
./build/tests/flexbuffers/reflect-cpp-flexbuffers-tests
./build/tests/json/reflect-cpp-json-tests
./build/tests/xml/reflect-cpp-xml-tests
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

# Output files
*.bson
*.cbor
*.json
*.fb
*.xml
Expand Down
11 changes: 10 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ cmake_minimum_required(VERSION 3.15)

option(REFLECTCPP_BUILD_SHARED "Build shared library" OFF)
option(REFLECTCPP_BSON "Enable BSON support" OFF)
option(REFLECTCPP_CBOR "Enable CBOR support" OFF)
option(REFLECTCPP_FLEXBUFFERS "Enable flexbuffers support" OFF)
option(REFLECTCPP_XML "Enable XML support" OFF)
option(REFLECTCPP_YAML "Enable YAML support" OFF)

option(REFLECTCPP_BUILD_TESTS "Build tests" OFF)

# enable vcpkg if require features other than JSON
if (REFLECTCPP_BSON OR REFLECTCPP_FLEXBUFFERS OR REFLECTCPP_XML OR REFLECTCPP_YAML)
if (REFLECTCPP_BSON OR REFLECTCPP_CBOR OR REFLECTCPP_FLEXBUFFERS OR REFLECTCPP_XML OR REFLECTCPP_YAML)
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file")
endif ()

Expand All @@ -30,6 +31,14 @@ if (REFLECTCPP_BSON)
target_link_libraries(reflectcpp PRIVATE $<IF:$<TARGET_EXISTS:mongo::bson_static>,mongo::bson_static,mongo::bson_shared>)
endif ()

if (REFLECTCPP_CBOR)
if (MSVC)
target_link_libraries(reflectcpp PRIVATE "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib/tinycbor${CMAKE_STATIC_LIBRARY_SUFFIX}")
else ()
target_link_libraries(reflectcpp PRIVATE "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib/libtinycbor${CMAKE_STATIC_LIBRARY_SUFFIX}")
endif ()
endif ()

if (REFLECTCPP_FLEXBUFFERS)
find_package(flatbuffers CONFIG REQUIRED)
target_link_libraries(reflectcpp INTERFACE flatbuffers::flatbuffers)
Expand Down
32 changes: 25 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ The following table lists the serialization formats currently supported by refle
| Format | Library | Version | License | Remarks |
|--------------|------------------------------------------------------|--------------|------------| -----------------------------------------------------|
| JSON | [yyjson](https://github.com/ibireme/yyjson) | >= 0.8.0 | MIT | out-of-the-box support, included in this repository |
| BSON | [libbson](https://github.com/mongodb/libbson) | >= 1.25.1 | Apache 2.0 | |
| flexbuffers | [flatbuffers](https://github.com/google/flatbuffers) | >= 23.5.26 | Apache 2.0 | |
| XML | [pugixml](https://github.com/zeux/pugixml) | >= 1.14 | MIT | |
| YAML | [yaml-cpp](https://github.com/jbeder/yaml-cpp) | >= 0.8.0 | MIT | |
| BSON | [libbson](https://github.com/mongodb/libbson) | >= 1.25.1 | Apache 2.0 | JSON-like binary format |
| CBOR | [tinycbor](https://github.com/intel/tinycbor) | >= 0.6.0 | MIT | JSON-like binary format |
| flexbuffers | [flatbuffers](https://github.com/google/flatbuffers) | >= 23.5.26 | Apache 2.0 | Schema-less version of flatbuffers, 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 |

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 @@ -69,8 +70,7 @@ The resulting JSON string looks like this:
{"first_name":"Homer","last_name":"Simpson","age":45}
```

Or you can use another format, such as YAML. This will work for just about
any example in the entire documentation or any supported format.
Or you can use another format, such as YAML.

```cpp
#include <rfl/yaml.hpp>
Expand All @@ -89,6 +89,21 @@ last_name: Simpson
age: 45
```
This will work for just about any example in the entire documentation
and any supported format:
```cpp
rfl::bson::write(homer);
rfl::cbor::write(homer);
rfl::flexbuf::write(homer);
rfl::xml::write(homer);

rfl::bson::read<Person>(bson_bytes);
rfl::cbor::read<Person>(cbor_bytes);
rfl::flexbuf::read<Person>(flexbuf_bytes);
rfl::xml::read<Person>(xml_string);
```

## More Comprehensive Example

```cpp
Expand Down Expand Up @@ -450,6 +465,7 @@ To use reflect-cpp in your project:
add_subdirectory(reflect-cpp) # Add this project as a subdirectory
set(REFLECTCPP_BSON ON) # Optional
set(REFLECTCPP_CBOR ON) # Optional
set(REFLECTCPP_FLEXBUFFERS ON) # Optional
set(REFLECTCPP_XML ON) # Optional
set(REFLECTCPP_YAML ON) # Optional
Expand Down Expand Up @@ -496,14 +512,16 @@ git submodule update --init
./vcpkg/bootstrap-vcpkg.bat # Windows
# You may be prompted to install additional dependencies.

cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release
cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_CBOR=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build -j 4 # gcc, clang
cmake --build build --config Release -j 4 # MSVC
```

To run the tests, do the following:

```
./build/tests/bson/reflect-cpp-bson-tests
./build/tests/cbor/reflect-cpp-cbor-tests
./build/tests/flexbuffers/reflect-cpp-flexbuffers-tests
./build/tests/json/reflect-cpp-json-tests
./build/tests/xml/reflect-cpp-xml-tests
Expand Down
8 changes: 5 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,13 @@

5.2) [BSON](https://github.com/getml/reflect-cpp/blob/main/docs/bson.md)

5.3) [flexbuffers](https://github.com/getml/reflect-cpp/blob/main/docs/flexbuffers.md)
5.3) [CBOR](https://github.com/getml/reflect-cpp/blob/main/docs/cbor.md)

5.4) [XML](https://github.com/getml/reflect-cpp/blob/main/docs/xml.md)
5.4) [flexbuffers](https://github.com/getml/reflect-cpp/blob/main/docs/flexbuffers.md)

5.5) [YAML](https://github.com/getml/reflect-cpp/blob/main/docs/yaml.md)
5.5) [XML](https://github.com/getml/reflect-cpp/blob/main/docs/xml.md)

5.6) [YAML](https://github.com/getml/reflect-cpp/blob/main/docs/yaml.md)

## 6) Advanced topics

Expand Down
100 changes: 100 additions & 0 deletions docs/cbor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# CBOR

For CBOR support, you must also include the header `<rfl/cbor.hpp>` and link to the tinycbor library (https://github.com/intel/tinycbor).

CBOR or Concise Binary Object Representation, is a JSON-like binary format with an emphasis on small binary sizes.

## 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 turned into a bytes vector like this:
```cpp
const auto person = Person{...};
const std::vector<char> bytes = rfl::cbor::write(person);
```

You can parse bytes like this:

```cpp
const rfl::Result<Person> result = rfl::cbor::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::cbor::load<Person>("/path/to/file.cbor");

const auto person = Person{...};
rfl::cbor::save("/path/to/file.cbor", person);
```
## Reading from and writing into streams
You can also read from and write into any `std::istream` and `std::ostream` respectively.
```cpp
const rfl::Result<Person> result = rfl::cbor::read<Person>(my_istream);
const auto person = Person{...};
rfl::cbor::write(person, my_ostream);
```

Note that `std::cout` is also an ostream, so this works as well:

```cpp
rfl::cbor::write(person, std::cout) << std::endl;
```
(Since CBOR is a binary format, the readability of this will be limited, but it might be useful for debugging).
## Custom constructors
One of the great things about C++ is that it gives you control over
when and how you code is compiled.
For large and complex systems of structs, it is often a good idea to split up
your code into smaller compilation units. You can do so using custom constructors.
For the CBOR format, these must be a static function on your struct or class called
`from_cbor` that take a `rfl::cbor::Reader::InputVarType` as input and return
the class or the class wrapped in `rfl::Result`.
In your header file you can write something like this:
```cpp
struct Person {
rfl::Rename<"firstName", std::string> first_name;
rfl::Rename<"lastName", std::string> last_name;
rfl::Timestamp<"%Y-%m-%d"> birthday;
using InputVarType = typename rfl::cbor::Reader::InputVarType;
static rfl::Result<Person> from_cbor(const InputVarType& _obj);
};
```

And in your source file, you implement `from_cbor` as follows:

```cpp
rfl::Result<Person> Person::from_cbor(const InputVarType& _obj) {
const auto from_nt = [](auto&& _nt) {
return rfl::from_named_tuple<Person>(std::move(_nt));
};
return rfl::cbor::read<rfl::named_tuple_t<Person>>(_obj)
.transform(from_nt);
}
```
This will force the compiler to only compile the CBOR parsing when the source file is compiled.
12 changes: 12 additions & 0 deletions include/rfl/cbor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef RFL_CBOR_HPP_
#define RFL_CBOR_HPP_

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

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

#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"

namespace rfl {
namespace parsing {

/// CBOR requires us to explicitly set the number of fields in advance. Because

This comment has been minimized.

Copy link
@bryceschober

bryceschober Jan 7, 2025

@liuzicheng1987 What is meant by this comment? CBOR certainly specifies indefinite-length maps, and jsoncons seems to support them as well. Maybe this was a constraint left over from TinyCBOR, or just the initial level of support?

/// of that, we require all of the fields and then set them to nullptr, if
/// necessary.
template <class... FieldTypes>
requires AreReaderAndWriter<cbor::Reader, cbor::Writer,
NamedTuple<FieldTypes...>>
struct Parser<cbor::Reader, cbor::Writer, NamedTuple<FieldTypes...>>
: public NamedTupleParser<cbor::Reader, cbor::Writer,
/*_ignore_empty_containers=*/false,
/*_all_required=*/true, FieldTypes...> {
};

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

} // namespace parsing
} // namespace rfl

namespace rfl {
namespace cbor {

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

}
} // namespace rfl

#endif
Loading

0 comments on commit f2d9779

Please sign in to comment.