内容简介: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 发布,支持表和块引用嵌套
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
引爆社群:移动互联网时代的新4C法则(第2版)
唐兴通 / 机械工业出版社 / 69.00元
社群已经被公认为是这个时代的商业新形态,原有的商业逻辑和方法被颠覆,新的基于社群的商业体系和规则亟待构建,今天几乎所有的企业都在为此而努力,都在摸索中前行。 本书提出的“新4C法则”为社群时代的商业践行提供了一套科学的、有效的、闭环的方法论,第1版上市后获得了大量企业和读者的追捧,“新4C法则”在各行各业被大量解读和应用,积累了越来越多的成功案例,被公认为是社群时代通用的方法论。也因此,第1......一起来看看 《引爆社群:移动互联网时代的新4C法则(第2版)》 这本书的介绍吧!