Is There Any Way to Let Struct Contains a Varaiable that May Have Different Struct Type?

Hi,

Recently, I have been trying to write some APIs for connecting between client and server. Right now, I will get the JSON string from the HTTP response and convert it to the FJsonWrapper object. But this will be messy if I just expose this to blueprint or C++ since other people need to use the get field function, layer by layer to get the thing they want. So I am thinking of using a struct or maybe a class to contain the generic variable so other people can use it.

Right now I tried to use a struct to contain the Template variable but this is not working with USTRUCT() macro and also T is not supported by UPROPERTY(). Here are some of my thoughts.

Template<typename T>  // wrong
USTRUCT(BlueprintType)
struct FPageWrapper{
    GENERATED_BODY()
    UPROPERTY(BlueprintReadWrite)
    int32 Page;
    UPROPERTY(BlueprintReadWrite)
    int32 Capacity;
    UPROPERTY(BlueprintReadWrite) // wrong
    T Model;
};

In this code, the T for the Model variable will be other structure type, like FCities, FStreets, etc. Then when I use this I can just call it and use the FJsonObjectConverter::JsonObjectToUStruct() to either convert the struct to FPageWrapper or maybe Model.
Like this:

// Thought 1
FPageWrapper Wrapper;
FJsonObjectWrapper JsonResponse.JsonObject = FString convert to TSharedPtr<FJsonObject>;
Wrapper.page = JsonResponse.JsonObject->GetNumberField("Page");
Wrapper.Capacity= JsonResponse.JsonObject->GetNumberField("Capacity");
FJsonObjectConverter::JsonObjectToUStruct(JsonResponse.JsonObject->GetObjectField("data"), Wrapper.Model);

// Or maybe thought 2

bool GetResponseStruct(FString& InJson, FStructProperty* OutStruct, void* StructPtr)
{
    const TSharedRef< TJsonReader<> >& Reader = 
    TJsonReaderFactory<>::Create(InJsonString);
    TSharedPtr<FJsonObject> Object;
    FJsonSerializer::Deserialize(Reader, /*out*/ Object);
    return FJsonObjectConverter::JsonObjectToUStruct(Object.ToSharedRef(),OutStruct->Struct,StructPtr);
}

bool Deserialize(const FString InJson, int32& OutStruct)
{
	check(0);
	return false;
}

DEFINE_FUNCTION(execDeserialize)
{
	P_GET_PROPERTY_REF(FStrProperty,InJson);
	Stack.StepCompiledIn<FStructProperty>(NULL);
	void* StructPtr = Stack.MostRecentPropertyAddress;
	FStructProperty* OutStruct = CastField<FStructProperty>(Stack.MostRecentProperty);
	P_FINISH;
	
	bool bSuccess = false;
	P_NATIVE_BEGIN;
	bSuccess = GetResponseStruct(InJsonString,OutStruct,StructPtr);
	P_NATIVE_END;
	*(bool*)RESULT_PARAM = bSuccess;
}

// Or maybe a template class
template<typename T>
class  FPageWrapper
{
public:
	UPROPERTY(BlueprintReadWrite)
        int32 Page;
        UPROPERTY(BlueprintReadWrite)
        int32 Capacity;
	UPROPERTY()
	T Model;

	void DeserializeList(const FString& JsonString)
	{
		TSharedPtr<FJsonObject> JsonObject;
		TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);

		if (FJsonSerializer::Deserialize(Reader, JsonObject))
		{
			Page= JsonObject->GetStringField("Page");
			Capacity= JsonObject->GetBoolField("Capacity");	
			FJsonObjectConverter::JsonArrayToUStruct(JsonObject->GetObjectField("Data"), &Model, 0, 0);
		}
	}

};

These are just thoughts, the code may contain errors. And I do not just have a page-type struct, I also need to consider the tree-type JSON object.

So my question is: “Is there any built-in variable type for this Model variable that can take a variety of structures?”

Hope I explain my question and thoughts clearly.

Thanks for your reply.

