让UE4支持Json嵌套

Tips: 让UE4支持Json嵌套 (How to Support Nested at UE4)

UE4项目开发笔记(三)

开UE4的坑已经五月有余,这五个月里用各种姿势躺过不同的坑,有横着躺的;有竖着躺的;有睁着眼躺的…Orz…
这周躺了一个老坑,此坑在Unity也被蹂躏过一次.(别以为换了皮肤我就不认识你了 ( ╯□╰ ))

0x00. 基础知识

简单唠一句 , UE4在自己的反射机制之上实现的JSON 序列化反序列化 . 并非使用目前主流的任何JSON C++库.(libJson jsoncpp jsonFx rapidjson or other else..)

0x01. 问题再现

项目里几乎所有数据都使用了Json,用户生产的数据使用Json保存,网络请求数据使用Json往返. 由于小伙伴们设计的数据结构实在复杂 , 不免产生各种复杂的结构体 , 例如:

TMap < FString,FString > mapFF;
TMap < FString,TArray < FJsonStruct1 > > mapFTA;
TMap < FString,TMap < FString,FJsonStruct1 > > mapFTM;

以上三种数据变量 ,mapFF 是一个普通的字典变量 , 这个在UE4里是没问题可以正常, 在大多数UE4支持的UPROPERTY变量中 通过 FJsonObjectConverter 序列化和反序列化的.一般的处理方式是这样的:

// 定义F型数据结构
USTRUCT()
struct FJsonStruct1
{
    GENERATED_USTRUCT_BODY()
    UPROPERTY()
        int nIndex;
    UPROPERTY()
        TMap < FString, FString > mapFF;
    FJsonStruct1()
    {
        mapFF.Empty();
    }
};
// JSON序列化处理
FJsonStruct1 f1;
f1.nIndex = 100;
f1.mapFF.Add("A1", "Value1");
f1.mapFF.Add("A2", "Value2");
FString strToJson;
if (FJsonObjectConverter::UStructToJsonObjectString(f1, strToJson, 0, 0))
{
    // strToJson == { "nIndex":100,"mapFF":{ "A1": "Value1", "A2": "Value2"  }}
}
// JSON反序列化处理
FJsonStruct1 f2;
if (FJsonObjectConverter::JsonObjectStringToUStruct(strToJson,&f2,0,0))
{
    // do something
}

一般情况下 通过以上代码片段 即处理绝大部分的JSON数据结构, 但是遇到嵌套型的数据结构 就要歇菜了.举个例子:

// 定义F型数据结构
USTRUCT()
struct FJsonStruct2
{
    GENERATED_USTRUCT_BODY()

    UPROPERTY()
        TMap < FString, TArray < FJsonStruct1 > > mapFTA;
    FJsonStruct2()
    {
        mapFTA.Empty();
    }
};

以上结构定义 , 编译的时候UE4会爆出ERROR:

error : Nested containers are not supported.(嵌套的结构并不支持)

通过以上信息跟踪报错代码大概在 – UHT – HeaderParser.cpp , 通过阅读UHT源码 发现如果改动UHT让它支持嵌套结构 这也是一个解决方案.

接下来我们看看UE4的Json模块,代码位置在 Json , JsonUtilities , 通过阅读源码 , 我们发现UE4的Json 序列化 反序列化是依赖 UE4的底层机制实现的 , 也就是说 如果我们遇到嵌套型的数据结构 换一种方式进行 序列化/反序列化 也可以解决我们的问题.

所以现在问题就很清晰了 解决方案有两个:

  1. 修改UnrealBuildTool 让UE4的反射系统支持嵌套变量
  2. 修改UE4 Json模块 让Json模块支持嵌套变量的序列化反序列化

0x02. 解决思路分析

  • 修改UnrealBuildTool 让UE4的反射系统支持嵌套变量

这个是我首选的方案 , 在尝试了几天之后只能放弃 , 放弃理由:修改每一句UBT代码 就会导致UE4全体的重新编译,通过修改UBT让UE4支持嵌套变量 进而实现解析JSON嵌套数据 感觉有一点杀鸡牛刀 (其实是自我安慰..UBT功力不足改不动哇..泪目) , 所以我们使用第二种思路.

  • 修改UE4 Json模块 让Json模块支持嵌套变量的序列化反序列化

通过阅读UE4 Json源码 我们很容易可以发现几个地方:

