@ChrisK I think the built-in Json parser in Unreal does not support that.
I think you have to use an ArrayHelper there to do what you want.
I came across the same problem while writing a C# compiler for Blueprint nodes.
The only way I found to serialize the custom Array of Struct coming from C#, was to iterate through each struct item by myself, I had to go a bit low level into the BPs VM for that;
I will show you what I did, both to write a Vector and what I did to read it back to C#, might give you ideas of what is going on…
- The Blueprint high level, common nodes:
UFUNCTION(Category="MagicNode", BlueprintCallable, CustomThunk, meta=(WorldContext="Context", BlueprintInternalUseOnly=true))
static void SetMonoArray_Vector3D (UObject* Context, UMagicNodeSharp* Node, const FName Field, const TArray<FVector>& Input)
{
}
UFUNCTION(Category="MagicNode", BlueprintCallable, meta=(WorldContext="Context", BlueprintInternalUseOnly=true))
static void GetMonoArray_Vector3D (UObject* Context, UMagicNodeSharp* Node, const FName Field, TArray<FVector>& Output);
void UMagicNodeSharp::GetMonoArray_Vector3D (UObject* Context, UMagicNodeSharp* Node, const FName Field, TArray<FVector>& Output)
{
GET_MonoArrayValue_Vector3D(Node,Field,Output);
}
- Custom BP VM instructions.
This is useful to get the address of the array property being edited:
DECLARE_FUNCTION(execSetMonoArray_Vector3D)
{
P_GET_OBJECT(UObject,_Context);
P_GET_OBJECT(UMagicNodeSharp,_Node);
P_GET_PROPERTY(FNameProperty,_Field);
Stack.StepCompiledIn<FArrayProperty>(nullptr);
void* _Address = Stack.MostRecentPropertyAddress;
FArrayProperty* _Array = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!_Array) {Stack.bArrayContextFailed=true; return;}
P_FINISH;
P_NATIVE_BEGIN;
SET_MonoArrayValue(_Node, _Field, _Array, _Address);
P_NATIVE_END;
}
- Then this is the actual serializer, it’s similar to what Savior does…
(but in the Savior API the Json API is used as a middle-man to make code easier).
FScriptArrayHelper is the key point of interest:
void IMonoObject::SET_MonoArrayValue(UMagicNodeSharp* Node, const FName &Field, FArrayProperty* Property, void* Address)
{
if (Field.IsNone()) {LOG::CS_CHAR(ESeverity::Error,TEXT("__SET_MonoArrayValue:: Field is None.")); return;}
if (Node==nullptr) {LOG::CS_CHAR(ESeverity::Error,TEXT("__SET_MonoArrayValue:: No Context.")); return;}
MonoObject* ManagedObject = Node->GetMonoObject();
if (ManagedObject==nullptr)
{
LOG::CS_CHAR(ESeverity::Error,TEXT("__SET_MonoArrayValue:: Invalid Managed Object.")); return;
}
if (MonoProperty* MonoProp = Node->GetMonoProperty(Field))
{
FMonoArray Array = FMonoArray(Property,Address);
void* Args[1]; Args[0] = &Array;
if (MonoMethod* SetMethod = mono_property_get_set_method(MonoProp))
{
mono_runtime_invoke(SetMethod, ManagedObject, Args, NULL);
}
}
}
void IMonoObject::GET_MonoArrayValue_Vector3D(UMagicNodeSharp* Node, const FName &Field, TArray<FVector>&Output)
{
if (Field.IsNone()) {LOG::CS_CHAR(ESeverity::Error,TEXT("__GET_MonoArrayValue_Vector3D:: Field is None.")); return;}
if (Node==nullptr) {LOG::CS_CHAR(ESeverity::Error,TEXT("__GET_MonoArrayValue_Vector3D:: No Context.")); return;}
MonoObject* ManagedObject = Node->GetMonoObject();
if (ManagedObject==nullptr)
{
LOG::CS_CHAR(ESeverity::Error,TEXT("__GET_MonoArrayValue_Vector3D:: Invalid Managed Object.")); return;
}
if (MonoProperty*MonoProp=Node->GetMonoProperty(Field))
{
MonoMethod* GET = mono_property_get_get_method(MonoProp);
if (MonoObject* iCall = mono_runtime_invoke(GET,ManagedObject,NULL,NULL))
{
FMonoArray Out = *(FMonoArray*)mono_object_unbox(iCall);
if (FArrayProperty* Property = reinterpret_cast<FArrayProperty*>(Out.Property))
{
if (FStructProperty* InnerValue = CastField<FStructProperty>(Property->Inner))
{
if (InnerValue->Struct == TBaseStructure<FVector>::Get())
{
FScriptArrayHelper Array(Property,Out.ValueAddr);
Output.SetNum(Array.Num());
for (int32 I=0; I<Array.Num(); ++I)
{
const uint8* ValuePtr = Array.GetRawPtr(I);
FVector ValueCopy;
InnerValue->CopySingleValue(&ValueCopy,ValuePtr);
Output[I] = ValueCopy;
}
}
}
}
}
}
}
I would do 99% the same thing to bypass problems caused by the Json parser.
But instead of invoking mono_xx API calls there, I would simply create Json object for the array manually, fill it with member values, then inject it into the save record’s “JSON” master object.