diff --git a/README.md b/README.md
index 2064fbb..6dc71e2 100644
--- a/README.md
+++ b/README.md
@@ -353,6 +353,51 @@ An example for such custom (de)serialization can be found in the file
`json/reflector-chronoutilities.h`. It provides (de)serialization of `DateTime` and
`TimeSpan` objects from the C++ utilities library mentioned under dependencies.
+### Versioning
+#### JSON (de)serializer
+The JSON (de)serializer doesn't support versioning at this point. It'll simply read/write the
+members present in the struct. Additional members (which were e.g. present in older/newer
+versions of the struct) are ignored when reading and in consequence dropped when writing.
+
+#### Binary (de)serializer
+The binary (de)serializer supports *very* experimental versioning. Otherwise adding/removing
+members is a breaking change. The versioning looks like this:
+
+
+// enable definition of the macros shown below (otherwise use long macros defined in
+// `lib/versioning.h`)
+#define REFLECTIVE_RAPIDJSON_SHORT_MACROS
+
+#include <reflective_rapidjson/binary/serializable.h>
+
+// example struct where version is *not* serialized/deserialized; defaults to version from
+// outer scope when reading/writing, defaults to version 0 on top-level
+struct Nested : public BinarySerializable<Nested> { //
+ std::uint32_t foo; // will be read/written in any case
+
+as_of_version(3):
+ std::uint32_t bar; // will be read/written if outer scope version is >= 3
+};
+
+// example struct where version is serialized/deserialized; defaults to version when writing
+struct Example : public BinarySerializable<Example, 3> {
+ Nested nested; // will be read/written in any case, version is "propagated down"
+ std::uint32_t a, b; // will be read/written in any case
+
+until_version(2):
+ std::uint32_t c, d; // will be read/written if version is <= 2
+
+as_of_version(3):
+ std::uint32_t e, f; // will be read/written if version is >= 3
+
+as_of_version(4):
+ std::uint32_t g; // will be read/written if version is >= 4
+};
+
+
+A mechanism to catch unsupported versions during deserialization is yet to be implemented.
+Additionally, the versioning is completely untested at this point.
+
### Remarks
* Static member variables and member functions are currently ignored by the generator.
* It is currently not possible to ignore a specific member variable.
diff --git a/generator/binaryserializationcodegenerator.cpp b/generator/binaryserializationcodegenerator.cpp
index a527104..b8b9b9b 100644
--- a/generator/binaryserializationcodegenerator.cpp
+++ b/generator/binaryserializationcodegenerator.cpp
@@ -5,6 +5,10 @@
#include
#include
#include
+#include
+#include
+
+#include
#include
@@ -35,36 +39,159 @@ BinarySerializationCodeGenerator::BinarySerializationCodeGenerator(CodeFactory &
}
/*!
- * \brief Returns the qualified name of the specified \a record if it is considered relevant.
+ * \brief Checks whether \a possiblyRelevantClass is actually relevant.
*/
-string BinarySerializationCodeGenerator::qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const
+void BinarySerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
{
- const string qualifiedName(record->getQualifiedNameAsString());
- switch (isQualifiedNameIfRelevant(record, qualifiedName)) {
- case IsRelevant::Yes:
- return qualifiedName;
- case IsRelevant::No:
- return string();
- default:;
+ SerializationCodeGenerator::computeRelevantClass(possiblyRelevantClass);
+ if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
+ return;
}
// consider all classes specified via "--additional-classes" argument relevant
if (!m_options.additionalClassesArg.isPresent()) {
- return string();
+ return;
}
- for (const char *className : m_options.additionalClassesArg.values()) {
- if (className == qualifiedName) {
- return qualifiedName;
+ for (const char *const className : m_options.additionalClassesArg.values()) {
+ if (className == possiblyRelevantClass.qualifiedName) {
+ possiblyRelevantClass.isRelevant = IsRelevant::Yes;
+ return;
}
}
+}
- return string();
+/// \brief The RetrieveIntegerLiteralFromDeclaratorDecl struct is used to traverse a variable declaration to get the integer value.
+struct RetrieveIntegerLiteralFromDeclaratorDecl : public clang::RecursiveASTVisitor {
+ explicit RetrieveIntegerLiteralFromDeclaratorDecl(const clang::ASTContext &ctx);
+ bool VisitStmt(clang::Stmt *st);
+ const clang::ASTContext &ctx;
+ std::uint64_t res;
+ bool success;
+};
+
+/// \brief Constructs a new instance for the specified AST context.
+RetrieveIntegerLiteralFromDeclaratorDecl::RetrieveIntegerLiteralFromDeclaratorDecl(const clang::ASTContext &ctx)
+ : ctx(ctx)
+ , res(0)
+ , success(false)
+{
+}
+
+/// \brief Reads the integer value of \a st for integer literals.
+bool RetrieveIntegerLiteralFromDeclaratorDecl::VisitStmt(clang::Stmt *st)
+{
+ if (st->getStmtClass() != clang::Stmt::IntegerLiteralClass) {
+ return true;
+ }
+ const auto *const integerLiteral = static_cast(st);
+ auto evaluation = clang::Expr::EvalResult();
+ integerLiteral->EvaluateAsInt(evaluation, ctx, clang::Expr::SE_NoSideEffects, true);
+ if (!evaluation.Val.isInt()) {
+ return true;
+ }
+ const auto &asInt = evaluation.Val.getInt();
+ if (asInt.getActiveBits() > 64) {
+ return true;
+ }
+ res = asInt.getZExtValue();
+ success = true;
+ return false;
+}
+
+/// \brief The MemberTracking struct is an internal helper for BinarySerializationCodeGenerator::generate().
+struct MemberTracking {
+ bool membersWritten = false, withinCondition = false;
+ BinaryVersion asOfVersion = BinaryVersion(), lastAsOfVersion = BinaryVersion();
+ BinaryVersion untilVersion = BinaryVersion(), lastUntilVersion = BinaryVersion();
+
+ bool checkForVersionMarker(clang::Decl *decl);
+ void concludeCondition(std::ostream &os);
+ void writeVersionCondition(std::ostream &os);
+ void writeExtraPadding(std::ostream &os);
+};
+
+/*!
+ * \brief Returns whether \a delc is a static member variable and processes special static member variables
+ * for versioning.
+ */
+bool MemberTracking::checkForVersionMarker(clang::Decl *decl)
+{
+ if (decl->getKind() != clang::Decl::Kind::Var) {
+ return false;
+ }
+ auto *const declarator = static_cast(decl);
+ const auto declarationName = declarator->getName();
+ const auto isAsOfVersion = declarationName.startswith("rrjAsOfVersion");
+ if (isAsOfVersion || declarationName.startswith("rrjUntilVersion")) {
+ auto v = RetrieveIntegerLiteralFromDeclaratorDecl(declarator->getASTContext());
+ v.TraverseDecl(declarator);
+ if (v.success) {
+ if (isAsOfVersion) {
+ asOfVersion = v.res;
+ if (asOfVersion > untilVersion) {
+ untilVersion = 0;
+ }
+ } else {
+ untilVersion = v.res;
+ if (untilVersion < asOfVersion) {
+ asOfVersion = 0;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+/*!
+ * \brief Concludes an unfinished version condition if-block.
+ */
+void MemberTracking::concludeCondition(std::ostream &os)
+{
+ if (withinCondition) {
+ os << " }\n";
+ }
+}
+
+/*!
+ * \brief Starts a new version condition if-block if versioning parameters have changed.
+ */
+void MemberTracking::writeVersionCondition(std::ostream &os)
+{
+ if (asOfVersion == lastAsOfVersion && untilVersion == lastUntilVersion) {
+ return;
+ }
+ concludeCondition(os);
+ lastAsOfVersion = asOfVersion;
+ lastUntilVersion = untilVersion;
+ if ((withinCondition = asOfVersion || untilVersion)) {
+ os << " if (";
+ if (asOfVersion) {
+ os << "version >= " << asOfVersion;
+ if (untilVersion) {
+ os << " && ";
+ }
+ }
+ if (untilVersion) {
+ os << "version <= " << untilVersion;
+ }
+ os << ") {\n";
+ }
+}
+
+/*!
+ * \brief Writes extra padding (if within a version condition).
+ */
+void MemberTracking::writeExtraPadding(std::ostream &os)
+{
+ if (withinCondition) {
+ os << " ";
+ }
}
/*!
* \brief Generates pull() and push() helper functions in the ReflectiveRapidJSON::BinaryReflector namespace for the relevant classes.
*/
-void BinarySerializationCodeGenerator::generate(ostream &os) const
+void BinarySerializationCodeGenerator::generate(std::ostream &os) const
{
// initialize source manager to make use of isOnlyIncluded() for skipping records which are only included
lazyInitializeSourceManager();
@@ -118,22 +245,55 @@ void BinarySerializationCodeGenerator::generate(ostream &os) const
// print writeCustomType method
os << "template <> " << visibility << " void writeCustomType<::" << relevantClass.qualifiedName
- << ">(BinarySerializer &serializer, const ::" << relevantClass.qualifiedName
- << " &customObject)\n{\n"
- " // write base classes\n";
+ << ">(BinarySerializer &serializer, const ::" << relevantClass.qualifiedName << " &customObject, BinaryVersion version)\n{\n";
+ if (!relevantClass.relevantBase.empty()) {
+ os << " // write version\n"
+ " using V = Versioning<"
+ << relevantClass.relevantBase
+ << ">;\n"
+ " if constexpr (V::enabled) {\n"
+ " serializer.writeVariableLengthUIntBE(V::applyDefault(version));\n"
+ " }\n";
+ }
+ os << " // write base classes\n";
for (const RelevantClass *baseClass : relevantBases) {
- os << " serializer.write(static_castqualifiedName << " &>(customObject));\n";
+ os << " serializer.write(static_castqualifiedName << " &>(customObject), version);\n";
}
os << " // write members\n";
- auto membersWritten = false;
- for (const clang::FieldDecl *field : relevantClass.record->fields()) {
- if (writePrivateMembers || field->getAccess() == clang::AS_public) {
- os << " serializer.write(customObject." << field->getName() << ");\n";
- membersWritten = true;
+ auto mt = MemberTracking();
+ for (clang::Decl *const decl : relevantClass.record->decls()) {
+ // check static member variables for version markers
+ if (mt.checkForVersionMarker(decl)) {
+ continue;
}
+
+ // skip all further declarations but fields
+ if (decl->getKind() != clang::Decl::Kind::Field) {
+ continue;
+ }
+
+ // skip const members
+ const auto *const field = static_cast(decl);
+ if (field->getType().isConstant(field->getASTContext())) {
+ continue;
+ }
+
+ // skip private members conditionally
+ if (!writePrivateMembers && field->getAccess() != clang::AS_public) {
+ continue;
+ }
+
+ // write version markers
+ mt.writeVersionCondition(os);
+ mt.writeExtraPadding(os);
+
+ // write actual code for serialization
+ os << " serializer.write(customObject." << field->getName() << ", version);\n";
+ mt.membersWritten = true;
}
- if (relevantBases.empty() && !membersWritten) {
- os << " (void)serializer;\n (void)customObject;\n";
+ mt.concludeCondition(os);
+ if (relevantBases.empty() && !mt.membersWritten) {
+ os << " (void)serializer;\n (void)customObject;\n \n(void)version;";
}
os << "}\n";
@@ -143,28 +303,53 @@ void BinarySerializationCodeGenerator::generate(ostream &os) const
}
// print readCustomType method
- os << "template <> " << visibility << " void readCustomType<::" << relevantClass.qualifiedName
- << ">(BinaryDeserializer &deserializer, ::" << relevantClass.qualifiedName
- << " &customObject)\n{\n"
- " // read base classes\n";
+ mt = MemberTracking();
+ os << "template <> " << visibility << " BinaryVersion readCustomType<::" << relevantClass.qualifiedName
+ << ">(BinaryDeserializer &deserializer, ::" << relevantClass.qualifiedName << " &customObject, BinaryVersion version)\n{\n";
+ if (!relevantClass.relevantBase.empty()) {
+ os << " // read version\n"
+ " if constexpr (Versioning<"
+ << relevantClass.relevantBase
+ << ">::enabled) {\n"
+ " version = deserializer.readVariableLengthUIntBE();\n"
+ " }\n";
+ }
+ os << " // read base classes\n";
for (const RelevantClass *baseClass : relevantBases) {
os << " deserializer.read(static_cast<::" << baseClass->qualifiedName << " &>(customObject));\n";
}
os << " // read members\n";
- auto membersRead = false;
- for (const clang::FieldDecl *field : relevantClass.record->fields()) {
+ for (clang::Decl *const decl : relevantClass.record->decls()) {
+ // check static member variables for version markers
+ if (mt.checkForVersionMarker(decl)) {
+ continue;
+ }
+
+ // skip all further declarations but fields
+ if (decl->getKind() != clang::Decl::Kind::Field) {
+ continue;
+ }
+
// skip const members
+ const auto *const field = static_cast(decl);
if (field->getType().isConstant(field->getASTContext())) {
continue;
}
+
+ // write version markers
+ mt.writeVersionCondition(os);
+ mt.writeExtraPadding(os);
+
if (readPrivateMembers || field->getAccess() == clang::AS_public) {
- os << " deserializer.read(customObject." << field->getName() << ");\n";
- membersRead = true;
+ os << " deserializer.read(customObject." << field->getName() << ", version);\n";
+ mt.membersWritten = true;
}
}
- if (relevantBases.empty() && !membersRead) {
+ mt.concludeCondition(os);
+ if (relevantBases.empty() && !mt.membersWritten) {
os << " (void)deserializer;\n (void)customObject;\n";
}
+ os << " return version;\n";
os << "}\n\n";
}
diff --git a/generator/binaryserializationcodegenerator.h b/generator/binaryserializationcodegenerator.h
index 2563db8..4bbc851 100644
--- a/generator/binaryserializationcodegenerator.h
+++ b/generator/binaryserializationcodegenerator.h
@@ -27,7 +27,7 @@ public:
void generate(std::ostream &os) const override;
protected:
- std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const override;
+ void computeRelevantClass(RelevantClass &possiblyRelevantClass) const override;
const Options &m_options;
};
diff --git a/generator/codegenerator.cpp b/generator/codegenerator.cpp
index 0694ae6..f2f3acc 100644
--- a/generator/codegenerator.cpp
+++ b/generator/codegenerator.cpp
@@ -44,17 +44,18 @@ bool CodeGenerator::isOnlyIncluded(const clang::Decl *declaration) const
/*!
* \brief Returns whether the specified \a record inherits from an instantiation of the specified \a templateClass.
+ * \returns Returns the relevant base class if that's the case and otherwise nullptr.
* \remarks The specified \a record must be defined (not only forward-declared).
*/
-bool CodeGenerator::inheritsFromInstantiationOf(clang::CXXRecordDecl *const record, const char *const templateClass)
+clang::CXXBaseSpecifier *CodeGenerator::inheritsFromInstantiationOf(clang::CXXRecordDecl *const record, const char *const templateClass)
{
- for (const clang::CXXBaseSpecifier &base : record->bases()) {
- const clang::CXXRecordDecl *const baseDecl = base.getType()->getAsCXXRecordDecl();
+ for (clang::CXXBaseSpecifier &base : record->bases()) {
+ clang::CXXRecordDecl *const baseDecl = base.getType()->getAsCXXRecordDecl();
if (baseDecl && baseDecl->getQualifiedNameAsString() == templateClass) {
- return true;
+ return &base;
}
}
- return false;
+ return nullptr;
}
} // namespace ReflectiveRapidJSON
diff --git a/generator/codegenerator.h b/generator/codegenerator.h
index 0c5de9d..a206b87 100644
--- a/generator/codegenerator.h
+++ b/generator/codegenerator.h
@@ -8,6 +8,7 @@
namespace clang {
class Decl;
class CXXRecordDecl;
+class CXXBaseSpecifier;
class SourceManager;
} // namespace clang
@@ -32,7 +33,7 @@ protected:
CodeFactory &factory() const;
void lazyInitializeSourceManager() const;
bool isOnlyIncluded(const clang::Decl *declaration) const;
- static bool inheritsFromInstantiationOf(clang::CXXRecordDecl *record, const char *templateClass);
+ static clang::CXXBaseSpecifier *inheritsFromInstantiationOf(clang::CXXRecordDecl *record, const char *templateClass);
private:
CodeFactory &m_factory;
diff --git a/generator/jsonserializationcodegenerator.cpp b/generator/jsonserializationcodegenerator.cpp
index 3f90657..700d96d 100644
--- a/generator/jsonserializationcodegenerator.cpp
+++ b/generator/jsonserializationcodegenerator.cpp
@@ -35,30 +35,25 @@ JsonSerializationCodeGenerator::JsonSerializationCodeGenerator(CodeFactory &fact
}
/*!
- * \brief Returns the qualified name of the specified \a record if it is considered relevant.
+ * \brief Checks whether \a possiblyRelevantClass is actually relevant.
*/
-string JsonSerializationCodeGenerator::qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const
+void JsonSerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
{
- const string qualifiedName(record->getQualifiedNameAsString());
- switch (isQualifiedNameIfRelevant(record, qualifiedName)) {
- case IsRelevant::Yes:
- return qualifiedName;
- case IsRelevant::No:
- return string();
- default:;
+ SerializationCodeGenerator::computeRelevantClass(possiblyRelevantClass);
+ if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
+ return;
}
// consider all classes specified via "--additional-classes" argument relevant
if (!m_options.additionalClassesArg.isPresent()) {
- return string();
+ return;
}
- for (const char *className : m_options.additionalClassesArg.values()) {
- if (className == qualifiedName) {
- return qualifiedName;
+ for (const char *const className : m_options.additionalClassesArg.values()) {
+ if (className == possiblyRelevantClass.qualifiedName) {
+ possiblyRelevantClass.isRelevant = IsRelevant::Yes;
+ return;
}
}
-
- return string();
}
/*!
diff --git a/generator/jsonserializationcodegenerator.h b/generator/jsonserializationcodegenerator.h
index 67e8a19..f94024a 100644
--- a/generator/jsonserializationcodegenerator.h
+++ b/generator/jsonserializationcodegenerator.h
@@ -27,7 +27,7 @@ public:
void generate(std::ostream &os) const override;
protected:
- std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const override;
+ void computeRelevantClass(RelevantClass &possiblyRelevantClass) const override;
const Options &m_options;
};
diff --git a/generator/serializationcodegenerator.cpp b/generator/serializationcodegenerator.cpp
index b7fffe2..6c6a936 100644
--- a/generator/serializationcodegenerator.cpp
+++ b/generator/serializationcodegenerator.cpp
@@ -5,6 +5,8 @@
#include
#include
#include
+#include
+#include
#include
@@ -66,12 +68,12 @@ void SerializationCodeGenerator::addDeclaration(clang::Decl *decl)
}
}
-SerializationCodeGenerator::IsRelevant SerializationCodeGenerator::isQualifiedNameIfRelevant(
- clang::CXXRecordDecl *record, const std::string &qualifiedName) const
+void SerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
{
// skip all classes which are only forward-declared
- if (!record->isCompleteDefinition()) {
- return IsRelevant::No;
+ if (!possiblyRelevantClass.record->isCompleteDefinition()) {
+ possiblyRelevantClass.isRelevant = IsRelevant::No;
+ return;
}
// consider all classes for which a specialization of the "AdaptedJsonSerializable" struct is available
@@ -80,31 +82,40 @@ SerializationCodeGenerator::IsRelevant SerializationCodeGenerator::isQualifiedNa
if (isOnlyIncluded(adaptionRecord.record)) {
continue;
}
- if (adaptionRecord.qualifiedName == qualifiedName) {
- return IsRelevant::Yes;
+ if (adaptionRecord.qualifiedName == possiblyRelevantClass.qualifiedName) {
+ possiblyRelevantClass.isRelevant = IsRelevant::Yes;
+ return;
}
}
// skip all classes which are only included
- if (isOnlyIncluded(record)) {
- return IsRelevant::No;
+ if (isOnlyIncluded(possiblyRelevantClass.record)) {
+ possiblyRelevantClass.isRelevant = IsRelevant::No;
+ return;
}
// consider all classes inheriting from an instantiation of "JsonSerializable" relevant
- if (inheritsFromInstantiationOf(record, m_qualifiedNameOfRecords)) {
- return IsRelevant::Yes;
+ if (const auto *const relevantBase = inheritsFromInstantiationOf(possiblyRelevantClass.record, m_qualifiedNameOfRecords)) {
+ auto policy = clang::PrintingPolicy(possiblyRelevantClass.record->getLangOpts());
+ policy.FullyQualifiedName = true;
+ policy.SuppressScope = false;
+ policy.SuppressUnwrittenScope = false;
+ policy.SplitTemplateClosers = false;
+ possiblyRelevantClass.relevantBase
+ = clang::TypeName::getFullyQualifiedName(relevantBase->getType(), possiblyRelevantClass.record->getASTContext(), policy, true);
+ possiblyRelevantClass.isRelevant = IsRelevant::Yes;
+ return;
}
-
- return IsRelevant::Maybe;
}
std::vector SerializationCodeGenerator::findRelevantClasses() const
{
std::vector relevantClasses;
- for (clang::CXXRecordDecl *record : m_records) {
- string qualifiedName(qualifiedNameIfRelevant(record));
- if (!qualifiedName.empty()) {
- relevantClasses.emplace_back(move(qualifiedName), record);
+ for (clang::CXXRecordDecl *const record : m_records) {
+ auto &relevantClass = relevantClasses.emplace_back(record->getQualifiedNameAsString(), record);
+ computeRelevantClass(relevantClass);
+ if (relevantClass.isRelevant != IsRelevant::Yes) {
+ relevantClasses.pop_back();
}
}
return relevantClasses;
diff --git a/generator/serializationcodegenerator.h b/generator/serializationcodegenerator.h
index ba171c3..23a3236 100644
--- a/generator/serializationcodegenerator.h
+++ b/generator/serializationcodegenerator.h
@@ -5,6 +5,8 @@
#include
+#include
+
namespace ReflectiveRapidJSON {
std::ostream &operator<<(std::ostream &os, llvm::StringRef str);
@@ -15,11 +17,14 @@ std::ostream &operator<<(std::ostream &os, llvm::StringRef str);
*/
class SerializationCodeGenerator : public CodeGenerator {
public:
+ enum class IsRelevant { Yes, No, Maybe };
struct RelevantClass {
explicit RelevantClass(std::string &&qualifiedName, clang::CXXRecordDecl *record);
std::string qualifiedName;
- clang::CXXRecordDecl *record;
+ std::string relevantBase;
+ clang::CXXRecordDecl *record = nullptr;
+ IsRelevant isRelevant = IsRelevant::Maybe;
};
SerializationCodeGenerator(CodeFactory &factory);
@@ -27,9 +32,7 @@ public:
void addDeclaration(clang::Decl *decl) override;
protected:
- enum class IsRelevant { Yes, No, Maybe };
- IsRelevant isQualifiedNameIfRelevant(clang::CXXRecordDecl *record, const std::string &qualifiedName) const;
- virtual std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const = 0;
+ virtual void computeRelevantClass(RelevantClass &possiblyRelevantClass) const;
std::vector findRelevantClasses() const;
static std::vector findRelevantBaseClasses(
const RelevantClass &relevantClass, const std::vector &relevantBases);
diff --git a/generator/tests/morestructs.h b/generator/tests/morestructs.h
index d355236..c98a9f8 100644
--- a/generator/tests/morestructs.h
+++ b/generator/tests/morestructs.h
@@ -1,8 +1,11 @@
#ifndef REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H
#define REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H
+#define REFLECTIVE_RAPIDJSON_SHORT_MACROS
+
#include "../../lib/binary/serializable.h"
#include "../../lib/json/serializable.h"
+#include "../../lib/versioning.h"
using namespace std;
using namespace ReflectiveRapidJSON;
@@ -67,4 +70,29 @@ struct PointerStruct : public BinarySerializable {
std::shared_ptr s3;
};
+/*!
+ * \brief All of this is supposed to work if classes are within a namespace so let's use a namespace here.
+ */
+namespace SomeNamespace {
+
+/*!
+ * \brief The PointerStruct struct is used to test the behavior of the binary (de)serialization with smart pointer.
+ */
+// clang-format off
+struct VersionedStruct : public BinarySerializable {
+ std::uint32_t a, b;
+
+until_version(2):
+ std::uint32_t c, d;
+
+as_of_version(3):
+ std::uint32_t e, f;
+
+as_of_version(4):
+ std::uint32_t g;
+};
+// clang-format on
+
+} // namespace SomeNamespace
+
#endif // REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index f555b2a..7ff73cb 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -5,7 +5,7 @@ set(META_PROJECT_TYPE library)
set(META_HEADER_ONLY_LIB ON)
# add project files
-set(HEADER_FILES traits.h)
+set(HEADER_FILES traits.h versioning.h)
set(SRC_FILES)
set(TEST_HEADER_FILES)
set(TEST_SRC_FILES)
diff --git a/lib/binary/reflector-boosthana.h b/lib/binary/reflector-boosthana.h
index fd57de2..ea64add 100644
--- a/lib/binary/reflector-boosthana.h
+++ b/lib/binary/reflector-boosthana.h
@@ -24,16 +24,17 @@
namespace ReflectiveRapidJSON {
namespace BinaryReflector {
-template > *> void readCustomType(BinaryDeserializer &deserializer, Type &customType)
+template > *>
+BinaryVersion readCustomType(BinaryDeserializer &deserializer, Type &customType, BinaryVersion version)
{
- boost::hana::for_each(
- boost::hana::keys(customType), [&deserializer, &customType](auto key) { deserializer.read(boost::hana::at_key(customType, key)); });
+ boost::hana::for_each(boost::hana::keys(customType), [&](auto key) { deserializer.read(boost::hana::at_key(customType, key), version); });
+ return 0;
}
-template > *> void writeCustomType(BinarySerializer &serializer, const Type &customType)
+template > *>
+void writeCustomType(BinarySerializer &serializer, const Type &customType, BinaryVersion version)
{
- boost::hana::for_each(
- boost::hana::keys(customType), [&serializer, &customType](auto key) { serializer.write(boost::hana::at_key(customType, key)); });
+ boost::hana::for_each(boost::hana::keys(customType), [&](auto key) { serializer.write(boost::hana::at_key(customType, key), version); });
}
} // namespace BinaryReflector
diff --git a/lib/binary/reflector-chronoutilities.h b/lib/binary/reflector-chronoutilities.h
index 79c911e..e600ebf 100644
--- a/lib/binary/reflector-chronoutilities.h
+++ b/lib/binary/reflector-chronoutilities.h
@@ -16,23 +16,33 @@
namespace ReflectiveRapidJSON {
namespace BinaryReflector {
-template <> inline void readCustomType(BinaryDeserializer &deserializer, CppUtilities::DateTime &dateTime)
+template <>
+inline BinaryVersion readCustomType(BinaryDeserializer &deserializer, CppUtilities::DateTime &dateTime, BinaryVersion version)
{
+ CPP_UTILITIES_UNUSED(version)
deserializer.read(dateTime.ticks());
+ return 0;
}
-template <> inline void writeCustomType(BinarySerializer &serializer, const CppUtilities::DateTime &dateTime)
+template <>
+inline void writeCustomType(BinarySerializer &serializer, const CppUtilities::DateTime &dateTime, BinaryVersion version)
{
+ CPP_UTILITIES_UNUSED(version)
serializer.write(dateTime.totalTicks());
}
-template <> inline void readCustomType(BinaryDeserializer &deserializer, CppUtilities::TimeSpan &timeSpan)
+template <>
+inline BinaryVersion readCustomType(BinaryDeserializer &deserializer, CppUtilities::TimeSpan &timeSpan, BinaryVersion version)
{
+ CPP_UTILITIES_UNUSED(version)
deserializer.read(timeSpan.ticks());
+ return 0;
}
-template <> inline void writeCustomType(BinarySerializer &serializer, const CppUtilities::TimeSpan &timeSpan)
+template <>
+inline void writeCustomType(BinarySerializer &serializer, const CppUtilities::TimeSpan &timeSpan, BinaryVersion version)
{
+ CPP_UTILITIES_UNUSED(version)
serializer.write(timeSpan.totalTicks());
}
diff --git a/lib/binary/reflector.h b/lib/binary/reflector.h
index 32aedc9..b5eb38e 100644
--- a/lib/binary/reflector.h
+++ b/lib/binary/reflector.h
@@ -8,6 +8,7 @@
*/
#include "../traits.h"
+#include "../versioning.h"
#include
#include
@@ -33,7 +34,8 @@ template struct AdaptedBinarySerializable : public Traits::Bool struct BinarySerializable;
+using BinaryVersion = std::uint64_t;
+template struct BinarySerializable;
/*!
* \brief The BinaryReflector namespace contains BinaryReader and BinaryWriter for automatic binary (de)serialization.
@@ -50,9 +52,22 @@ template using IsCustomType = Traits::Not>;
class BinaryDeserializer;
class BinarySerializer;
-template > * = nullptr> void readCustomType(BinaryDeserializer &deserializer, Type &customType);
-template > * = nullptr> void writeCustomType(BinarySerializer &serializer, const Type &customType);
+/// \brief Reads \a customType via \a deserializer.
+/// \remarks
+/// - If \tp Type is versioned, the version is determined from the data. Otherwise \a version is assumed.
+/// - The specified \a version shall be passed to nested invocations.
+/// \returns Returns the determined/assumed version.
+template > * = nullptr>
+BinaryVersion readCustomType(BinaryDeserializer &deserializer, Type &customType, BinaryVersion version = 0);
+/// \brief Writes \a customType via \a serializer.
+/// \remarks
+/// - If \tp Type is versioned, \a version is prepended to the data.
+/// - The specified \a version shall be passed to nested invocations.
+template > * = nullptr>
+void writeCustomType(BinarySerializer &serializer, const Type &customType, BinaryVersion version = 0);
+
+/// \brief The BinaryDeserializer class can read various data types, including custom ones, from an std::istream.
class BinaryDeserializer : public CppUtilities::BinaryReader {
friend class ::BinaryReflectorTests;
@@ -71,12 +86,14 @@ public:
void read(Type &iteratable);
template > * = nullptr> void read(Type &enumValue);
template > * = nullptr> void read(Type &variant);
- template > * = nullptr> void read(Type &customType);
+ template > * = nullptr> BinaryVersion read(Type &builtInType, BinaryVersion version);
+ template > * = nullptr> BinaryVersion read(Type &customType, BinaryVersion version = 0);
private:
std::unordered_map m_pointer;
};
+/// \brief The BinarySerializer class can write various data types, including custom ones, to an std::ostream.
class BinarySerializer : public CppUtilities::BinaryWriter {
friend class ::BinaryReflectorTests;
@@ -90,7 +107,8 @@ public:
template , Traits::HasSize> * = nullptr> void write(const Type &iteratable);
template > * = nullptr> void write(const Type &enumValue);
template > * = nullptr> void write(const Type &variant);
- template > * = nullptr> void write(const Type &customType);
+ template > * = nullptr> void write(const Type &builtInType, BinaryVersion version);
+ template > * = nullptr> void write(const Type &customType, BinaryVersion version = 0);
private:
std::unordered_map m_pointer;
@@ -207,9 +225,15 @@ template > *> void BinaryDeseria
Detail::readVariantValueByRuntimeIndex(readByte(), variant, *this);
}
-template > *> void BinaryDeserializer::read(Type &customType)
+template > *> BinaryVersion BinaryDeserializer::read(Type &builtInType, BinaryVersion version)
{
- readCustomType(*this, customType);
+ read(builtInType);
+ return version;
+}
+
+template > *> BinaryVersion BinaryDeserializer::read(Type &customType, BinaryVersion version)
+{
+ return readCustomType(*this, customType, version);
}
inline BinarySerializer::BinarySerializer(std::ostream *stream)
@@ -286,9 +310,15 @@ template > *> void BinarySeriali
variant);
}
-template > *> void BinarySerializer::write(const Type &customType)
+template > *> void BinarySerializer::write(const Type &builtInType, BinaryVersion version)
{
- writeCustomType(*this, customType);
+ CPP_UTILITIES_UNUSED(version)
+ write(builtInType);
+}
+
+template > *> void BinarySerializer::write(const Type &customType, BinaryVersion version)
+{
+ writeCustomType(*this, customType, version);
}
} // namespace BinaryReflector
diff --git a/lib/binary/serializable.h b/lib/binary/serializable.h
index 9e65633..ff0cd26 100644
--- a/lib/binary/serializable.h
+++ b/lib/binary/serializable.h
@@ -17,25 +17,26 @@ namespace ReflectiveRapidJSON {
/*!
* \brief The BinarySerializable class provides the CRTP-base for (de)serializable objects.
*/
-template struct BinarySerializable {
- void toBinary(std::ostream &outputStream) const;
- void restoreFromBinary(std::istream &inputStream);
+template struct BinarySerializable {
+ void toBinary(std::ostream &outputStream, BinaryVersion version = 0) const;
+ BinaryVersion restoreFromBinary(std::istream &inputStream);
static Type fromBinary(std::istream &inputStream);
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::BinarySerializable";
+ static constexpr auto version = v;
};
-template inline void BinarySerializable::toBinary(std::ostream &outputStream) const
+template inline void BinarySerializable::toBinary(std::ostream &outputStream, BinaryVersion version) const
{
- BinaryReflector::BinarySerializer(&outputStream).write(static_cast(*this));
+ BinaryReflector::BinarySerializer(&outputStream).write(static_cast(*this), version);
}
-template inline void BinarySerializable::restoreFromBinary(std::istream &inputStream)
+template inline BinaryVersion BinarySerializable::restoreFromBinary(std::istream &inputStream)
{
- BinaryReflector::BinaryDeserializer(&inputStream).read(static_cast(*this));
+ return BinaryReflector::BinaryDeserializer(&inputStream).read(static_cast(*this));
}
-template Type BinarySerializable::fromBinary(std::istream &inputStream)
+template Type BinarySerializable::fromBinary(std::istream &inputStream)
{
Type object;
static_cast &>(object).restoreFromBinary(inputStream);
diff --git a/lib/tests/binaryreflector.cpp b/lib/tests/binaryreflector.cpp
index e9258f8..50ee263 100644
--- a/lib/tests/binaryreflector.cpp
+++ b/lib/tests/binaryreflector.cpp
@@ -75,8 +75,9 @@ struct ObjectWithVariantsBinary : public BinarySerializable void readCustomType(BinaryDeserializer &deserializer, TestObjectBinary &customType)
+template <> BinaryVersion readCustomType(BinaryDeserializer &deserializer, TestObjectBinary &customType, BinaryVersion version)
{
+ CPP_UTILITIES_UNUSED(version)
deserializer.read(customType.number);
deserializer.read(customType.number2);
deserializer.read(customType.numbers);
@@ -92,10 +93,12 @@ template <> void readCustomType(BinaryDeserializer &deserializ
deserializer.read(customType.someEnumClass);
deserializer.read(customType.timeSpan);
deserializer.read(customType.dateTime);
+ return 0;
}
-template <> void writeCustomType(BinarySerializer &serializer, const TestObjectBinary &customType)
+template <> void writeCustomType(BinarySerializer &serializer, const TestObjectBinary &customType, BinaryVersion version)
{
+ CPP_UTILITIES_UNUSED(version)
serializer.write(customType.number);
serializer.write(customType.number2);
serializer.write(customType.numbers);
@@ -113,27 +116,35 @@ template <> void writeCustomType(BinarySerializer &serializer,
serializer.write(customType.dateTime);
}
-template <> void readCustomType(BinaryDeserializer &deserializer, NestingArrayBinary &customType)
+template <> BinaryVersion readCustomType(BinaryDeserializer &deserializer, NestingArrayBinary &customType, BinaryVersion version)
{
+ CPP_UTILITIES_UNUSED(version)
deserializer.read(customType.name);
deserializer.read(customType.testObjects);
+ return 0;
}
-template <> void writeCustomType(BinarySerializer &serializer, const NestingArrayBinary &customType)
+template <> void writeCustomType(BinarySerializer &serializer, const NestingArrayBinary &customType, BinaryVersion version)
{
+ CPP_UTILITIES_UNUSED(version)
serializer.write(customType.name);
serializer.write(customType.testObjects);
}
-template <> void readCustomType(BinaryDeserializer &deserializer, ObjectWithVariantsBinary &customType)
+template <>
+BinaryVersion readCustomType(BinaryDeserializer &deserializer, ObjectWithVariantsBinary &customType, BinaryVersion version)
{
+ CPP_UTILITIES_UNUSED(version)
deserializer.read(customType.someVariant);
deserializer.read(customType.anotherVariant);
deserializer.read(customType.yetAnotherVariant);
+ return 0;
}
-template <> void writeCustomType(BinarySerializer &serializer, const ObjectWithVariantsBinary &customType)
+template <>
+void writeCustomType(BinarySerializer &serializer, const ObjectWithVariantsBinary &customType, BinaryVersion version)
{
+ CPP_UTILITIES_UNUSED(version)
serializer.write(customType.someVariant);
serializer.write(customType.anotherVariant);
serializer.write(customType.yetAnotherVariant);
diff --git a/lib/tests/traits.cpp b/lib/tests/traits.cpp
index f996cc8..6689957 100644
--- a/lib/tests/traits.cpp
+++ b/lib/tests/traits.cpp
@@ -1,13 +1,27 @@
#include "../traits.h"
+#include "../versioning.h"
+
+#include "../binary/serializable.h"
#include
#include
-// treat some types differently to test Treat... traits
+// define structs for testing REFLECTIVE_RAPIDJSON_TREAT_AS_…
struct Foo {
};
struct Bar {
};
+
+// define structs for testing versioning
+struct VersionlessBase : public ReflectiveRapidJSON::BinarySerializable {
+};
+struct VersionedDerived : public VersionlessBase, public ReflectiveRapidJSON::BinarySerializable {
+};
+struct VersionedBase : public ReflectiveRapidJSON::BinarySerializable {
+};
+struct VersionlessDerived : public VersionedBase, public ReflectiveRapidJSON::BinarySerializable {
+};
+
namespace ReflectiveRapidJSON {
REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH(Foo);
REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH(Foo);
@@ -48,3 +62,14 @@ static_assert(IsIteratableExceptString>::value, "vector is iter
static_assert(!IsIteratableExceptString::value, "string not iteratable");
static_assert(!IsIteratableExceptString::value, "wstring not iteratable");
static_assert(!IsIteratableExceptString::value, "string not iteratable");
+
+// test versioning traits
+static_assert(!Versioning::enabled, "versioning for built-in types not enabled");
+static_assert(!Versioning::enabled, "versioning for standard types not enabled");
+static_assert(!Versioning::enabled, "versioning not enabled by default");
+static_assert(Versioning>::enabled, "versioning enabled if non-zero version parameter specified (derived)");
+static_assert(Versioning::enabled, "versioning enabled if non-zero version parameter specified (base)");
+static_assert(!Versioning>::enabled, "versioning disabled for derived, even if base is versioned");
+static_assert(!Versioning>::enabled, "versioning disabled if zero-version specified");
+static_assert(Versioning>::applyDefault(0) == 3, "default version returned");
+static_assert(Versioning>::applyDefault(2) == 2, "default version overridden");
diff --git a/lib/versioning.h b/lib/versioning.h
new file mode 100644
index 0000000..ec7fa39
--- /dev/null
+++ b/lib/versioning.h
@@ -0,0 +1,44 @@
+#ifndef REFLECTIVE_RAPIDJSON_VERSIONING
+#define REFLECTIVE_RAPIDJSON_VERSIONING
+
+#include
+
+namespace ReflectiveRapidJSON {
+
+#ifdef REFLECTIVE_RAPIDJSON_GENERATOR
+#define REFLECTIVE_RAPIDJSON_CAT_1(a, b) a##b
+#define REFLECTIVE_RAPIDJSON_CAT_2(a, b) REFLECTIVE_RAPIDJSON_CAT_1(a, b)
+#define REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version) \
+ static constexpr std::size_t REFLECTIVE_RAPIDJSON_CAT_2(rrjAsOfVersion, __COUNTER__) = version; \
+public
+#define REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version) \
+ static constexpr std::size_t REFLECTIVE_RAPIDJSON_CAT_2(rrjUntilVersion, __COUNTER__) = version; \
+public
+#else
+#define REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version) public
+#define REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version) public
+#endif
+
+#ifdef REFLECTIVE_RAPIDJSON_SHORT_MACROS
+#define as_of_version(version) REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version)
+#define until_version(version) REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version)
+#endif
+
+CPP_UTILITIES_TRAITS_DEFINE_TYPE_CHECK(IsVersioned, T::version);
+
+template ::value> struct Versioning {
+ static constexpr auto enabled = false;
+};
+
+template struct Versioning {
+ static constexpr auto enabled = Type::version != 0;
+ static constexpr auto serializationDefault = Type::version;
+ static constexpr auto applyDefault(decltype(serializationDefault) version)
+ {
+ return version ? version : serializationDefault;
+ }
+};
+
+} // namespace ReflectiveRapidJSON
+
+#endif // REFLECTIVE_RAPIDJSON_TRAITS