We all know that debugging simple or complex Structs in Unreal can be a total nightmare. I wanted to share this simple function that I always carry with me to any new project. It can convert any Struct into a clean and readable JSON Object in string format, so you can easily debug your data.
The Formatting Nightmare
Let’s say you have a Struct and you want to print it to the screen to see if the data is correct. Usually you do it this way:
- Break the struct.
- Create a
FormatTextnode and build the debug message manually. - Connect every pin which the data it holds might require conversion.
- If you have an array or a map then you need to create a
loopnode and print every element in that array individually.
The Problem: If you add a new variable to that Struct later, you have to go back and update every debug node in your Blueprints manually. And you need to redo the same logic for every struct you want to debug and print to the log.
Keep it simple
All this chaos can be replaced with a simple and tiny function, it takes advantage of CustomThunk. In short, when you mark a UFUNCTION with CustomThunk, you’re telling the engine’s Unreal Header Tool (UHT) to skip the automatic generation of the function body and that you will be providing a custom implementation to handle the Blueprint Virtual Machine (BPVM) stack operations yourself.
Here is how to use this function:
So easy right !! and it is also formatted in a beautiful JSNO object.
How to Implement it
First, you need to enable the JSON modules in your project. Open your [ProjectName].Build.cs file and add "Json" and "JsonUtilities".
// MyProject.Build.cs
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"CoreUObject",
"Engine",
"InputCore",
"Json", // <--- Add this
"JsonUtilities" // <--- Add this
});
Then create a Blueprint Function Library class if you don’t have one for your project, and create this function ConvertStructToJsonString in the header file (.h) as shown below.
// Copyright 2025 Cool Glitch, Inc. All Rights Reserved.
#pragma once
#include "TutorialFunctionLibrary.generated.h"
#define UE_API TUTORIAL_API
UCLASS(MinimalAPI)
class UTutorialFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Converts any Struct type into a formatted JSON Object String.
* Useful for debugging, logging, or sending data to a server.
*
* @param InStruct The wildcard struct to convert.
* @param OutJsonString The resulting string in JSON format.
*/
UFUNCTION(
BlueprintPure,
CustomThunk,
meta = (CustomStructureParam = "InStruct", AutoCreateRefTerm = "InStruct"),
Category = "Tutorial|Debug")
static UE_API void ConvertStructToJsonString(
UPARAM(DisplayName="Struct") const int32& InStruct,
UPARAM(DisplayName="JsonString") FString& OutJsonString);
/** The internal execution body for the CustomThunk. */
DECLARE_FUNCTION(execConvertStructToJsonString);
};
#undef UE_API
Because this is a CustomThunk, we don’t write a standard C++ function body. Instead, we write a DEFINE_FUNCTION that reads the memory stack directly and accesses the Blueprint Virtual Machine.
Define the function as follows. I added some comments inside the code so you can follow along with what is happening inside the function.
// Copyright 2025 Cool Glitch, Inc. All Rights Reserved.
#include "Utils/TutorialFunctionLibrary.h"
#include "JsonObjectConverter.h"
#include "Blueprint/BlueprintExceptionInfo.h"
#define LOCTEXT_NAMESPACE "TutorialFunctionLibrary"
#include UE_INLINE_GENERATED_CPP_BY_NAME(TutorialFunctionLibrary)
DEFINE_FUNCTION(UTutorialFunctionLibrary::execConvertStructToJsonString)
{
// Reset pointers to ensure we don't carry over garbage from previous steps.
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
// Read Wildcard Input (InStruct)
Stack.StepCompiledIn<FStructProperty>(nullptr);
const FStructProperty* ValueProp = CastField<FStructProperty>(Stack.MostRecentProperty);
const void* ValuePtr = Stack.MostRecentPropertyAddress;
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
// Step into the second parameter (OutJsonString).
Stack.StepCompiledIn<FStrProperty>(nullptr);
// We expect a String Property Reference only.
void* OutStringAddr = Stack.MostRecentPropertyAddress;
FString* OutStringPtr = static_cast<FString*>(OutStringAddr);
// Finish Blueprint vm Stack
P_FINISH;
// If any pointers are null, we cannot proceed safely.
if(!ValueProp || !ValuePtr || !OutStringPtr)
{
// Define the runtime exception
const FBlueprintExceptionInfo ExceptionInfo(
EBlueprintExceptionType::AbortExecution,
LOCTEXT(
"Tutorial_JsonError",
"Failed to resolve parameters for ConvertStructToJson. Ensure a valid struct is connected.")
);
// Throw the exception to the Blueprint VM
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
P_NATIVE_BEGIN;
// Safe failure state: Return an empty object string
if(OutStringPtr)
{
*OutStringPtr = TEXT("{}");
}
P_NATIVE_END;
}
else
{
P_NATIVE_BEGIN;
// Perform the conversion safely within the native block
FJsonObjectConverter::UStructToJsonObjectString(ValueProp->Struct.Get(), ValuePtr, *OutStringPtr);
P_NATIVE_END;
}
}
#undef LOCTEXT_NAMESPACE
Conclusion
With this simple function, you can now just drop this node whenever you need to inspect a Struct’s data. No more manual formatting for every struct in your project, and no more updating debug functions when your struct changes!



