#pragma once
// DOCUMENTATION: https://confluence.gameloft.org/display/OT2/OT+-+Framework%3A+Serialization
#include <RKJson.h>
#include <stack>
#include <string>
#include <type_traits>
// Base class for Json IO serializers.
// This only exists so that we can cram writer and reader together in our API as we did in Zombie
class JsonSerializerBase
{
public:
virtual bool IsWriter() const = 0;
virtual bool PushFrame(const char* name) = 0;
virtual bool PushFrame(size_t index) = 0;
virtual void PopFrame() = 0;
virtual bool BeginRootObject() { return true; }
virtual bool EndRootObject() { return true; }
// #TODO can probably use root object here as well?
virtual bool BeginNestedObject() { return BeginRootObject(); }
virtual bool EndNestedObject() { return EndRootObject(); }
virtual void MemberProcessed(std::string&& tag, bool successfully) {}
enum SerializerError
{
Error_TypeMismatch,
Error_MissingMember, // Member missing in the data
Error_ExtraMember, // Member missing in the code
Error_Unknown,
};
struct ErrorRecord
{
SerializerError Type;
std::string Details;
};
void RecordError(SerializerError type, std::string&& details)
{
m_Errors.emplace_back(ErrorRecord { type, std::move(details) });
}
bool HasErrors() const { return !m_Errors.empty(); }
const std::vector<ErrorRecord>& GetErrors() const { return m_Errors; }
// #TODO OM: this is just a quick solution to get us up and running, revisit this
struct Metadata {};
JsonSerializerBase(Metadata* metadata) : m_Metadata(metadata) {}
template <class T>
T* GetMetadata() { return static_cast<T*>(m_Metadata); }
private:
std::vector<ErrorRecord> m_Errors;
Metadata* m_Metadata;
};
// Serializer to write values into Json format
class JsonWriter : public JsonSerializerBase
{
public:
JsonWriter(bne::JsonValueRef json, JsonSerializerBase::Metadata* metadata = nullptr)
: JsonSerializerBase(metadata)
{
m_Frames.push(json);
}
bool IsWriter() const override { return true; }
bne::JsonValueRef CurrentFrame()
{
return m_Frames.top();
}
bool PushFrame(const char* name) override
{
bne::JsonValueRef frame = CurrentFrame()[name];
m_Frames.push(bne::JsonValueRef::null);
m_Frames.top().Assign(frame);
return true;
}
bool PushFrame(size_t index) override
{
assert(CurrentFrame().isArray());
bne::JsonValueRef frame = CurrentFrame().appendEmpty();
m_Frames.push(bne::JsonValueRef::null);
m_Frames.top().Assign(frame);
return true;
}
void PopFrame() override
{
m_Frames.pop();
}
private:
std::stack<bne::JsonValueRef> m_Frames;
};
// Serializer to read values from Json format
class JsonReader : public JsonSerializerBase
{
public:
JsonReader(bne::JsonValueConstRef json, JsonSerializerBase::Metadata* metadata = nullptr, bool enableMigration = true)
: JsonSerializerBase(metadata), m_EnableMigration(enableMigration)
{
m_Frames.push(json);
}
bool IsWriter() const override { return false; }
bne::JsonValueConstRef CurrentFrame()
{
return m_Frames.top();
}
bool PushFrame(const char* name) override
{
if (CurrentFrame().isObject())
{
if (CurrentFrame().hasMember(name))
{
bne::JsonValueConstRef frame = CurrentFrame()[name];
m_Frames.push(bne::JsonValueConstRef::null);
m_Frames.top().Assign(frame);
return true;
}
else
{
RecordError(JsonSerializerBase::Error_MissingMember, "Requested member could not be found: " + std::string(name));
return false;
}
}
else
{
RecordError(JsonSerializerBase::Error_TypeMismatch, "Expected an object for member: " + std::string(name));
return false;
}
}
bool PushFrame(size_t index) override
{
if (CurrentFrame().isArray())
{
if (CurrentFrame().size() > index)
{
bne::JsonValueConstRef frame = CurrentFrame()[index];
m_Frames.push(bne::JsonValueConstRef::null);
m_Frames.top().Assign(frame);
return true;
}
else
{
RecordError(JsonSerializerBase::Error_Unknown, "Invalid array index: " + std::to_string(index));
return false;
}
}
else
{
RecordError(JsonSerializerBase::Error_TypeMismatch, "Expected an array");
return false;
}
}
void PopFrame() override
{
m_Frames.pop();
}
bool BeginRootObject() override
{
bne::JsonValueConstRef root = CurrentFrame();
if (!root.isObject())
{
RecordError(JsonSerializerBase::Error_TypeMismatch, "Expected an object");
return false;
}
if (IsMigrationEnabled())
{
MigrationInfoFrame frame;
frame.OriginalMembers = root.getMemberNames();
m_MigrationFrames.push(std::move(frame));
}
return true;
}
bool EndRootObject() override
{
if (IsMigrationEnabled())
{
MigrationInfoFrame& currentFrame = m_MigrationFrames.top();
bool success = true;
if (!currentFrame.OriginalMembers.empty())
{
std::string err("The following members were not consumed by serialization: ");
for (std::string& mem : currentFrame.OriginalMembers)
{
err += mem + ", ";
}
RecordError(JsonSerializerBase::Error_ExtraMember, std::move(err));
success = false;
}
if (!currentFrame.NewMembers.empty())
{
std::string err("The following new members were not migrated by serialization: ");
for (std::string& mem : currentFrame.NewMembers)
{
err += mem + ", ";
}
RecordError(JsonSerializerBase::Error_MissingMember, std::move(err));
success = false;
}
m_MigrationFrames.pop();
return success;
}
return true;
}
void MemberProcessed(std::string&& tag, bool successfully) override
{
if (IsMigrationEnabled())
{
MigrationInfoFrame& currentFrame = m_MigrationFrames.top();
auto it = std::find(currentFrame.OriginalMembers.begin(), currentFrame.OriginalMembers.end(), tag); // NOLINT(c++11/auto)
if (successfully)
{
// If the field was successfully read, we can remove it
assert(it != currentFrame.OriginalMembers.end());
if ((it != currentFrame.OriginalMembers.end()))
{
currentFrame.OriginalMembers.erase(it);
}
}
else
{
// There might have been a general error reading the value, or this might be a brand new field that doesn't even
// exist in the data. In any case, migration will have to deal with it
if ((it == currentFrame.OriginalMembers.end()))
{
currentFrame.NewMembers.emplace_back(std::move(tag));
}
}
}
}
void MigrateWaiveExistingMember(std::string&& tag)
{
if (IsMigrationEnabled())
{
MigrationInfoFrame& currentFrame = m_MigrationFrames.top();
auto it = std::find(currentFrame.OriginalMembers.begin(), currentFrame.OriginalMembers.end(), tag); // NOLINT(c++11/auto)
if (it != currentFrame.OriginalMembers.end())
{
currentFrame.OriginalMembers.erase(it);
}
}
}
void MigrateWaiveNewMember(std::string&& tag)
{
if (IsMigrationEnabled())
{
MigrationInfoFrame& currentFrame = m_MigrationFrames.top();
auto it = std::find(currentFrame.NewMembers.begin(), currentFrame.NewMembers.end(), tag); // NOLINT(c++11/auto)
if (it != currentFrame.NewMembers.end())
{
currentFrame.NewMembers.erase(it);
}
}
}
bool IsMigrationEnabled() const { return m_EnableMigration; }
private:
std::stack<bne::JsonValueConstRef> m_Frames;
bool m_EnableMigration = false;
struct MigrationInfoFrame
{
std::vector<std::string> OriginalMembers;
std::vector<std::string> NewMembers;
};
std::stack<MigrationInfoFrame> m_MigrationFrames;
};
// Base for all serialized objects
class SerializedBase
{
public:
bool Serialize(JsonSerializerBase& serializer);
protected:
virtual bool SerializeInternal(JsonSerializerBase& serializer) { return true; }
};
// Function to handle non-class object serialization
template <typename T>
bool JsonSerialize(T& value, JsonSerializerBase& serializer);
// #TODO OM: make this work for transparency's sake?
//bool JsonSerialize(SerializedBase*& value, JsonSerializerBase& serializer);
// For unknown types, we'll first try to serialize as a nested SerializedBase instance
template <typename T, bool isSerializedBase>
struct UnknownTypeSerializer
{
static_assert(std::is_base_of<SerializedBase, T>::value, "Serialization not implemented for this type!");
static bool Write(const T& value, JsonWriter& writer)
{
const SerializedBase& serialized = value;
return const_cast<SerializedBase&>(serialized).Serialize(writer); // #TODO OM: remove the const_cast
}
static bool Read(T& value, JsonReader& reader)
{
SerializedBase& serialized = value;
return serialized.Serialize(reader);
}
};
// As a last resort, we'll try to look for a POD serialization function and let the linker fail if it cannot be found
template <typename T>
struct UnknownTypeSerializer<T, false>
{
static bool Write(const T& value, JsonWriter& writer)
{
return JsonSerialize(const_cast<T&>(value), writer); // #TODO OM: remove the const_cast
}
static bool Read(T& value, JsonReader& reader)
{
return JsonSerialize(value, reader);
}
};
// Generic value serializer acts as a fallback option for unknown types
// It will try to serialize the type as SerializedBase if possible, otherwise a compile-time error
// will be emitted
template <typename T>
struct ValueSerializer
{
static bool Write(const T& value, JsonWriter& writer)
{
return UnknownTypeSerializer<T, std::is_base_of<SerializedBase, T>::value>::Write(value, writer);
}
static bool Read(T& value, JsonReader& reader)
{
return UnknownTypeSerializer<T, std::is_base_of<SerializedBase, T>::value>::Read(value, reader);
}
};
// Helper to call the appropriate Read or Write serialization function based on the type of the serializer provided
template <typename T>
struct SerializerDispatch
{
static bool Process(T& value, const char* tag, JsonSerializerBase& serializer)
{
bool success = false;
if (serializer.IsWriter())
{
JsonWriter& writer = static_cast<JsonWriter&>(serializer);
if (writer.PushFrame(tag))
{
success = ValueSerializer<T>::Write(value, writer);
writer.PopFrame();
}
}
else
{
JsonReader& reader = static_cast<JsonReader&>(serializer);
if (reader.PushFrame(tag))
{
success = ValueSerializer<T>::Read(value, reader);
reader.PopFrame();
}
}
if (!success)
{
serializer.RecordError(JsonSerializerBase::Error_Unknown, "Failed serializing member: " + std::string(tag));
}
serializer.MemberProcessed(tag, success);
return success;
}
};
// Use this to declare serialization in a class
#define JSON_SERIALIZED() \
protected: \
bool SerializeInternal(JsonSerializerBase& serializer) override;
// Use this to begin serialization definition in a .cpp
// for classes that derive directly from SerializedBase
#define JSON_SERIALIZE_BASE_CLASS(Type) \
JSON_SERIALIZE_CLASS_BEGIN(Type, SerializedBase)
// Use this to begin serialization definition in a .cpp
// for classes that derive from children of SerializedBase
#define JSON_SERIALIZE_CLASS(Type, Base) \
JSON_SERIALIZE_CLASS_BEGIN(Type, Base)
#define JSON_SERIALIZE_CLASS_BEGIN(Type, Base) \
bool Type::SerializeInternal(JsonSerializerBase& serializer) \
{ \
Type& _valueToSerialize = *this; \
static const bool _isStruct = false; \
bool success = Base::SerializeInternal(serializer);
#define JSON_SERIALIZE_STRUCT(Type) \
template <> \
bool JsonSerialize(Type& _valueToSerialize, JsonSerializerBase& serializer) \
{ \
static const bool _isStruct = true; \
bool success = serializer.BeginRootObject();
// Use this to end a serialization block previously started with JSON_SERIALIZE_BEGIN()
// In the struct case, is this intentionally ignoring the success variable?
#define JSON_SERIALIZE_END(...) \
return _isStruct ? serializer.EndRootObject() : success; \
}
// Use this to serialize a member of a class
#define JSON_SERIALIZE(Member, Tag) success &= SerializerDispatch<decltype(_valueToSerialize.Member)>::Process(_valueToSerialize.Member, Tag, serializer);
// Use this if you want to provide a custom (de)serialization function for a member
// Expected function signature is 'bool Fn(<Member Type> member, const char* tag, JsonSerializerBase& serializer)'
#define JSON_SERIALIZE_CUSTOM(Member, Tag, Fn) \
{ \
bool _scc = Fn(_valueToSerialize.Member, Tag, serializer); \
if (!_scc) serializer.RecordError(JsonSerializerBase::Error_Unknown, "User defined serialization function has returned an error"); \
serializer.MemberProcessed(Tag, _scc); \
success &= _scc; \
}
// Use these macros to enclose members that should reside inside a named scope in the serialized result
#define JSON_AREA_BEGIN(Name) \
if (serializer.PushFrame(Name)) \
{ \
serializer.MemberProcessed(Name, true); /* #TODO OM: Strictly speaking, we should move this to the END macro, but it works just fine here ATM */ \
if (serializer.BeginNestedObject()) \
{
#define JSON_AREA_END() \
serializer.EndNestedObject(); \
} \
serializer.PopFrame(); \
} \
else \
{ \
serializer.RecordError(JsonSerializerBase::Error_Unknown, "Failed to process json area"); \
success = false; \
}
// Begin a migration block inside serialization definition
#define JSON_MIGRATE_BEGIN(CurrentVersion) \
if (serializer.IsWriter()) \
{ \
static_cast<JsonWriter&>(serializer).CurrentFrame()["_version"] = CurrentVersion; \
} \
else \
{ \
JsonReader& _migrationReader = static_cast<JsonReader&>(serializer); \
int _dataVersion = 0; /* #TODO OM: CurrentVersion? */ \
if (_migrationReader.CurrentFrame().hasMember("_version")) \
{ \
_dataVersion = _migrationReader.CurrentFrame()["_version"].asInt(); \
_migrationReader.MigrateWaiveExistingMember("_version"); \
} \
if (CurrentVersion != _dataVersion && _migrationReader.IsMigrationEnabled()) \
{ \
bne::JsonValue _migrationJson = _migrationReader.CurrentFrame(); \
switch (_dataVersion) \
{
// Begin migration to the given version
#define JSON_MIGRATE_TO_VERSION(Version) \
case (Version - 1):; // the ';' is there to avoid an empty case warning
#define JSON_MIGRATE_END() \
} \
} \
}
// Migration helpers
// The reader used during migration. Same as static_cast<JsonReader&>(serializer)
#define MG_READER _migrationReader
// Reference to the deserialized target value
#define MG_VALUE _valueToSerialize
// A write-enabled copy of the source data that can be used during migration
#define MG_JSON _migrationJson
// A read-only version of the source data
#define MG_JSON_SRC MG_READER.CurrentFrame()
// Mark a member that was added in the current version as migrated
#define MG_WAIVE_NEW_MEMBER(Tag) MG_READER.MigrateWaiveNewMember(Tag);
// Mark a member that was removed or changed in the current version as migrated
#define MG_WAIVE_FAILED_MEMBER(Tag) MG_READER.MigrateWaiveExistingMember(Tag);
// Helper to set a new member and waive the error in one call
#define MG_NEW_MEMBER(Member, Tag, Value) MG_VALUE.Member = Value; MG_WAIVE_NEW_MEMBER(Tag);
struct JsonStructSizeLock
{
JsonStructSizeLock(size_t size)
{
TotalSize += size;
}
static size_t TotalSize;
};
// Structure consistency check
#define JSON_LOCK_OBJECT_SIZE(Type, Size) \
namespace \
{ \
template <size_t ExpectedSize = Size, size_t ActualSize = sizeof(Type)> \
struct Type##_SizeLock : JsonStructSizeLock \
{ \
Type##_SizeLock() : JsonStructSizeLock(ActualSize) {} \
static_assert(ActualSize == ExpectedSize, "Locked data type size changed, please bump up the version, implement migration, then update the size in JSON_LOCK_OBJECT_SIZE!"); \
}; \
static const Type##_SizeLock<> g_##Type##SizeLock; \
}
#include "SerializerTypes.inl"