From 03ec6465b5038a4a875dc419f0d0ea70878cfe06 Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Tue, 25 May 2021 18:49:06 -0700 Subject: [PATCH] Support deserializing non-reflected enums (#815) The serialize context allows users to reflect fields that are enums to a class without reflecting the enum type itself with the EnumBuilder. In this case, the serialize context stores the mapping of the enum's typeid to the underlying type's typeid. When asking for the class data for the enum typeid, the underlying type's class data is returned. This was throwing off the json serializer, which would then see that the type was "unsigned int" instead of an enum, and attempt to load the unsigned int value. The unsigned int deserializer would then complain, because the incoming typeid was the typeid of the enum, and not equal to the typeid of unsigned int. This change adds support for detecting the non-reflected enum, and loading it properly. --- .../Serialization/Json/JsonDeserializer.cpp | 31 +++++++---- .../Tests/Serialization/Json/TestCases.h | 2 +- .../Serialization/Json/TestCases_Classes.cpp | 51 +++++++++++++++++++ .../Serialization/Json/TestCases_Classes.h | 31 +++++++++++ 4 files changed, 105 insertions(+), 10 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.cpp index 93d12acba3..9c4641741e 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.cpp @@ -10,6 +10,7 @@ * */ +#include "AzCore/RTTI/TypeInfo.h" #include #include #include @@ -61,6 +62,13 @@ namespace AZ if (classData->m_azRtti && classData->m_azRtti->GetGenericTypeId() != typeId) { + if (((classData->m_azRtti->GetTypeTraits() & (AZ::TypeTraits::is_signed | AZ::TypeTraits::is_unsigned)) != AZ::TypeTraits{0}) && + context.GetSerializeContext()->GetUnderlyingTypeId(typeId) == classData->m_typeId) + { + // This value is from an enum, where a field has been reflected using ClassBuilder::Field, but the enum + // type itself has not been reflected using EnumBuilder. Treat it as an enum. + return LoadEnum(object, *classData, value, context); + } serializer = context.GetRegistrationContext()->GetSerializerForType(classData->m_azRtti->GetGenericTypeId()); if (serializer) { @@ -77,21 +85,18 @@ namespace AZ { return LoadEnum(object, *classData, value, context); } - else if (classData->m_container) + if (classData->m_container) { return context.Report(Tasks::ReadField, Outcomes::Unsupported, "The Json Serializer uses custom serializers to load containers. If this message is encountered " "then a serializer for the target containers is missing, isn't registered or doesn't exist."); } - else if (value.IsObject()) + if (value.IsObject()) { return LoadClass(object, *classData, value, context); } - else - { - return context.Report(Tasks::ReadField, Outcomes::Unsupported, - AZStd::string::format("Reading into targets of type '%s' is not supported.", classData->m_name)); - } + return context.Report(Tasks::ReadField, Outcomes::Unsupported, + AZStd::string::format("Reading into targets of type '%s' is not supported.", classData->m_name)); } JsonSerializationResult::ResultCode JsonDeserializer::LoadToPointer(void* object, const Uuid& typeId, @@ -233,8 +238,16 @@ namespace AZ AZ::TypeId underlyingTypeId = AZ::TypeId::CreateNull(); if (!attributeReader.Read(underlyingTypeId)) { - return context.Report(Tasks::RetrieveInfo, Outcomes::Unknown, - "Unable to find underlying type of enum in class data."); + // for non-reflected enums, the passed-in classData already represents the enum's underlying type + if (context.GetSerializeContext()->GetUnderlyingTypeId(classData.m_typeId) == classData.m_typeId) + { + underlyingTypeId = classData.m_typeId; + } + else + { + return context.Report(Tasks::RetrieveInfo, Outcomes::Unknown, + "Unable to find underlying type of enum in class data."); + } } const SerializeContext::ClassData* underlyingClassData = context.GetSerializeContext()->FindClassData(underlyingTypeId); diff --git a/Code/Framework/AzCore/Tests/Serialization/Json/TestCases.h b/Code/Framework/AzCore/Tests/Serialization/Json/TestCases.h index b01dbe9b0d..ae313632af 100644 --- a/Code/Framework/AzCore/Tests/Serialization/Json/TestCases.h +++ b/Code/Framework/AzCore/Tests/Serialization/Json/TestCases.h @@ -21,7 +21,7 @@ namespace JsonSerializationTests { using JsonSerializationTestCases = ::testing::Types< // Structures - SimpleClass, SimpleInheritence, MultipleInheritence, SimpleNested, SimpleEnumWrapper, + SimpleClass, SimpleInheritence, MultipleInheritence, SimpleNested, SimpleEnumWrapper, NonReflectedEnumWrapper, // Pointers SimpleNullPointer, SimpleAssignedPointer, ComplexAssignedPointer, ComplexNullInheritedPointer, ComplexAssignedDifferentInheritedPointer, ComplexAssignedSameInheritedPointer, diff --git a/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Classes.cpp b/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Classes.cpp index 5be031d70a..6da3120f59 100644 --- a/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Classes.cpp +++ b/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Classes.cpp @@ -373,6 +373,57 @@ namespace JsonSerializationTests return MakeInstanceWithoutDefaults(AZStd::move(instance), json); } + // NonReflectedEnumWrapper + bool NonReflectedEnumWrapper::Equals(const NonReflectedEnumWrapper& rhs, bool fullReflection) const + { + return !fullReflection || (m_enumClass == rhs.m_enumClass && m_rawEnum== rhs.m_rawEnum); + } + + void NonReflectedEnumWrapper::Reflect(AZStd::unique_ptr& context, bool fullReflection) + { + if (fullReflection) + { + // Note that the enums are not reflected using context->Enum<> + + context->Class() + ->Field("enumClass", &NonReflectedEnumWrapper::m_enumClass) + ->Field("rawEnum", &NonReflectedEnumWrapper::m_rawEnum); + } + } + + InstanceWithSomeDefaults NonReflectedEnumWrapper::GetInstanceWithSomeDefaults() + { + auto instance = AZStd::make_unique(); + instance->m_enumClass = NonReflectedEnumWrapper::SimpleEnumClass::Option2; + + const char* strippedDefaults = R"( + { + "enumClass": 2 + })"; + const char* keptDefaults = R"( + { + "enumClass": 2, + "rawEnum": 0 + })"; + + return MakeInstanceWithSomeDefaults(AZStd::move(instance), + strippedDefaults, keptDefaults); + } + + InstanceWithoutDefaults NonReflectedEnumWrapper::GetInstanceWithoutDefaults() + { + auto instance = AZStd::make_unique(); + instance->m_enumClass = NonReflectedEnumWrapper::SimpleEnumClass::Option2; + instance->m_rawEnum = NonReflectedEnumWrapper::SimpleRawEnum::RawOption1; + + const char* json = R"( + { + "enumClass": 2, + "rawEnum": 1 + })"; + return MakeInstanceWithoutDefaults(AZStd::move(instance), json); + } + // TemplatedClass bool TemplatedClass::Equals(const TemplatedClass& rhs, bool fullReflection) const diff --git a/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Classes.h b/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Classes.h index db5db23fba..1830ca9e6f 100644 --- a/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Classes.h +++ b/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Classes.h @@ -134,6 +134,35 @@ namespace JsonSerializationTests SimpleRawEnum m_rawEnum{}; }; + struct NonReflectedEnumWrapper + { + enum class SimpleEnumClass + { + Option1 = 1, + Option2, + }; + enum SimpleRawEnum + { + RawOption1 = 1, + RawOption2, + }; + AZ_CLASS_ALLOCATOR(NonReflectedEnumWrapper, AZ::SystemAllocator, 0); + AZ_RTTI(NonReflectedEnumWrapper, "{A80D5B6B-2FD1-46E9-A7A9-44C5E2650526}"); + + static constexpr bool SupportsPartialDefaults = true; + + NonReflectedEnumWrapper() = default; + virtual ~NonReflectedEnumWrapper() = default; + + bool Equals(const NonReflectedEnumWrapper& rhs, bool fullReflection) const; + static void Reflect(AZStd::unique_ptr& context, bool fullReflection); + static InstanceWithSomeDefaults GetInstanceWithSomeDefaults(); + static InstanceWithoutDefaults GetInstanceWithoutDefaults(); + + SimpleEnumClass m_enumClass{}; + SimpleRawEnum m_rawEnum{}; + }; + template struct TemplatedClass { @@ -158,5 +187,7 @@ namespace AZ { AZ_TYPE_INFO_SPECIALIZE(JsonSerializationTests::SimpleEnumWrapper::SimpleEnumClass, "{AF6F1964-5B20-4689-BF23-F36B9C9AAE6A}"); AZ_TYPE_INFO_SPECIALIZE(JsonSerializationTests::SimpleEnumWrapper::SimpleRawEnum, "{EB24207F-B48F-4D8B-940D-3CD06A371739}"); + AZ_TYPE_INFO_SPECIALIZE(JsonSerializationTests::NonReflectedEnumWrapper::SimpleEnumClass, "{E80E4A41-B29E-4B7C-B630-3B599172C837}"); + AZ_TYPE_INFO_SPECIALIZE(JsonSerializationTests::NonReflectedEnumWrapper::SimpleRawEnum, "{C42AF28D-4F84-4540-972A-5B6EEFAB13FF}"); AZ_TYPE_INFO_TEMPLATE(JsonSerializationTests::TemplatedClass, "{CA4ADF74-66E7-4D16-B4AC-F71278C60EC7}", AZ_TYPE_INFO_TYPENAME); }