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的底层机制实现的 , 也就是说 如果我们遇到嵌套型的数据结构 换一种方式进行 序列化/反序列化 也可以解决我们的问题.
所以现在问题就很清晰了 解决方案有两个:
- 修改UnrealBuildTool 让UE4的反射系统支持嵌套变量
- 修改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-