How to get TMap pair's value?

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);

	
}

Did you see API for FScriptMapHelper?

I see this of course. But documentation is obscure.

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?

This is recursive iterations by UE4 types.

Similar situation here:

		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.

GetPairPtr gives me pointer really to pair? Or somthing else?
TMap can be parsed successfully.
And TMap gives me error or unexpected values…

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.

My code crashes using this method…
My map contains the structures inside (TMap&ltint32, FMyStruct>). And FMyStruct has int32 and FString fields.

My method _IterateStruct iterates by all types (include TArray, all basic types and UStructs).
But I also want to add TMap to this list.

if (UMapProperty* Prop = Cast<UMapProperty>(Property))
{
	void* ptr = Prop->ContainerPtrToValuePtr<void>(StructPtr);
	FScriptMapHelper ScriptMapHelper(Prop, ptr);
	const int32 ElementCount = ScriptMapHelper.Num();
	TArray<FString> keys;
	TArray<FString> values;
	for (int32 j = 0; j < ElementCount; ++j)
	{
		if (!ScriptMapHelper.IsValidIndex(j))
			continue;

		const uint8* MapPairPtr = ScriptMapHelper.GetPairPtr(j);
		result = _ParseObject(Prop->KeyProp, (void*)(MapPairPtr + Prop->MapLayout.KeyOffset));
		keys.Add(result);
		result = _ParseObject(Prop->ValueProp, (void*)(MapPairPtr + Prop->MapLayout.ValueOffset));
		values.Add(result);
	}
	result = FString::Printf(TEXT("\"%s\": [[%s], [%s]]"), *child->GetName(), *FString::Join(keys, TEXT(", ")), *FString::Join(values, TEXT(", ")));
}

And got crash in gathring FString data. Can I show my code for revision?

Here all code:
http://pastebin.com/WUeFkTS4

This is struct property case.

 if (UStructProperty* Prop = Cast<UStructProperty>(Property))
 {
     void* ptr = Prop->ContainerPtrToValuePtr<void>(ValuePtr);
     return _IterateStruct(ptr, Prop->Struct);
 }

Finally: I must remove the ContainerPtrToValuePtr from the code _ParseObject?

Like a

     return _IterateStruct(ValuePtr, Prop->Struct);

Yes. With arrays did not notice any crashes. And values were correct.

Can you edit a bit of code on pastebin site to show whan you mean?

UPD: I tried to remove ContainerPtrToValuePtr and I don’t got anything good… I don’t understand whats wrong with it…

Thanks for you quick answers, Jamie! :slight_smile:

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).

This really helps me!
So awesome reflection in Unreal :slight_smile:

I going to also remake my JSON → UStruct conversion code. Is there any nuaces with it?
Can I use ImportText instead ExportText_Direct?

Thank you, Jamie! Very cool.

UPD: ScriptArrayHelper.EmptyAndAddValues(JsonArray.Num()); got crash :frowning:

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.