DTO: polymorphic fields.

This commit is contained in:
lganzzzo 2021-11-06 04:54:41 +02:00
parent fbf26c9c3c
commit 11e80b8a4a
9 changed files with 363 additions and 10 deletions

View File

@ -84,7 +84,8 @@ static bool Z__PROPERTY_INIT_##NAME(... /* default initializer for all cases */)
} \
\
static TYPE Z__PROPERTY_INITIALIZER_PROXY_##NAME() { \
static bool initialized = Z__PROPERTY_INIT_##NAME(1 /* init info if found */); \
static bool initialized = Z__PROPERTY_INIT_##NAME(1 /* init info if found */, \
1 /* init type selector if found */); \
(void)initialized; \
return TYPE(); \
} \
@ -114,7 +115,8 @@ static bool Z__PROPERTY_INIT_##NAME(... /* default initializer for all cases */)
} \
\
static TYPE Z__PROPERTY_INITIALIZER_PROXY_##NAME() { \
static bool initialized = Z__PROPERTY_INIT_##NAME(1 /* init info if found */); \
static bool initialized = Z__PROPERTY_INIT_##NAME(1 /* init info if found */, \
1 /* init type selector if found */); \
(void)initialized; \
return TYPE(); \
} \
@ -134,7 +136,7 @@ OATPP_MACRO_EXPAND(OATPP_MACRO_MACRO_SELECTOR(OATPP_MACRO_DTO_FIELD_, (__VA_ARGS
#define DTO_FIELD_INFO(NAME) \
\
static bool Z__PROPERTY_INIT_##NAME(int) { \
static bool Z__PROPERTY_INIT_##NAME(int, ...) { \
Z__PROPERTY_INIT_##NAME(); /* call first initialization */ \
Z__PROPERTY_ADD_INFO_##NAME(&Z__PROPERTY_SINGLETON_##NAME()->info); \
return true; \
@ -142,6 +144,24 @@ static bool Z__PROPERTY_INIT_##NAME(int) { \
\
static void Z__PROPERTY_ADD_INFO_##NAME(oatpp::data::mapping::type::BaseObject::Property::Info* info)
#define DTO_FIELD_TYPE_SELECTOR(NAME) \
\
class Z__PROPERTY_TYPE_SELECTOR_##NAME : public oatpp::BaseObject::Property::TypeSelector { \
public: \
const oatpp::Type* selectType(oatpp::BaseObject *self) override { \
return Z__PROPERTY_TYPE_SELECTOR_METHOD_##NAME(static_cast<Z__CLASS*>(self)); \
} \
}; \
\
static bool Z__PROPERTY_INIT_##NAME(int, int) { \
Z__PROPERTY_INIT_##NAME(1); /* call property info initialization */ \
Z__PROPERTY_SINGLETON_##NAME()->info.typeSelector = new Z__PROPERTY_TYPE_SELECTOR_##NAME(); \
return true; \
} \
\
static oatpp::Type* Z__PROPERTY_TYPE_SELECTOR_METHOD_##NAME(Z__CLASS* self)
// FOR EACH
#define OATPP_MACRO_DTO_HC_EQ_PARAM_HC(INDEX, COUNT, X) \

View File

@ -36,6 +36,10 @@
#undef DTO_FIELD_INFO
// Type Selector
#undef DTO_FIELD_TYPE_SELECTOR
// Hashcode & Equals
#undef OATPP_MACRO_DTO_HC_EQ_PARAM_HC

View File

@ -39,6 +39,12 @@ namespace oatpp {
*/
typedef oatpp::data::mapping::type::ClassId ClassId;
/**
* ObjectWrapper.
*/
template <class T, class Clazz = oatpp::data::mapping::type::__class::Void>
using ObjectWrapper = oatpp::data::mapping::type::ObjectWrapper<T, Clazz>;
/**
* ObjectWrapper over the `void*`.
*/

View File

@ -280,7 +280,7 @@ public:
*/
template<class OtherInter>
EnumObjectWrapper(const EnumObjectWrapper<T, OtherInter>& other)
: type::ObjectWrapper<T, EnumObjectClass>(other.getPtr())
: type::ObjectWrapper<T, EnumObjectClass>(other.m_ptr)
{}
/**
@ -290,7 +290,7 @@ public:
*/
template<class OtherInter>
EnumObjectWrapper(EnumObjectWrapper<T, OtherInter>&& other)
: type::ObjectWrapper<T, EnumObjectClass>(std::move(other.getPtr()))
: type::ObjectWrapper<T, EnumObjectClass>(std::move(other.m_ptr))
{}
inline EnumObjectWrapper& operator = (std::nullptr_t) {
@ -306,7 +306,7 @@ public:
template<class OtherInter>
inline EnumObjectWrapper& operator = (EnumObjectWrapper<T, OtherInter>&& other) {
this->m_ptr = std::forward<std::shared_ptr<T>>(other.m_ptr);
this->m_ptr = std::move(other.m_ptr);
return *this;
}

View File

@ -53,18 +53,50 @@ public:
* Class to map object properties.
*/
class Property {
public:
/**
* Property Type Selector.
*/
class TypeSelector {
public:
/**
* Select property type.
* @param self - pointer to `this` object.
* @return - &id:oatpp::Type;.
*/
virtual const type::Type* selectType(BaseObject* self) = 0;
};
public:
/**
* Editional Info about Property.
*/
struct Info {
/**
* Description.
*/
std::string description = "";
/**
* Pattern.
*/
std::string pattern = "";
/**
* Required.
*/
bool required = false;
/**
* Type selector.
* &l:Property::TypeSelector;.
*/
TypeSelector* typeSelector = nullptr;
};
private:
@ -271,6 +303,8 @@ namespace __class {
*/
template<class ObjT>
class DTOWrapper : public ObjectWrapper<ObjT, __class::Object<ObjT>> {
template<class Type>
friend class DTOWrapper;
public:
typedef ObjT TemplateObjectType;
typedef __class::Object<ObjT> TemplateObjectClass;
@ -278,10 +312,32 @@ public:
OATPP_DEFINE_OBJECT_WRAPPER_DEFAULTS(DTOWrapper, TemplateObjectType, TemplateObjectClass)
template<class OtherT>
DTOWrapper(const OtherT& other)
: type::ObjectWrapper<ObjT, __class::Object<ObjT>>(other.m_ptr)
{}
template<class OtherT>
DTOWrapper(OtherT&& other)
: type::ObjectWrapper<ObjT, __class::Object<ObjT>>(std::move(other.m_ptr))
{}
static DTOWrapper createShared() {
return std::make_shared<TemplateObjectType>();
}
template<class T>
DTOWrapper& operator = (const DTOWrapper<T>& other) {
this->m_ptr = other.m_ptr;
return *this;
}
template<class T>
DTOWrapper& operator = (DTOWrapper<T>&& other) {
this->m_ptr = std::move(other.m_ptr);
return *this;
}
template<typename T,
typename enabled = typename std::enable_if<std::is_same<T, std::nullptr_t>::value, void>::type
>
@ -312,6 +368,31 @@ public:
return !operator == (other);
}
static const std::unordered_map<std::string, BaseObject::Property*>& getPropertiesMap() {
auto dispatcher = static_cast<const __class::AbstractObject::PolymorphicDispatcher*>(
__class::Object<ObjT>::getType()->polymorphicDispatcher
);
return dispatcher->getProperties()->getMap();
}
static const std::list<BaseObject::Property*>& getPropertiesList() {
auto dispatcher = static_cast<const __class::AbstractObject::PolymorphicDispatcher*>(
__class::Object<ObjT>::getType()->polymorphicDispatcher
);
return dispatcher->getProperties()->getList();
}
static v_int64 getPropertiesCount() {
auto dispatcher = static_cast<const __class::AbstractObject::PolymorphicDispatcher*>(
__class::Object<ObjT>::getType()->polymorphicDispatcher
);
return dispatcher->getProperties()->getList().size();
}
ObjectWrapper<void>& operator[](const std::string& propertyName) {
return getPropertiesMap().at(propertyName)->getAsRef(this->m_ptr.get());
}
};
/**

View File

@ -119,6 +119,10 @@ class Void; // FWD
template <class T, class Clazz = __class::Void>
class ObjectWrapper {
friend Void;
template <class Q, class W>
friend class ObjectWrapper;
protected:
static void checkType(const Type* _this, const Type* other);
protected:
std::shared_ptr<T> m_ptr;
const Type* m_valueType;
@ -174,12 +178,40 @@ public:
, m_valueType(other.m_valueType)
{}
template <class Q, class W>
ObjectWrapper(const ObjectWrapper<Q, W>& other)
: m_ptr(other.m_ptr)
, m_valueType(other.m_valueType)
{}
template <class Q, class W>
ObjectWrapper(ObjectWrapper<Q, W>&& other)
: m_ptr(std::move(other.m_ptr))
, m_valueType(other.m_valueType)
{}
inline ObjectWrapper& operator=(const ObjectWrapper& other){
checkType(m_valueType, other.m_valueType);
m_ptr = other.m_ptr;
return *this;
}
inline ObjectWrapper& operator=(ObjectWrapper&& other){
checkType(m_valueType, other.m_valueType);
m_ptr = std::move(other.m_ptr);
return *this;
}
template <class Q, class W>
inline ObjectWrapper& operator=(const ObjectWrapper<Q, W>& other){
checkType(m_valueType, other.m_valueType);
m_ptr = other.m_ptr;
return *this;
}
template <class Q, class W>
inline ObjectWrapper& operator=(ObjectWrapper<Q, W>&& other){
checkType(m_valueType, other.m_valueType);
m_ptr = std::move(other.m_ptr);
return *this;
}
@ -197,7 +229,7 @@ public:
return m_ptr.get();
}
void setPtr(const std::shared_ptr<T>& ptr) {
void resetPtr(const std::shared_ptr<T>& ptr = nullptr) {
m_ptr = ptr;
}
@ -261,6 +293,14 @@ public:
: type::ObjectWrapper<void, __class::Void>(std::forward<std::shared_ptr<void>>(ptr))
{}
Void(const Void& other)
: type::ObjectWrapper<void, __class::Void>(other.getPtr(), other.getValueType())
{}
Void(Void&& other)
: type::ObjectWrapper<void, __class::Void>(std::move(other.getPtr()), other.getValueType())
{}
template<typename T, typename C>
Void(const ObjectWrapper<T, C>& other)
: type::ObjectWrapper<void, __class::Void>(other.getPtr(), other.getValueType())
@ -279,6 +319,18 @@ public:
return *this;
}
inline Void& operator = (const Void& other){
m_ptr = other.m_ptr;
m_valueType = other.getValueType();
return *this;
}
inline Void& operator = (Void&& other){
m_ptr = std::move(other.m_ptr);
m_valueType = other.getValueType();
return *this;
}
template<typename T, typename C>
inline Void& operator = (const ObjectWrapper<T, C>& other){
m_ptr = other.m_ptr;
@ -483,6 +535,15 @@ public:
};
template <class T, class Clazz>
void ObjectWrapper<T, Clazz>::checkType(const Type* _this, const Type* other) {
if(!_this->extends(other)) {
throw std::runtime_error("[oatpp::data::mapping::type::ObjectWrapper::checkType()]: Error. "
"Type mismatch: stored '" + std::string(_this->classId.name) + "' vs "
"assigned '" + std::string(other->classId.name) + "'.");
}
}
#define OATPP_DEFINE_OBJECT_WRAPPER_DEFAULTS(WRAPPER_NAME, OBJECT_TYPE, OBJECT_CLASS) \
public: \
WRAPPER_NAME(const std::shared_ptr<OBJECT_TYPE>& ptr, const type::Type* const valueType) \
@ -521,7 +582,7 @@ public: \
} \
\
inline WRAPPER_NAME& operator = (WRAPPER_NAME&& other) { \
this->m_ptr = std::forward<std::shared_ptr<OBJECT_TYPE>>(other.m_ptr); \
this->m_ptr = std::move(other.m_ptr); \
return *this; \
} \

View File

@ -301,6 +301,7 @@ oatpp::Void Deserializer::deserializeObject(Deserializer* deserializer, parser::
caret.skipBlankChars();
std::vector<std::pair<oatpp::BaseObject::Property*, oatpp::String>> polymorphs;
while (!caret.isAtChar('}') && caret.canContinue()) {
caret.skipBlankChars();
@ -321,7 +322,14 @@ oatpp::Void Deserializer::deserializeObject(Deserializer* deserializer, parser::
caret.skipBlankChars();
auto field = fieldIterator->second;
field->set(static_cast<oatpp::BaseObject*>(object.get()), deserializer->deserialize(caret, field->type));
if(field->info.typeSelector) {
auto label = caret.putLabel();
skipValue(caret);
polymorphs.emplace_back(field, label.toString()); // store polymorphs for later processing.
} else {
field->set(static_cast<oatpp::BaseObject *>(object.get()), deserializer->deserialize(caret, field->type));
}
} else if (deserializer->getConfig()->allowUnknownFields) {
caret.skipBlankChars();
@ -348,6 +356,13 @@ oatpp::Void Deserializer::deserializeObject(Deserializer* deserializer, parser::
return nullptr;
}
for(auto& p : polymorphs) {
parser::Caret polyCaret(p.second);
auto selectedType = p.first->info.typeSelector->selectType(static_cast<oatpp::BaseObject *>(object.get()));
auto value = deserializer->deserialize(polyCaret, selectedType);
p.first->set(static_cast<oatpp::BaseObject *>(object.get()), oatpp::Void(value.getPtr(), p.first->type));
}
return object;
} else {

View File

@ -158,7 +158,13 @@ void Serializer::serializeObject(Serializer* serializer,
for (auto const& field : fields) {
auto value = field->get(object);
oatpp::Void value;
if(field->info.typeSelector) {
value = oatpp::Void(field->get(object).getPtr(), field->info.typeSelector->selectType(object));
} else {
value = field->get(object);
}
if (value || config->includeNullFields || (field->info.required && config->alwaysIncludeRequired)) {
(first) ? first = false : stream->writeSimple(",", 1);
serializeString(stream, field->name, std::strlen(field->name), serializer->m_config->escapeFlags);

View File

@ -24,6 +24,8 @@
#include "ObjectTest.hpp"
#include "oatpp/parser/json/mapping/ObjectMapper.hpp"
#include "oatpp/core/macro/codegen.hpp"
#include "oatpp/core/Types.hpp"
@ -94,6 +96,56 @@ class DtoD : public DtoA {
};
class DtoTypeA : public oatpp::DTO {
DTO_INIT(DtoTypeA, DTO)
DTO_FIELD(String, fieldA) = "type-A";
};
class DtoTypeB : public oatpp::DTO {
DTO_INIT(DtoTypeB, DTO)
DTO_FIELD(String, fieldB) = "type-B";
};
class PolymorphicDto1 : public oatpp::DTO {
DTO_INIT(PolymorphicDto1, DTO)
DTO_FIELD(String, type);
DTO_FIELD(Object<DTO>, polymorph);
DTO_FIELD_TYPE_SELECTOR(polymorph) {
if(self->type == "A") return Object<DtoTypeA>::Class::getType();
if(self->type == "B") return Object<DtoTypeB>::Class::getType();
return Object<DTO>::Class::getType();
}
};
class PolymorphicDto2 : public oatpp::DTO {
DTO_INIT(PolymorphicDto2, DTO)
DTO_FIELD(String, type);
DTO_FIELD_INFO(polymorph) {
info->description = "description";
}
DTO_FIELD(Object<DTO>, polymorph);
DTO_FIELD_TYPE_SELECTOR(polymorph) {
if(self->type == "A") return Object<DtoTypeA>::Class::getType();
if(self->type == "B") return Object<DtoTypeB>::Class::getType();
return Object<DTO>::Class::getType();
}
};
#include OATPP_CODEGEN_END(DTO)
void runDtoInitializations() {
@ -331,6 +383,114 @@ void ObjectTest::onRun() {
OATPP_LOGI(TAG, "OK");
}
{
OATPP_LOGI(TAG, "Test 11...");
auto map = oatpp::Object<PolymorphicDto1>::getPropertiesMap();
auto p = map["polymorph"];
OATPP_ASSERT(p->info.description == "");
OATPP_ASSERT(p->info.typeSelector != nullptr);
OATPP_LOGI(TAG, "OK");
}
{
OATPP_LOGI(TAG, "Test 12...");
auto map = oatpp::Object<PolymorphicDto2>::getPropertiesMap();
auto p = map["polymorph"];
OATPP_ASSERT(p->info.description == "description");
OATPP_ASSERT(p->info.typeSelector != nullptr);
OATPP_LOGI(TAG, "OK");
}
{
OATPP_LOGI(TAG, "Test 13...");
auto dto = oatpp::Object<PolymorphicDto2>::createShared();
OATPP_ASSERT(dto->type == nullptr)
OATPP_ASSERT(dto->type.getValueType() == oatpp::String::Class::getType())
OATPP_ASSERT(dto["type"] == nullptr)
OATPP_ASSERT(dto["type"].getValueType() == oatpp::String::Class::getType())
dto["type"] = oatpp::String("hello");
OATPP_ASSERT(dto->type == "hello");
OATPP_ASSERT(dto->type.getValueType() == oatpp::String::Class::getType())
OATPP_LOGI(TAG, "OK");
}
{
OATPP_LOGI(TAG, "Test 14...");
auto dto = oatpp::Object<PolymorphicDto2>::createShared();
bool thrown = false;
try{
dto["type"] = oatpp::Int32(32);
} catch(std::runtime_error e) {
OATPP_LOGD(TAG, "error='%s'", e.what());
thrown = true;
}
OATPP_ASSERT(thrown)
OATPP_LOGI(TAG, "OK");
}
{
OATPP_LOGI(TAG, "Test 15...");
auto dto = oatpp::Object<PolymorphicDto2>::createShared();
bool thrown = false;
try{
dto["non-existing"];
} catch(std::out_of_range e) {
OATPP_LOGD(TAG, "error='%s'", e.what());
thrown = true;
}
OATPP_ASSERT(thrown)
OATPP_LOGI(TAG, "OK");
}
{
OATPP_LOGI(TAG, "Test 16...");
oatpp::parser::json::mapping::ObjectMapper mapper;
auto dto = PolymorphicDto1::createShared();
dto->type = "A";
dto->polymorph = DtoTypeA::createShared();
OATPP_ASSERT(dto->polymorph.getValueType() == oatpp::Object<oatpp::DTO>::Class::getType())
auto json = mapper.writeToString(dto);
OATPP_LOGD(TAG, "json0='%s'", json->c_str())
auto dtoClone = mapper.readFromString<oatpp::Object<PolymorphicDto1>>(json);
auto jsonClone = mapper.writeToString(dtoClone);
OATPP_LOGD(TAG, "json1='%s'", jsonClone->c_str())
OATPP_ASSERT(json == jsonClone)
OATPP_ASSERT(dtoClone->polymorph)
OATPP_ASSERT(dtoClone->polymorph.getValueType() == oatpp::Object<oatpp::DTO>::Class::getType())
auto polymorphClone = dtoClone->polymorph.staticCast<oatpp::Object<DtoTypeA>>();
OATPP_ASSERT(polymorphClone->fieldA == "type-A")
OATPP_LOGI(TAG, "OK");
}
}
}}}}}}