内容简介:Tips: 让UE4支持Json嵌套 (How to Support Nested at UE4)UE4项目开发笔记(三)开UE4的坑已经五月有余,这五个月里用各种姿势躺过不同的坑,有横着躺的;有竖着躺的;有睁着眼躺的…Orz…
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 <fjsonvalue> >& 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 <fjsonvalue> >& 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; } // ... 省略一吨代码 } </fjsonvalue> </fjsonvalue>
以上两处代码 , 分别是执行 序列化 (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 <fjsonobject> JsonObject; explicit operator bool() const { return JsonObject.IsValid(); } virtual ~FJsonObjectNested(){} virtual void JsonObjectToUStruct() {} virtual void UStructToJsonObject(TMap< FString, TSharedPtr <fjsonvalue> >& OutJsonAttributes) {} }; </fjsonvalue> </fjsonobject>
第二步 在UE4 Json底层添加 嵌套数据结构序列化自定义操作 – 添加到 JsonObjectConverter.cpp
bool FJsonObjectConverter::UStructToJsonAttributes( const UStruct* StructDefinition , const void* Struct , TMap< FString, TSharedPtr <fjsonvalue> >& 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); } // 自定义序列化操作结束 //------------------------------------------------------------------------- // .. 省略一吨代码 } </fjsonvalue>
第三步 在UE4 Json底层添加 嵌套数据结构反序列化自定义操作 – 添加到 JsonObjectConverter.cpp
bool FJsonObjectConverter::JsonAttributesToUStruct( const TMap< FString, TSharedPtr <fjsonvalue> >& 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(); } // 自定义反序列化操作结束 //------------------------------------------------------------------------- // .. 省略一吨代码 } </fjsonvalue>
第四步 修改 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 <ejson::object> (key)) { return; } TSharedPtr <fjsonobject> JOmapFTA = JsonObject->GetObjectField(key); for (auto e = JOmapFTA->Values.CreateIterator(); e; ++e) { // key FString keyMap = e.Key(); // Array TArray <tsharedptr> <fjsonvalue> > JsonParsed = JOmapFTA->GetArrayField(keyMap); TArray <fjsonstruct1> 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 <fjsonvalue> >& OutJsonAttributes) override { const FString VariableName = TEXT("mapFTA"); if (true == OutJsonAttributes.Contains(VariableName) || 0 == mapFTA.Num()) { return; } TSharedPtr <fjsonobject> JOmapFTA = MakeShareable(new FJsonObject); for (auto e = mapFTA.CreateIterator(); e; ++e) { // key FString keyMap = e.Key(); TArray <fjsonstruct1> valueMap = e.Value(); if (0 == valueMap.Num()) { continue; } // Array TSharedPtr <fjsonobject> ObjectArray = MakeShareable(new FJsonObject); TArray <tsharedptr> <fjsonvalue> > JsonValueArray; for (auto v : valueMap) { FString toJson; TSharedRef <fjsonobject> JOmapFTA = MakeShareable(new FJsonObject()); if (FJsonObjectConverter::UStructToJsonObject(FJsonStruct1::StaticStruct(), &v, JOmapFTA, 0, 0)) { TSharedPtr <fjsonvalue> JVFTileData = MakeShareable(new FJsonValueObject(JOmapFTA)); JsonValueArray.Add(JVFTileData); } } JOmapFTA->SetArrayField(keyMap, JsonValueArray); } TSharedPtr </fjsonvalue> <fjsonvalue> JVmapFTA = MakeShareable(new FJsonValueObject(JOmapFTA)); OutJsonAttributes.Add(VariableName, JVmapFTA); } }; </fjsonvalue> </fjsonobject> </fjsonvalue> </tsharedptr> </fjsonobject> </fjsonstruct1> </fjsonobject> </fjsonvalue> </fjsonstruct1> </fjsonvalue> </tsharedptr> </fjsonobject> </ejson::object>
如此便可实现 UE4中 对于JSON嵌套型数据结构的 序列化和反序列化
-EOF-
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Vardump 更新,支持打印各种 Java 数据结构,支持嵌套
- Vardump 更新,支持打印各种 Java 数据结构,支持嵌套
- 让Proxmox VE支持嵌套虚拟化
- 苞米豆-多数据源 2.3.4 发布:优化底层,支持嵌套切换
- 苞米豆-多数据源 2.3.4 发布:优化底层,支持嵌套切换
- CKEditor 5 v27.1.0 发布,支持表和块引用嵌套
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
嵌入式系统软件设计中的常用算法
周航慈 / 2010-1 / 24.00元
《嵌入式系统软件设计中的常用算法》根据嵌入式系统软件设计需要的常用算法知识编写而成。基本内容有:线性方程组求解、代数插值和曲线拟合、数值积分、能谱处理、数字滤波、数理统计、自动控制、数据排序、数据压缩和检错纠错等常用算法。从嵌入式系统的实际应用出发,用通俗易懂的语言代替枯燥难懂的数学推导,使读者能在比较轻松的条件下学到最基本的常用算法,并为继续学习其他算法打下基础。 《嵌入式系统软件设计中的......一起来看看 《嵌入式系统软件设计中的常用算法》 这本书的介绍吧!