Unreal Engine 4 – Sorting by Arbitrary Properties

When creating the File Manager and Inventory screens for Waves² I found myself needing to sort lots of arrays of various types by largely arbitrary properties. I adapted this snippet to be able to sort arrays of either objects or structs by almost any UProperty within them. You’ll have to implement sorting by structures yourself but this took care of sorting tables full of strings or numerical values.

First we need to declare an ENum for direction.

UENUM(BlueprintType)
enum class ESortDirection : uint8
{
	ASCENDING UMETA(DisplayName = "Ascending"),
	DESCENDING UMETA(DisplayName = "Descending")
};

Now we need our sorting method structure that contains our predicate.
This could work for sorting Objects that inherit from different classes but I prefer to specify their common class so I know they are compatible.

struct FArraySortObjectByFieldPredicate
{
	FArraySortObjectByFieldPredicate(const FString &InFieldName, ESortDirection InSortDirection, UClass* InClass)
		: FieldName(InFieldName), SortDirection(InSortDirection), ourClass(InClass)
	{
	}

	bool operator ()(const UObject& A, const UObject& B) const
	{
		if (!A.IsA(ourClass) || !B.IsA(ourClass))
			return false;

		UProperty *targetProperty = FindField<UProperty>(ourClass, *FieldName);
		if (targetProperty == nullptr)
			return false;

		const void *Aa = (SortDirection == ESortDirection::ASCENDING) ? &A : &B;
		const void *Bb = (SortDirection == ESortDirection::ASCENDING) ? &B : &A;

		if (targetProperty->IsA<UByteProperty>())
		{
			return Cast<UByteProperty>(targetProperty)->GetPropertyValue_InContainer(Aa) <
				Cast<UByteProperty>(targetProperty)->GetPropertyValue_InContainer(Bb);
		}
		else if (targetProperty->IsA<UIntProperty>())
		{
			return
				Cast<UIntProperty>(targetProperty)->GetPropertyValue_InContainer(Aa) <
				Cast<UIntProperty>(targetProperty)->GetPropertyValue_InContainer(Bb);
		}
		else if (targetProperty->IsA<UUInt32Property>())
		{
			return
				Cast<UUInt32Property>(targetProperty)->GetPropertyValue_InContainer(Aa) <
				Cast<UUInt32Property>(targetProperty)->GetPropertyValue_InContainer(Bb);
		}
		else if (targetProperty->IsA<UFloatProperty>())
		{
			return
				Cast<UFloatProperty>(targetProperty)->GetPropertyValue_InContainer(Aa) <
				Cast<UFloatProperty>(targetProperty)->GetPropertyValue_InContainer(Bb);
		}
		else if (targetProperty->IsA<UDoubleProperty>())
		{
			return
				Cast<UDoubleProperty>(targetProperty)->GetPropertyValue_InContainer(Aa) <
				Cast<UDoubleProperty>(targetProperty)->GetPropertyValue_InContainer(Bb);
		}
		else if (targetProperty->IsA<UStrProperty>())
		{
			return
				Cast<UStrProperty>(targetProperty)->GetPropertyValue_InContainer(Aa) >
				Cast<UStrProperty>(targetProperty)->GetPropertyValue_InContainer(Bb);
		}
		else if (targetProperty->IsA<UNameProperty>())
		{
			return
				Cast<UNameProperty>(targetProperty)->GetPropertyValue_InContainer(Aa) >
				Cast<UNameProperty>(targetProperty)->GetPropertyValue_InContainer(Bb);
		}
		else if (targetProperty->IsA<UTextProperty>())
		{
			return
				Cast<UTextProperty>(targetProperty)->GetPropertyValue_InContainer(Aa).ToString() >
				Cast<UTextProperty>(targetProperty)->GetPropertyValue_InContainer(Bb).ToString();
		}

		return false;
	}

	FString FieldName;
	ESortDirection SortDirection;
	UClass* ourClass;
};

For Sorting Structs you need to provide the struct as part of the template.

template <typename T>
struct FArraySortStructByFieldPredicate
{
	FArraySortStructByFieldPredicate(const FString &InFieldName, ESortDirection InSortDirection, UStruct* InStruct)
		: FieldName(InFieldName), SortDirection(InSortDirection), ourStruct(InStruct)
	{
	}

	bool operator ()(const T& A, const T& B) const
	{
		UProperty *targetProperty = FindField<UProperty>(ourStruct, *FieldName);
		if (targetProperty == nullptr)
			return false;

		const void *Aa = (SortDirection == ESortDirection::ASCENDING) ? &A : &B;
		const void *Bb = (SortDirection == ESortDirection::ASCENDING) ? &B : &A;

		if (targetProperty->IsA<UByteProperty>())
		{
			return Cast<UByteProperty>(targetProperty)->GetPropertyValue_InContainer(Aa) <
				Cast<UByteProperty>(targetProperty)->GetPropertyValue_InContainer(Bb);
		}
		else if (targetProperty->IsA<UIntProperty>())
		{
			return
				Cast<UIntProperty>(targetProperty)->GetPropertyValue_InContainer(Aa) <
				Cast<UIntProperty>(targetProperty)->GetPropertyValue_InContainer(Bb);
		}
		else if (targetProperty->IsA<UUInt32Property>())
		{
			return
				Cast<UUInt32Property>(targetProperty)->GetPropertyValue_InContainer(Aa) <
				Cast<UUInt32Property>(targetProperty)->GetPropertyValue_InContainer(Bb);
		}
		else if (targetProperty->IsA<UFloatProperty>())
		{
			return
				Cast<UFloatProperty>(targetProperty)->GetPropertyValue_InContainer(Aa) <
				Cast<UFloatProperty>(targetProperty)->GetPropertyValue_InContainer(Bb);
		}
		else if (targetProperty->IsA<UDoubleProperty>())
		{
			return
				Cast<UDoubleProperty>(targetProperty)->GetPropertyValue_InContainer(Aa) <
				Cast<UDoubleProperty>(targetProperty)->GetPropertyValue_InContainer(Bb);
		}
		else if (targetProperty->IsA<UStrProperty>())
		{
			return
				Cast<UStrProperty>(targetProperty)->GetPropertyValue_InContainer(Aa) >
				Cast<UStrProperty>(targetProperty)->GetPropertyValue_InContainer(Bb);
		}
		else if (targetProperty->IsA<UNameProperty>())
		{
			return
				Cast<UNameProperty>(targetProperty)->GetPropertyValue_InContainer(Aa) >
				Cast<UNameProperty>(targetProperty)->GetPropertyValue_InContainer(Bb);
		}
		else if (targetProperty->IsA<UTextProperty>())
		{
			return
				Cast<UTextProperty>(targetProperty)->GetPropertyValue_InContainer(Aa).ToString() >
				Cast<UTextProperty>(targetProperty)->GetPropertyValue_InContainer(Bb).ToString();
		}
			
		return false;
	}

	FString FieldName;
	ESortDirection SortDirection;
	UStruct* ourStruct;
};

Some examples:

Sorting an Array of Objects.

Filtered.Sort(FArraySortObjectByFieldPredicate(SortBy, InSortDirection, UWavesCard::StaticClass()));

Sorting an Array of Structures.

Filtered.Sort(FArraySortStructByFieldPredicate<FVirtualFileInfo>(SortBy, InSortDirection, FVirtualFileInfo::StaticStruct()));

Leave a Reply

Your email address will not be published. Required fields are marked *