// 反序列化的关键位置 JsonObjectConverter.cpp -> JsonAttributesToUStruct
bool FJsonObjectConverter::JsonAttributesToUStruct(
const TMap< FString, TSharedPtr >& JsonAttributes
, const UStruct* StructDefinition
, void* OutStruct
, int64 CheckFlags
, int64 SkipFlags)
{
    if (StructDefinition == FJsonObjectWrapper::StaticStruct())
    {
        // Just copy it into the object
        FJsonObjectWrapper* ProxyObject = (FJsonObjectWrapper *)OutStruct;
        ProxyObject->JsonObject = MakeShareable(new FJsonObject());
        ProxyObject->JsonObject->Values = JsonAttributes;
        return true;
    }
    // ... 省略一吨代码
}
// 序列化关键地方 JsonObjectConverter.cpp -> UStructToJsonAttributes
bool FJsonObjectConverter::UStructToJsonAttributes(
const UStruct* StructDefinition
, const void* Struct
, TMap< FString, TSharedPtr >& OutJsonAttributes
, int64 CheckFlags, int64 SkipFlags
, const CustomExportCallback* ExportCb)
{
    if (SkipFlags == 0)
    {
        // If we have no specified skip flags, skip deprecated, transient and skip serialization by default when writing
        SkipFlags |= CPF_Deprecated | CPF_Transient;
    }

    if (StructDefinition == FJsonObjectWrapper::StaticStruct())
    {
        // Just copy it into the object
        const FJsonObjectWrapper* ProxyObject = (const FJsonObjectWrapper *)Struct;

        if (ProxyObject->JsonObject.IsValid())
        {
            OutJsonAttributes = ProxyObject->JsonObject->Values;
        }
        return true;
    }
    // ... 省略一吨代码
}

以上两处代码 , 分别是执行 序列化 (Struct 2 String) 和 反序列化(String 2 Struct) 的关键位置 , 我们可以看到 在这个地方 JsonObject 已经被Parser出来 , 这个JsonObject 就是当前变量的JsonObject. 我们可以在这里加入我们的自定义解析.

0x03 . 解决方案 (若你遇到此问题 以下内容请务必逐字阅读)

针对序列化和反序列化嵌套型结构体 例如如下数据结构:

USTRUCT()
struct FJsonStruct2 
{
    GENERATED_USTRUCT_BODY()
    UPROPERTY()
        int nIndex;
    // UPROPERTY() --> 注意 我们不需要加 UPROPERTY
        TMap < FString, TArray < FJsonStruct1 > > mapFTA;
}

第一步 在UE4 Json底层添加嵌套自定义结构体 – 添加到JsonObjectWrapper.h

// Add this Struct in JsonObjectWrapper.h
USTRUCT(BlueprintType)
struct JSONUTILITIES_API FJsonObjectNested
{
    GENERATED_USTRUCT_BODY()
public:
    UPROPERTY(EditAnywhere, Category = "JSON")
        FString JsonString;
    TSharedPtr JsonObject;

    explicit operator bool() const
    {
        return JsonObject.IsValid();
    }
    virtual ~FJsonObjectNested(){}
    virtual void JsonObjectToUStruct() {}
    virtual void UStructToJsonObject(TMap< FString, TSharedPtr >& OutJsonAttributes) {}
};

第二步 在UE4 Json底层添加 嵌套数据结构序列化自定义操作 – 添加到JsonObjectConverter.cpp

bool FJsonObjectConverter::UStructToJsonAttributes(
const UStruct* StructDefinition
, const void* Struct
, TMap< FString, TSharedPtr >& OutJsonAttributes
, int64 CheckFlags
, int64 SkipFlags
, const CustomExportCallback* ExportCb)
{
    if (SkipFlags == 0)
    {
        // If we have no specified skip flags, skip deprecated, transient and skip serialization by default when writing
        SkipFlags |= CPF_Deprecated | CPF_Transient;
    }

    if (StructDefinition == FJsonObjectWrapper::StaticStruct())
    {
        // Just copy it into the object
        const FJsonObjectWrapper* ProxyObject = (const FJsonObjectWrapper *)Struct;

        if (ProxyObject->JsonObject.IsValid())
        {
            OutJsonAttributes = ProxyObject->JsonObject->Values;
        }
        return true;
    }
    //-------------------------------------------------------------------------
    // 自定义序列化操作开始
    // set convert the Neste property  on the output object by Rect 2018-10-10 14:57
    if (StructDefinition->GetSuperStruct() == FJsonObjectNested::StaticStruct())
    {
        // Just copy it into the object
        FJsonObjectNested* ProxyObject = (FJsonObjectNested *)Struct;
        ProxyObject->UStructToJsonObject(OutJsonAttributes);
    }
    // 自定义序列化操作结束
    //-------------------------------------------------------------------------
    // .. 省略一吨代码
}

