This article is the 20th day post for the Unreal Engine 4 (UE4) Advent Calendar 2019.

Unreal Engine 4 (UE4) Advent Calendar 2019 - Qiita

I have a plugin called ObjectDeliverer published on the Marketplace.

This article is about the “function to serialize objects to Json” implemented in this plugin.

For example, if you have a Blueprint (inheriting from Object) with Variables defined like this,

It will be converted into a Json string like this.

{
    "IntValue":0,
    "BoolValue":false,
    "StringValue":"ABC"
}

Since Json is represented as a string, it can be saved directly to a text file. Also, being a string, its content is easily understandable by humans.

By also creating a function to restore objects from this Json, saving and restoring objects via Json becomes possible.

In ObjectDeliverer, by sending and receiving this Json string over communication, objects can be delivered to another location (another application).

Handling Json in UE4

UE4 comes standard with modules named Json and JsonUtilities.

Json Module

A module that supports reading and writing Json. However, it cannot be used in Blueprints and is only supported in C++.

It can be used like this.

Json Object -> Json String

// Create FJsonObject (container for Json data)
TSharedPtr<FJsonObject> jsonObject = MakeShareable(new FJsonObject());

// Add properties to jsonObject

// Create a Writer to write Json to FString
FString OutputString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);

// Write Json to FString
FJsonSerializer::Serialize(jsonObject.ToSharedRef(), Writer);

// Json string is in OutputString, do something with it

Json String -> Json Object

// FString containing the json string
FString jsonString;

// Create a Reader to read Json from FString
TSharedRef<TJsonReader<TCHAR>> JsonReader = TJsonReaderFactory<TCHAR>::Create(jsonString);

// Create FJsonObject (container for Json data)
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject());

// Read from Json string into Json object
FJsonSerializer::Deserialize(JsonReader, JsonObject);

// Extract properties from jsonObject and do something

Get, Set Json Object Values

TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject());

// Set values
JsonObject->SetNumberField("IntValue", 1);
JsonObject->SetBoolField("BoolValue", true);
JsonObject->SetStringField("StringValue", "ABCDEFG");

// Get values
int32 intValue = JsonObject->GetIntegerField("IntValue");
bool boolValue = JsonObject->GetBoolField("BoolValue");
FString stringValue = JsonObject->GetStringField("StringValue");

// Array and Object properties can also be Get, Set

Knowing the above patterns should cover most cases.

JsonUtilities Module

Separate from the Json module, there is a JsonUtilities module. This is a support module that uses the Json module.

Inside it, there is a class called FJsonObjectConverter, which sounds like it could convert objects to Json based on the name.

However, looking inside, it was a class that provides mutual conversion between UStruct and JSON. What I want to achieve is mutual conversion between UObject and JSON, so it was slightly different. Disappointing.

By the way, this functionality can be used by calling the following two static methods.

  • UStructToJsonObject
  • JsonObjectToUStruct

Creating UObject and JSON Mutual Conversion Yourself

First, although I wrote creating yourself, it’s almost the same as the implementation of the FJsonObjectConverter class.

https://github.com/ayumax/ObjectDeliverer/blob/master/Plugins/ObjectDeliverer/Source/ObjectDeliverer/Private/Utils/ObjectJsonSerializer.cpp

If you compare it side-by-side with the FJsonObjectConverter class, you’ll see they are almost identical.

I only added the UObject handling process to the implementation of the FJsonObjectConverter class.

The complete code is at the link above, but I will explain only the representative parts for realizing the functionality.

UObject -> JsonObject

This is the process of converting UObject to Json.

First, use TFieldIterator to get a list of UObject properties (including variables defined in Blueprints) and process them sequentially.

In a pure C++ program not using Unreal Engine, this kind of thing (reflection) is not possible. (Although I’ve seen articles discussing it for future C++ plans)

In UE4 C++, reflection is apparently called the property system.

Details are written here. Unreal Property System (Reflection)

Once property information (UProperty) is obtained, the property name and the actual property value can also be retrieved using it.

After that, Json properties can be created in the same way as setting properties on a Json object described earlier.

However, since nested structures where a UObject contains a UObject property also need to be considered, the processing here is done recursively.

TSharedPtr<FJsonObject> UObjectJsonSerializer::CreateJsonObject(const UObject* Obj)
{
    TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject());

    if (!Obj) return JsonObject;

    // Process the list of UObject properties sequentially
    for (TFieldIterator<UProperty> PropIt(Obj->GetClass(), EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt)
    {
        UProperty* Property = *PropIt;

        // Get property name
        FString PropertyName = Property->GetName();

        // Get property value
        uint8* CurrentPropAddr = PropIt->ContainerPtrToValuePtr<uint8>((UObject*)Obj);

        FJsonObjectConverter::CustomExportCallback CustomCB;
        CustomCB.BindStatic(UObjectJsonSerializer::ObjectJsonCallback);

        // Set to Json property
        // FJsonObjectConverter::UPropertyToJsonValue is processed recursively
        JsonObject->SetField(PropertyName, FJsonObjectConverter::UPropertyToJsonValue(Property, CurrentPropAddr, 0, 0, &CustomCB));
    }

    return JsonObject;
}

JsonObject -> UObject

This is the process of restoring UObject from Json.

Similar to the process of converting UObject to Json, this also uses the UE4 property system to dynamically obtain the property list and process it sequentially.

The flow is to obtain UProperty, get the property name from it, then get the Json property from JsonObject and set it to the UObject’s property.

