I using epic’s reflection system for serialization of JSON.
And I don’t know how to work with TMap.
The type of keys and values is arbitrary (structs, arrays, ints, etc)
My attempt:
FScriptMapHelper Helper(Prop, ptr);
TArray<FString> keys;
TArray<FString> values;
for (int32 i = 0, n = Helper.Num(); i<n; ++i)
{
// Here I can get Key of element of TMap (start memory for pair is also for key?)
result = _ParseObject(Prop->KeyProp, Helper.GetPairPtr(i));
keys.Add(result);
// What about value?
// Memory offset?
result = _ParseObject(Prop->ValueProp, (Helper.GetPairPtr(i) + ???)); // This not works with + Prop->KeyProp->GetSize()
values.Add(result);
}
Also I didn’t never work with FScriptMapHelper, but why are you using it? And what are your Prop and ptr variables? As I understand Prop is UMapProperty, so you can’t just iterate through TMap itself?
void* ptr = Prop->ContainerPtrToValuePtr<void>(StructPtr);
TArray<FString> array_strings
FScriptArrayHelper Helper(Prop, ptr);
for (int32 i = 0, n = Helper.Num(); i<n; ++i)
{
result = _ParseObject(Prop->Inner, Helper.GetRawPtr(i));/
array_strings.Add(result);
}
But this is only TArray case. Fully working.
TMap has a different structure.Can’t understand how values allocated in memory. If type of TMap value is int32 then I can add offset to Helper.GetPairPtr(i) pointer +4. But in any other case, if type of value is USTRUCT for example and I add the specified size I got a crash with access violation.
Here’s the code I use the process FText properties within a TMap when gathering for localisation. Note that you need to check for a valid index when iterating, as our maps can have holes in them.
// Iterate over all elements of the map.
FScriptMapHelper ScriptMapHelper(MapProperty, ElementValueAddress);
const int32 ElementCount = ScriptMapHelper.Num();
for (int32 j = 0; j < ElementCount; ++j)
{
if (!ScriptMapHelper.IsValidIndex(j))
{
continue;
}
const uint8* MapPairPtr = ScriptMapHelper.GetPairPtr(j);
GatherLocalizationDataFromChildTextProperties(PathToElement + FString::Printf(TEXT("(%d - Key)"), j), MapProperty->KeyProp, MapPairPtr + MapProperty->MapLayout.KeyOffset, ChildPropertyGatherTextFlags);
GatherLocalizationDataFromChildTextProperties(PathToElement + FString::Printf(TEXT("(%d - Value)"), j), MapProperty->ValueProp, MapPairPtr + MapProperty->MapLayout.ValueOffset, ChildPropertyGatherTextFlags);
}
In the example above, MapPairPtr + MapProperty->MapLayout.KeyOffset and MapPairPtr + MapProperty->MapLayout.ValueOffset are what give you the pointer to the raw data for the key and value respectively, and MapProperty->KeyProp and MapProperty->ValueProp contain the key and value property type that can be used to access the given raw data.
So your value property is of type UStructProperty and it’s crashing in your _ParseObject function? What does _ParseObject do with struct properties?
Bear in mind that the data you have from doing MapPairPtr + Prop->MapLayout.ValueOffset is already offset to the start of your struct instance, so you don’t need to use the struct property to offset it further.
I’d suspect it’s the call to ContainerPtrToValuePtr that’s causing you the issues, as that will advance the offset by Offset_Internal.
What you’re doing is correct for cases where you’re enumerating fields on a struct or class, but a container will give you back the pre-offset value… I’m surprised you’re not seeing this crash for array properties as well.
Basically, when iterating fields, you need to use ContainerPtrToValuePtr, but you don’t need to use it when iterating the contents of a container. I’d try and move the calls to ContainerPtrToValuePtr to the code that deals with the field iteration, rather than having that logic in the code that deals with the property.
I’ve made an example here. This builds, but I have no idea if it produces valid JSON - it should help you understand our reflection though.
In the code I pasted, ObjectToJsonString and StructToJsonString are what handle enumerating the fields within an object or struct (using TFieldIterator rather than by hand like you were doing) - these take care of calling ContainerPtrToValuePtr before passing the value into PropertyValueToJsonString.
PropertyValueToJsonString is what takes care of converting a property and its associated data to a string. It will call itself recursively when dealing with UArrayProperty and UMapProperty, passing in the pre-offset values and avoiding a call to ContainerPtrToValuePtr.
My PropertyValueToJsonString also only introduces custom behaviour for the properties that need it (containers, objects, and structs), and uses the generic ExportText_Direct functionality to deal with stringifying everything else (PPF_Delimited makes sure that strings and such are quoted and correctly escaped).
My example also handles recursing into UObjectPropertyBase properties in a really dangerous way (due to the fact I based the code on the FText property scraping code, which has a lot more sanity checks when dealing with object properties that what I included here). You likely don’t want to do this, as it can lead to all sorts of nasty behaviour (even our own struct → JSON converter doesn’t handle object properties). I’d suggest either ignoring object properties, or just writing out their path so you can try and find them again later (eg, if you’re referencing an asset).
UPD2: ScriptMapHelper.AddDefaultValue_Invalid_NeedsRehash() and ScriptMapHelper.Rehash() is true way to construct TMap?
UPD3: I do it! But… I got strange consequences. After exit from my function editor was crashed: Access Violation at address 0x00007FFD57E03AF8 (UE4Editor-SlateCore.dll) in UE4Editor.exe: 0xC0000005 read of address 0xFFFFFFFFFFFFFFFF
And after retry exception was raised again, but deffers
(selected line is exit of my method, without specified entry, destructor?)
I understand, I’m doing something wrong with writing data. Maybe this is my Map and/or Array iteration with writing to forbidden address. So I used method above.
For adding new item to arrays I used ScriptArrayHelper.AddValue() and writing data to GetRawPtr() to this item index after.
UPD4: Finally I solve the problem. I used ContainerPtrToValuePtr with values at some place in code. It was incorrect.
But what about ArrayDim I’m not understood…
Strange moment ContainerPtrToValuePtr with values gives me bug only with TMap, with the TArray this not observed.