could you utilize struct inheritance instead of Templates

in C++ a struct is a special class (technically it is the other way around because struct existed in C and C++ added class, but most OOP starts with classes)

the only limitations in Unreal is that when you give a struct a function (besides an operator()) they can not be UFUNCTION() but you can create a BlueprintFunctionLibrarary Function or give the container a function that passes through.

then utilize Polymorphic behavior to carry through the store it. I would also suggest like an Enum of some sort for more efficient casting when needed.

then in your containing struct give it the parent struct.

Thanks for your reply,

Inheritance is a good way for containing the structs but there is another requirement which I cannot use this way.

I need to convert them to JsonObjectString to send to the server. If I use the inheritance, the variables in two structs will be in the same layer. And that is not what I want.

Like this JSON string.

{
  "capacity": 10,
  "model": {
    "id": null,
    "level": 1,
    "name": null
  },
  "page": 1
}

If I use the inheritance it would be a struct with all variables together. Then the function FJsonObjectConverter::UStructToJsonObjectString() will convert them to the same object layer like this:

{
  "capacity": 10,
    "id": null,
    "level": 1,
    "name": null,
  "page": 1
}

if the location of the derived struct is consistent then you could modify the string by converting the string->TArray<String> delineated by comma,

// inString = "Example1,Example2,Example3"
void YourFunction(FString inString)
{
    TArray<FString> StringArray;

    // Delimiter character
    TCHAR Delimiter = TEXT(',');

    // Parse the string into the array using the specified delimiter
    inString.ParseIntoArray(StringArray, &Delimiter, true);

    // Now StringArray contains individual strings from inString separated by ','
    
   // remember to add the ',' back when you are put it back together.
}

and insert the curly braces that way to put them on a “different layer” (this would absolutely assume you know the outer structure)

it looks like there are extra spaces/tabs already you might be able to use those to determine the “layer”

if this is your main concern, I’d suggest sticking with JSON object type, and provide utility functions to help BP users work with it. For example, functions to get a nested field with a single call, where the key is in dot-separated format.
For example :

UFUNCTION(BlueprintPure)
static FString GetStringValue(const FJsonObjectWrapper& JsonWrapper, FString FieldPath)
{
	const TSharedPtr<FJsonObject>* JsonObj = &JsonWrapper.JsonObject;
	TArray<FString> Parts;
	FieldPath.ParseIntoArray(Parts, TEXT("."));
	for (int32 i = 0; i < Parts.Num(); i++)
	{
		if (i == Parts.Num() - 1)
			return (*JsonObj)->GetStringField(Parts[i]);
		else if (!(*JsonObj)->TryGetObjectField(Parts[i], JsonObj))
			return ""; //error
	}
	return "";
}

You could even provide a wildcard version by applying this to a CustomThunk method, which you can copy from JsonBlueprintFunctionLibrary.

Thanks for your guys reply.

I finally decided to use the template struct as the root struct and then add a T value type inside. Then I will expose those values to the blueprint via delegate. The reason I use the template is because the response from API from the backend will be different. So I cannot make sure of a certain type for it and it will be a lot of APIs, I cannot write all the struct by hand. I write the base structure and then the code that uses my struct and functions are generated by code.

Here is an example:

template <typename T>
struct FRootStruct
{
    UPROPERTY()
    T Data;
};

Then when they use this, it will be


FRootStruct<FResponse1> Test1;
FRootStruct<FResponse2> Test2;
FRootStruct<FResponse3> Test3;
...

Then I will expose the value to the blueprint through a delegate since the blueprint does not support the template.


DECLEAR_MULTICAST_DELEGATE_OneParam(FMyDelegate1, FResponse1, Response);
DECLEAR_MULTICAST_DELEGATE_OneParam(FMyDelegate2, FResponse2, Response);

FMyDelegate1 MyDelegate1 ;
MyDelegate1.brodcast(Test1.Data)

But this only works for simple response types, for tree JSON structure response, I haven’t found a way to solve it. If one day I figure it out, I may leave a comment here.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.