第三步 在UE4 Json底层添加 嵌套数据结构反序列化自定义操作 – 添加到JsonObjectConverter.cpp

bool FJsonObjectConverter::JsonAttributesToUStruct(
const TMap< FString, TSharedPtr >& JsonAttributes
, const UStruct* StructDefinition
, void* OutStruct, int64 CheckFlags, int64 SkipFlags)
{
    if (StructDefinition == FJsonObjectWrapper::StaticStruct())
    {
        // Just copy it into the object
        FJsonObjectWrapper* ProxyObject = (FJsonObjectWrapper *)OutStruct;
        ProxyObject->JsonObject = MakeShareable(new FJsonObject());
        ProxyObject->JsonObject->Values = JsonAttributes;
        return true;
    }

    //-------------------------------------------------------------------------
    // 自定义反序列化操作开始
    //  find a json value matching Neste property  by Rect 2018-10-10 14:57
    if (StructDefinition->GetSuperStruct() == FJsonObjectNested::StaticStruct())
    {
        FJsonObjectNested* ProxyObject = (FJsonObjectNested *)OutStruct;
        ProxyObject->JsonObject = MakeShareable(new FJsonObject());
        ProxyObject->JsonObject->Values = JsonAttributes;
        ProxyObject->JsonObjectToUStruct();
    }
    // 自定义反序列化操作结束
    //-------------------------------------------------------------------------
    // .. 省略一吨代码
}

第四步 修改FJsonStruct2 继承 FJsonObjectNested 并重写 JsonObjectToUStruct,UStructToJsonObject函数

USTRUCT()
struct FJsonStruct2 : public FJsonObjectNested // 继承 FJsonObjectNested
{
    GENERATED_USTRUCT_BODY()

    // UPROPERTY()
        TMap < FString, TArray < FJsonStruct1 > > mapFTA;

    FJsonStruct2()
    {
        mapFTA.Empty();
    }
    // 重写 JsonObjectToUStruct
    virtual void JsonObjectToUStruct() override
    {
        const FString key = TEXT("mapFTA");
        if (false == JsonObject.IsValid() || false == JsonObject->HasTypedField(key))
        {
            return;
        }

        TSharedPtr JOmapFTA = JsonObject->GetObjectField(key);

        for (auto e = JOmapFTA->Values.CreateIterator(); e; ++e)
        {
            // key
            FString keyMap = e.Key();
            // Array
            TArray> JsonParsed = JOmapFTA->GetArrayField(keyMap);
            TArray argvArr;
            for (auto argv : JsonParsed)
            {
                FJsonStruct1 fff;
                if (FJsonObjectConverter::JsonObjectToUStruct(argv->AsObject().ToSharedRef(), &fff))
                {
                    argvArr.Add(fff);
                }
            }
            mapFTA.Add(keyMap, argvArr);
        }
    }
    // 重写 UStructToJsonObject
    virtual void UStructToJsonObject(TMap< FString, TSharedPtr >& OutJsonAttributes) override
    {
        const FString VariableName = TEXT("mapFTA");
        if (true == OutJsonAttributes.Contains(VariableName) || 0 == mapFTA.Num())
        {
            return;
        }

        TSharedPtr JOmapFTA = MakeShareable(new FJsonObject);

        for (auto e = mapFTA.CreateIterator(); e; ++e)
        {
            // key
            FString keyMap = e.Key();
            TArray valueMap = e.Value();
            if (0 == valueMap.Num())
            {
                continue;
            }
            // Array
            TSharedPtr ObjectArray = MakeShareable(new FJsonObject);
            TArray > JsonValueArray;
            for (auto v : valueMap)
            {
                FString toJson;
                TSharedRef JOmapFTA = MakeShareable(new FJsonObject());
                if (FJsonObjectConverter::UStructToJsonObject(FJsonStruct1::StaticStruct(), &v, JOmapFTA, 0, 0))
                {
                    TSharedPtr JVFTileData = MakeShareable(new FJsonValueObject(JOmapFTA));
                    JsonValueArray.Add(JVFTileData);
                }
            }

            JOmapFTA->SetArrayField(keyMap, JsonValueArray);
        }

        TSharedPtr JVmapFTA = MakeShareable(new FJsonValueObject(JOmapFTA));
        OutJsonAttributes.Add(VariableName, JVmapFTA);
    }
};

如此便可实现 UE4中 对于JSON嵌套型数据结构的 序列化和反序列化

-EOF-

发表评论

电子邮件地址不会被公开。 必填项已用*标注