This function is also called recursively to support nested structures.

bool UObjectJsonSerializer::JsonObjectToUObject(const TSharedPtr<FJsonObject>& JsonObject, UObject* OutObject)
{
    auto& JsonAttributes = JsonObject->Values;

    // Get the property list of the UObject to restore
    for (TFieldIterator<UProperty> PropIt(OutObject->GetClass()); PropIt; ++PropIt)
    {
        UProperty* Property = *PropIt;

        // Get Json property from JsonObject using property name as key
        const TSharedPtr<FJsonValue>* JsonValue = JsonAttributes.Find(Property->GetName());
        if (!JsonValue)
        {
            continue;
        }

        if (JsonValue->IsValid() && !(*JsonValue)->IsNull())
        {
            // Get pointer to UObject property
            void* Value = Property->ContainerPtrToValuePtr<uint8>(OutObject);

            // Read value from Json property and set to UObject property
            // JsonValueToUProperty is processed recursively
            if (!JsonValueToUProperty(*JsonValue, Property, Value))
            {
                return false;
            }
        }
    }

    return true;
}

Some Problems

In ObjectDeliverer, the above ObjectJsonSerializer is used within UObjectDeliveryBoxUsingJson to serialize UObjects to Json.

I also use this feature in my own projects, and while it generally works as intended, some points for improvement have emerged.

Need to Specify UObject Type During JsonObject -> UObject

As explained in the JsonObject -> UObject process description above, the UObject type must be predetermined for this process.

Looking at the Json format, you can see that Json has no mechanism to store the object type.

Therefore, just looking at the Json, it’s impossible to know which UObject to restore to, so the UObject type needs to be decided beforehand.

Because of this, communication using ObjectDeliverer’s UObjectDeliveryBoxUsingJson currently only supports sending and receiving a single UObject type. It cannot handle multiple types with normal usage.

Potential Information Loss When Having UObject Type Properties

This problem also arises because Json cannot store type information, similar to the previous issue.

For example, consider an environment where ClassB inherits from ClassA.

In that environment, the UObject saved to Json has a property of type ClassA.

An instance of ClassB can be put into this ClassA type property.

When saving to and restoring from Json at this time, properties that exist only in ClassB but not in ClassA will not be restored…

This is because the UProperty indicating ClassA type is used during property restoration.

This can be avoided if understood and used correctly, but it might lead to hard-to-notice bugs.

Json Property Name May Not Match the Specified String

For example, when converting the Blueprint shown in the example at the beginning to Json, it might end up like this.

{
    "Intvalue":0,
    "boolValue":false,
    "Stringvalue":"ABC"
}

At first glance, it seems to be working correctly, but the case of the property names is wrong.

This is because UE4 properties use the FName type.

FName

As mentioned on the page above, FName is case-insensitive, which causes this phenomenon.

No problem occurs if both saving to and restoring from Json use ObjectDeliverer within the same UE4 project, but problems (property name mismatch) may arise when exchanging Json with applications created in other development environments.

Countermeasures for Problems

Actually, I wanted to release an update with countermeasures for the above problems by the time this article was published, but it’s not complete yet… (Testing isn’t finished)

The work is being done on the following branch, and the implementation is almost complete, so you can see what it looks like.

The Json serialization part has undergone significant changes.

https://github.com/ayumax/ObjectDeliverer/tree/Remodeling-ObjectJsonSerializer

The countermeasures taken are the following two.

Write UObject Type Information into Json Based on User Selection

The first and second problems can be resolved by writing the UObject type information into the Json.

However, doing so changes the Json format from before, breaking compatibility, and purely increases the Json string length, making the data size larger, which might be undesirable from the perspective of data transmission, ObjectDeliverer’s main purpose.

Therefore, a mechanism is introduced where the user can choose whether or not to write type information into the Json for each UObject type.

This allows creating the same Json as before under the condition that only one type is sent/received and UObject properties using class inheritance are not created.

Also, by choosing to write the actual instance’s type information, reliable restoration can be achieved when saving and restoring UObjects.

Specifically, the following differences arise in the created Json.

When not writing type information (implementation up to now)

{
    "IntValue":0,
    "BoolValue":false,
    "StringValue":"ABC"
}

When writing type information

Example when serializing a UObject named NewBlueprint

{
    "Type":"NewBlueprint",
    "Body" :
    {
        "IntValue":0,
        "BoolValue" : false,
        "StringValue" : "ABC"
    }
}

Definition of an Interface to Implement Property Name Conversion Function

Since conversion from FName type to FString type might not result in the intended string if left to automatic conversion, an interface for property name conversion was added so users can define it reliably themselves.

If this interface is implemented on the UObject to be serialized, the custom property name conversion function will be used for writing and reading Json properties.

If the interface is not implemented, automatic FName -> FString conversion will occur as before.

By using this feature, it becomes possible to create Json properties with completely different strings, so I believe it has uses beyond solving the current problem.

Finally

This time, I wrote about serializing UObjects to JsonObjects.

The source code of FJsonObjectConverter from the JsonUtilities module mentioned in this article contains implementations that are helpful not only for creating UObject serialization to formats other than Json but also for utilizing the UE4 property system, so please take a look.

Also, the ObjectDeliverer I have published aims to easily create communication using TCP/IP, UDP, etc., using only Blueprints. It’s available for Free, so please try it if you like.

ObjectDeliverer: ayumax: Code Plugins - UE4 Marketplace