While working on exposing JSON handling to Blueprint, I really wished I could serialize and deserialize BP structs. I looked around and couldn’t find anything about making an UFUNCTION take wildcards without having to resort to creating a fullly custom K2_Node class (for which there’s almost no documentation also). I started digging the source and doing some experiments and I finally found a way, which I’m sharing here with you!
DISCLAIMER: Use this at your own risk! I haven’t tested this extensively since most of the stuff it uses isn’t deeply documented (thanks Epic!). You have been warned.
Note: This only works for receiving structs. It won’t work with integers, floats, booleans, strings and other primitive types.
Here’s the complete example:
// Example Blueprint function that receives any struct as input
UFUNCTION(BlueprintCallable, Category = "Example", CustomThunk, meta = (CustomStructureParam = "AnyStruct"))
static void ReceiveSomeStruct(UProperty* AnyStruct);
DECLARE_FUNCTION(execReceiveSomeStruct)
{
// Steps into the stack, walking to the next property in it
Stack.Step(Stack.Object, NULL);
// Grab the last property found when we walked the stack
// This does not contains the property value, only its type information
UStructProperty* StructProperty = ExactCast<UStructProperty>(Stack.MostRecentProperty);
// Grab the base address where the struct actually stores its data
// This is where the property value is truly stored
void* StructPtr = Stack.MostRecentPropertyAddress;
// We need this to wrap up the stack
P_FINISH;
// Iterate through the struct
IterateThroughStructProperty(StructProperty, StructPtr);
}
/* Example function for parsing a single property
* @param Property the property reflection data
* @param ValuePtr the pointer to the property value
*/
void ParseProperty(UProperty* Property, void* ValuePtr)
{
float FloatValue;
int32 IntValue;
bool BoolValue;
FString StringValue;
FName NameValue;
FText TextValue;
// Here's how to read integer and float properties
if (UNumericProperty *NumericProperty = Cast<UNumericProperty>(Property))
{
if (NumericProperty->IsFloatingPoint())
{
FloatValue = NumericProperty->GetFloatingPointPropertyValue(ValuePtr);
}
else if (NumericProperty->IsInteger())
{
IntValue = NumericProperty->GetSignedIntPropertyValue(ValuePtr);
}
}
// How to read booleans
if (UBoolProperty* BoolProperty = Cast<UBoolProperty>(Property))
{
BoolValue = BoolProperty->GetPropertyValue(ValuePtr);
}
// Reading names
if (UNameProperty* NameProperty = Cast<UNameProperty>(Property))
{
NameValue = NameProperty->GetPropertyValue(ValuePtr);
}
// Reading strings
if (UStrProperty* StringProperty = Cast<UStrProperty>(Property))
{
StringValue = StringProperty->GetPropertyValue(ValuePtr);
}
// Reading texts
if (UTextProperty* TextProperty = Cast<UTextProperty>(Property))
{
TextValue = TextProperty->GetPropertyValue(ValuePtr);
}
// Reading an array
if (UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Property))
{
// We need the helper to get to the items of the array
FScriptArrayHelper Helper(ArrayProperty, ValuePtr);
for (int32 i = 0, n = Helper.Num(); i<n; ++i)
{
ParseProperty(ArrayProperty->Inner, Helper.GetRawPtr(i));
}
}
// Reading a nested struct
if (UStructProperty* StructProperty = Cast<UStructProperty>(Property))
{
IterateThroughStructProperty(StructProperty, ValuePtr);
}
}
/*
* Example function for iterating through all properties of a struct
* @param StructProperty The struct property reflection data
* @param StructPtr The pointer to the struct value
*/
void IterateThroughStructProperty(UStructProperty* StructProperty, void* StructPtr)
{
// Walk the structs' properties
UScriptStruct* Struct = StructProperty->Struct;
for (TFieldIterator<UProperty> It(Struct); It; ++It)
{
UProperty* Property = *It;
// This is the variable name if you need it
FString VariableName = Property->GetName();
// Never assume ArrayDim is always 1
for (int32 ArrayIndex = 0; ArrayIndex < Property->ArrayDim; ArrayIndex++)
{
// This grabs the pointer to where the property value is stored
void* ValuePtr = Property->ContainerPtrToValuePtr<void>(StructPtr, ArrayIndex);
// Parse this property
ParseProperty(Property, ValuePtr);
}
}
}
And the blueprint:
&stc=1
&stc=1Now some explanations (at least from what I could understand):
- The CustomStructureParam metadata is what tells the Blueprint code generator to treat the pin as a wildcard.
- The CustomThunk UFUNCTION flag prevents UHT from generating the code that glues the Blueprint to the C++ function and requires you to provider your own. This is necessary because the code generated by UHT would try to extract a UProperty* from the wildcard pin’s value, which would never work. We need to access the actual pin property so we can grab it’s value manually. This is done by using the DECLARE_FUNCTION macro and it gives us low level access to the Stack and allow us to walk through the connected UProperties.
- This one too me a while to figure out: the UProperty only contains reflection data upon the property (its type). It does not contain the value itself! For that you need the pointer to the “container”, which is where the actual values are stored. Each property only contains the memory offset from the container pointer that leads to the data. This makes sense when you remember that this is used to access C++ data in structs and classes (in which case the “container” is a pointer to the C++ class/struct that contains the value).
- Fortunately the Stack allows us to get both the UProperty and it’s container address. The Stack.Step() function “steps” through the properties in the stack and stores the information for us.
- Blueprint structs have strange (and long) property names. Don’t be scared, those are the “true” names of a BP struct’s properties (the names you see in the Editor are the “friendly” names and don’t seem to exist during runtime). I suspect this is done to prevent name collisions or something, but I’m not sure. I’ll have to deal with this in my JSON data.