// Copyright Epic Games, Inc. All Rights Reserved. #include "Backends/CborStructSerializerBackend.h" #include "StructSerializationUtilities.h" #include "UObject/AnsiStrProperty.h" #include "UObject/EnumProperty.h" #include "UObject/PropertyPortFlags.h" #include "UObject/TextProperty.h" #include "UObject/UnrealType.h" #include "UObject/Utf8StrProperty.h" FCborStructSerializerBackend::FCborStructSerializerBackend(FArchive& InArchive) : CborWriter(&InArchive) , Flags(EStructSerializerBackendFlags::Legacy) {} FCborStructSerializerBackend::FCborStructSerializerBackend(FArchive& InArchive, const EStructSerializerBackendFlags InFlags) : CborWriter(&InArchive, EnumHasAnyFlags(InFlags, EStructSerializerBackendFlags::WriteCborStandardEndianness) ? ECborEndianness::StandardCompliant : ECborEndianness::Platform) , Flags(InFlags) {} FCborStructSerializerBackend::~FCborStructSerializerBackend() = default; void FCborStructSerializerBackend::BeginArray(const FStructSerializerState& State) { // If TArray/TArray content needs to be written as ByteString (to prevent paying a 1 byte header for each byte greater than 23 required by CBOR array). if (EnumHasAnyFlags(Flags, EStructSerializerBackendFlags::WriteByteArrayAsByteStream)) { if (FArrayProperty* ArrayProperty = CastField(State.ValueProperty)) { if (CastField(ArrayProperty->Inner) || CastField(ArrayProperty->Inner)) // A CBOR draft to support homogeneous array exists, but is not yet approved: https://datatracker.ietf.org/doc/draft-ietf-cbor-array-tags/. { check(!bSerializingByteArray); // WritePODArray should be preferred in this case instead of doing per element serialization // Hence omit reserving the space needed for per element serialization since it will be written directly to the cbor stream // if otherwise per element serialization is done, it will still work although through some additional allocation //FScriptArrayHelper ArrayHelper(ArrayProperty, ArrayProperty->ContainerPtrToValuePtr(State.ValueData)); //AccumulatedBytes.Reset(ArrayHelper.Num()); bSerializingByteArray = true; } } } // Array nested in Array/Set if (State.ValueProperty->GetOwner() || State.ValueProperty->GetOwner()) { // fall through. } // Array nested in Map else if (State.KeyProperty != nullptr) { FString KeyString; State.KeyProperty->ExportTextItem_Direct(KeyString, State.KeyData, nullptr, nullptr, PPF_None); CborWriter.WriteValue(KeyString); } // Array nested in Object else { CborWriter.WriteValue(State.ValueProperty->GetName()); } if (!bSerializingByteArray) // TArray/TArray are written as ByteString rather than CBOR array because it is more size efficient. { CborWriter.WriteContainerStart(ECborCode::Array, -1/*Indefinite*/); } } void FCborStructSerializerBackend::BeginStructure(const FStructSerializerState& State) { if (State.ValueProperty != nullptr) { // Object nested in Array/Set if ((State.ValueProperty->ArrayDim > 1 || State.ValueProperty->GetOwner() || State.ValueProperty->GetOwner() || (State.ValueProperty->GetOwner() && State.KeyProperty == nullptr)) && !EnumHasAnyFlags(State.StateFlags, EStructSerializerStateFlags::WritingContainerElement)) { CborWriter.WriteContainerStart(ECborCode::Map, -1/*Indefinite*/); } // Object nested in Map else if (State.KeyProperty != nullptr) { FString KeyString; State.KeyProperty->ExportTextItem_Direct(KeyString, State.KeyData, nullptr, nullptr, PPF_None); CborWriter.WriteValue(KeyString); CborWriter.WriteContainerStart(ECborCode::Map, -1/*Indefinite*/); } // Object nested in Object else { CborWriter.WriteValue(State.ValueProperty->GetName()); CborWriter.WriteContainerStart(ECborCode::Map, -1/*Indefinite*/); } } // Root Object else { CborWriter.WriteContainerStart(ECborCode::Map, -1/*Indefinite*/); } } void FCborStructSerializerBackend::EndArray(const FStructSerializerState& State) { if (bSerializingByteArray) // Does end a TArray/TArray? { // Flush the accumulated bytes as a ByteString(). CborWriter.WriteValue(AccumulatedBytes.GetData(), AccumulatedBytes.Num()); bSerializingByteArray = false; } else { CborWriter.WriteContainerEnd(); } } void FCborStructSerializerBackend::EndStructure(const FStructSerializerState& State) { CborWriter.WriteContainerEnd(); } void FCborStructSerializerBackend::WriteComment(const FString& Comment) { // Binary format do not support comment } namespace CborStructSerializerBackend { // Writes a property value to the serialization output. template void WritePropertyValue(FCborWriter& CborWriter, const FStructSerializerState& State, const ValueType& Value) { // Value nested in Array/Set (except single element) or map as array or as root if ((State.ValueProperty == nullptr) || ((State.ValueProperty->ArrayDim > 1 || State.ValueProperty->GetOwner() || State.ValueProperty->GetOwner() || (State.ValueProperty->GetOwner() && State.KeyProperty == nullptr)) && !EnumHasAnyFlags(State.StateFlags, EStructSerializerStateFlags::WritingContainerElement))) { CborWriter.WriteValue(Value); } // Value nested in Map else if (State.KeyProperty != nullptr) { FString KeyString; State.KeyProperty->ExportTextItem_Direct(KeyString, State.KeyData, nullptr, nullptr, PPF_None); CborWriter.WriteValue(KeyString); CborWriter.WriteValue(Value); } // Value nested in Object else { CborWriter.WriteValue(State.ValueProperty->GetName()); CborWriter.WriteValue(Value); } } // Writes a null value to the serialization output. void WriteNull(FCborWriter& CborWriter, const FStructSerializerState& State) { if ((State.ValueProperty == nullptr) || ((State.ValueProperty->ArrayDim > 1 || State.ValueProperty->GetOwner() || State.ValueProperty->GetOwner() || (State.ValueProperty->GetOwner() && State.KeyProperty == nullptr)) && !EnumHasAnyFlags(State.StateFlags, EStructSerializerStateFlags::WritingContainerElement))) { CborWriter.WriteNull(); } else if (State.KeyProperty != nullptr) { FString KeyString; State.KeyProperty->ExportTextItem_Direct(KeyString, State.KeyData, nullptr, nullptr, PPF_None); CborWriter.WriteValue(KeyString); CborWriter.WriteNull(); } else { CborWriter.WriteValue(State.ValueProperty->GetName()); CborWriter.WriteNull(); } } } void FCborStructSerializerBackend::WriteProperty(const FStructSerializerState& State, int32 ArrayIndex) { using namespace CborStructSerializerBackend; // Bool if (State.FieldType == FBoolProperty::StaticClass()) { WritePropertyValue(CborWriter, State, CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); } // Unsigned Bytes & Enums else if (State.FieldType == FEnumProperty::StaticClass()) { FEnumProperty* EnumProperty = CastFieldChecked(State.ValueProperty); WritePropertyValue(CborWriter, State, EnumProperty->GetEnum()->GetNameStringByValue(EnumProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(EnumProperty->ContainerPtrToValuePtr(State.ValueData, ArrayIndex)))); } else if (State.FieldType == FByteProperty::StaticClass()) { FByteProperty* ByteProperty = CastFieldChecked(State.ValueProperty); if (ByteProperty->IsEnum()) { WritePropertyValue(CborWriter, State, ByteProperty->Enum->GetNameStringByValue(ByteProperty->GetPropertyValue_InContainer(State.ValueData, ArrayIndex))); } else if (bSerializingByteArray) // Writing a byte from a TArray/TArray? { AccumulatedBytes.Add(ByteProperty->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); } else { WritePropertyValue(CborWriter, State, (int64)ByteProperty->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); } } // Double & Float else if (State.FieldType == FDoubleProperty::StaticClass()) { if (EnumHasAnyFlags(Flags, EStructSerializerBackendFlags::WriteLWCTypesAsFloats) && StructSerializationUtilities::IsLWCType(State.ValueProperty->GetOwnerStruct())) { const double Value = CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex); WritePropertyValue(CborWriter, State, static_cast(Value)); } else { WritePropertyValue(CborWriter, State, CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); } } else if (State.FieldType == FFloatProperty::StaticClass()) { WritePropertyValue(CborWriter, State, CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); } // Signed Integers else if (State.FieldType == FIntProperty::StaticClass()) { WritePropertyValue(CborWriter, State, (int64)CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); } else if (State.FieldType == FInt8Property::StaticClass()) { int8 Value = CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex); if (bSerializingByteArray) // Writing a int8 from a TArray/TArray? { AccumulatedBytes.Add(Value); } else { WritePropertyValue(CborWriter, State, (int64)Value); } } else if (State.FieldType == FInt16Property::StaticClass()) { WritePropertyValue(CborWriter, State, (int64)CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); } else if (State.FieldType == FInt64Property::StaticClass()) { WritePropertyValue(CborWriter, State, (int64)CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); } // Unsigned Integers else if (State.FieldType == FUInt16Property::StaticClass()) { WritePropertyValue(CborWriter, State, (int64)CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); } else if (State.FieldType == FUInt32Property::StaticClass()) { WritePropertyValue(CborWriter, State, (int64)CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); } else if (State.FieldType == FUInt64Property::StaticClass()) { WritePropertyValue(CborWriter, State, (int64)CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); } // FNames, Strings & Text else if (State.FieldType == FNameProperty::StaticClass()) { WritePropertyValue(CborWriter, State, CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex).ToString()); } else if (State.FieldType == FStrProperty::StaticClass()) { WritePropertyValue(CborWriter, State, CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); } else if (State.FieldType == FAnsiStrProperty::StaticClass()) { WritePropertyValue(CborWriter, State, CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); } else if (State.FieldType == FUtf8StrProperty::StaticClass()) { WritePropertyValue(CborWriter, State, CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); } else if (State.FieldType == FTextProperty::StaticClass()) { const FText& TextValue = CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex); if (EnumHasAnyFlags(Flags, EStructSerializerBackendFlags::WriteTextAsComplexString)) { FString TextValueString; FTextStringHelper::WriteToBuffer(TextValueString, TextValue); WritePropertyValue(CborWriter, State, TextValueString); } else { WritePropertyValue(CborWriter, State, TextValue.ToString()); } } // Classes & Objects else if (State.FieldType == FSoftClassProperty::StaticClass()) { FSoftObjectPtr const& Value = CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex); WritePropertyValue(CborWriter, State, Value.ToString()); } else if (State.FieldType == FWeakObjectProperty::StaticClass()) { FWeakObjectPtr const& Value = CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex); WritePropertyValue(CborWriter, State, Value.IsValid() ? Value.Get()->GetPathName() : FString()); } else if (State.FieldType == FSoftObjectProperty::StaticClass()) { FSoftObjectPtr const& Value = CastFieldChecked(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex); WritePropertyValue(CborWriter, State, Value.ToString()); } else if (FObjectProperty* ObjectProperty = CastField(State.ValueProperty)) { // @TODO: Could this be expanded to include everything derived from FObjectPropertyBase? // Generic handling for a property type derived from FObjectProperty that is obtainable as a pointer and will be stored using its path. // This must come after all the more specialized handlers for object property types. UObject* const Value = ObjectProperty->GetObjectPropertyValue_InContainer(State.ValueData, ArrayIndex); WritePropertyValue(CborWriter, State, Value ? Value->GetPathName() : FString()); } // Unsupported else { UE_LOG(LogSerialization, Verbose, TEXT("FCborStructSerializerBackend: Property %s cannot be serialized, because its type (%s) is not supported"), *State.ValueProperty->GetFName().ToString(), *State.ValueType->GetFName().ToString()); } } bool FCborStructSerializerBackend::WritePODArray(const FStructSerializerState& State) { FArrayProperty* ArrayProperty = CastField(State.ValueProperty); if (bSerializingByteArray && ArrayProperty && (CastField(ArrayProperty->Inner) || CastField(ArrayProperty->Inner))) // A CBOR draft to support homogeneous array exists, but is not yet approved: https://datatracker.ietf.org/doc/draft-ietf-cbor-array-tags/. { FScriptArrayHelper ArrayHelper(ArrayProperty, ArrayProperty->ContainerPtrToValuePtr(State.ValueData)); // write out the array as a ByteString directly. CborWriter.WriteValue(ArrayHelper.GetRawPtr(), ArrayHelper.Num()); bSerializingByteArray = false; return true; } return false; }