前些天在写UnrealAutomator的Web解析模块的时候,遇到了一些USTRUCT方面的问题,由于笔者以前并非UE4程序员,因此踩了一些坑,果断分享一下踩坑历程。

首先聊一下USTRUCT的生成。USTRUCT是UE4的特性之一,从非C++/UE4程序员的角度来讲,USTRUCT、UPROPERTY、GENERATED_BODY之类的概念类似于注释和装饰器的作用,可以在编译等时期将代码标识的内涵纳入自己的Runtime。举一个例子,UnrealAutomator中的UIModel.h:

#pragma once#include "CoreMinimal.h"
#include "Components/Widget.h"// 必须要有这个,include之后通过UE4刷新项目生成相关代码,用于支持USTRUCT等宏的识别
#include "UIModel.generated.h"/*** Query template for ui widget*/
USTRUCT()
struct FUIWidgetQuery
{GENERATED_BODY()public:UPROPERTY()int32 ID = 0;UPROPERTY()FString Name = TEXT("");UPROPERTY()FString Text = TEXT("");// properties below are relatively no need to be modifiedUPROPERTY()FString ClassName = TEXT("");UPROPERTY()bool bIsNameAsKeyword = false;UPROPERTY()bool bIsTextAsKeyword = false;FUIWidgetQuery(): ID(0), Name(TEXT("")), Text(TEXT("")), ClassName(TEXT("")), bIsNameAsKeyword(false), bIsTextAsKeyword(false){}FUIWidgetQuery(int32 InID,FString InName,FString InText,FString InClassName,bool bInIsNameAsKeyword,bool bInIsTextAsKeyword): ID(InID), Name(InName), Text(InText), ClassName(InClassName), bIsNameAsKeyword(bInIsNameAsKeyword), bIsTextAsKeyword(bInIsTextAsKeyword){}// 记得要有cpp文件实现它,当然不管有没有这个函数,都得加一个cpp文件bool IsMatch(UWidget* Widget,bool bIsDisabledIncluded = false,bool bIsInvisibleIncluded = false);
};

现在有一个需求,就是把一个以json字符串为body的http request解析成这个USTRUCT,自然而然会需要FJsonObject相关的功能。http request的body原生为TArray<uint8>的格式,得先转为FString,然后转为FJsonObject,之后再转为USTRUCT

// TArray<uint8> to FString
FString FWebUtil::GetRequestStringBody(const FHttpServerRequest& Request)
{// Body to utf8 string, should be called after CheckRequestContent(Request, bIsCheckUTF8 = true)TArray<uint8> RequestBodyBytes = Request.Body;FString RequestBodyString = FString(UTF8_TO_TCHAR(RequestBodyBytes.GetData()));UE_LOG(UALog, Log, TEXT("Request string body: %s"), *RequestBodyString);return RequestBodyString;
}// FString to TSharedPtr<FJsonObject>
TSharedPtr<FJsonObject> FCommonUtil::JsonParse(FString Str)
{// string to jsonTSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(Str);TSharedPtr<FJsonObject> JsonObject;bool bSuccess = FJsonSerializer::Deserialize(JsonReader, JsonObject);if (!bSuccess){return nullptr;}return JsonObject;
}// TSharedPtr<FJsonObject> to USTRUCT
// 就算用FJsonObjectConverter::JsonObjectStringToUStruct,也需要经历先转到Json再转到USTRUCT的过程
template <typename UStructType>
static bool GetRequestUStructBody(const FHttpServerRequest& Request, UStructType* StructBody)
{verifyf(StructBody != nullptr, TEXT("USTRUCT to be converted should not be null~"));TSharedPtr<FJsonObject> JsonBody = GetRequestJsonBody(Request);if (JsonBody == nullptr){return false;}// extend/update struct with json values// 如果原来struct有预设值,在json convert中,会覆盖原来的值if (!FJsonObjectConverter::JsonObjectToUStruct<UStructType>(JsonBody.ToSharedRef(), StructBody, 0, 0)){UE_LOG(UALog, Warning, TEXT("failed to parse json body to ustruct!"))return false;}if (StructBody == nullptr){UE_LOG(UALog, Warning, TEXT("cast to USTRUCT failed! struct ptr is still null!"));return false;}UE_LOG(UALog, Log, TEXT("convert to USTRUCT successfully!"));return true;
}

值得一提的是,json转USTRUCT的过程中,不能通过传空指针USTRUCT的方式企图希望FJsonObjectConverter::JsonObjectToUStruct能够对USTRUCT进行重赋值。一方面是C++左右值的特性引起的,另一方面在转属性的过程中,也需要读取原来USTRUCT的属性值相关信息,如果传进去的是空指针就会crash。

// 模板json转ustruct函数
template<typename OutStructType>
static bool JsonObjectToUStruct(const TSharedRef<FJsonObject>& JsonObject, OutStructType* OutStruct, int64 CheckFlags = 0, int64 SkipFlags = 0)
{return JsonObjectToUStruct(JsonObject, OutStructType::StaticStruct(), OutStruct, CheckFlags, SkipFlags);
}// 跳到JsonAttributesToUStruct
bool FJsonObjectConverter::JsonObjectToUStruct(const TSharedRef<FJsonObject>& JsonObject, const UStruct* StructDefinition, void* OutStruct, int64 CheckFlags, int64 SkipFlags)
{return JsonAttributesToUStruct(JsonObject->Values, StructDefinition, OutStruct, CheckFlags, SkipFlags);
}// 跳到JsonAttributesToUStructWithContainer
bool FJsonObjectConverter::JsonAttributesToUStruct(const TMap< FString, TSharedPtr<FJsonValue> >& JsonAttributes, const UStruct* StructDefinition, void* OutStruct, int64 CheckFlags, int64 SkipFlags)
{return JsonAttributesToUStructWithContainer(JsonAttributes, StructDefinition, OutStruct, StructDefinition, OutStruct, CheckFlags, SkipFlags);
}bool JsonAttributesToUStructWithContainer(const TMap< FString, TSharedPtr<FJsonValue> >& JsonAttributes, const UStruct* StructDefinition, void* OutStruct, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags)
{// 如果是FJsonObjectWrapper,可以直接转换if (StructDefinition == FJsonObjectWrapper::StaticStruct()){// Just copy it into the objectFJsonObjectWrapper* ProxyObject = (FJsonObjectWrapper *)OutStruct;ProxyObject->JsonObject = MakeShared<FJsonObject>();ProxyObject->JsonObject->Values = JsonAttributes;return true;}// 如果未声明properties,直接返回int32 NumUnclaimedProperties = JsonAttributes.Num();if (NumUnclaimedProperties <= 0){return true;}// iterate over the struct propertiesfor (TFieldIterator<UProperty> PropIt(StructDefinition); PropIt; ++PropIt){UProperty* Property = *PropIt;// Check to see if we should ignore this propertyif (CheckFlags != 0 && !Property->HasAnyPropertyFlags(CheckFlags)){continue;}if (Property->HasAnyPropertyFlags(SkipFlags)){continue;}// find a json value matching this property nameconst TSharedPtr<FJsonValue>* JsonValue = JsonAttributes.Find(Property->GetName());if (!JsonValue){// we allow values to not be found since this mirrors the typical UObject mantra that all the fields are optional when deserializingcontinue;}if (JsonValue->IsValid() && !(*JsonValue)->IsNull()){// 这里就需要取到OutStruct的属性的Value值了,如果是OutStruct是空指针,就会crash掉void* Value = Property->ContainerPtrToValuePtr<uint8>(OutStruct);if (!JsonValueToUPropertyWithContainer(*JsonValue, Property, Value, ContainerStruct, Container, CheckFlags, SkipFlags)){UE_LOG(LogJson, Error, TEXT("JsonObjectToUStruct - Unable to parse %s.%s from JSON"), *StructDefinition->GetName(), *Property->GetName());return false;}}if (--NumUnclaimedProperties <= 0){// If we found all properties that were in the JsonAttributes map, there is no reason to keep looking for more.break;}}return true;
}

规避了这些坑,FJsonObject转USTRUCT就顺利了

【极客日常】解决UE4中FJsonObject转USTRUCT的坑相关推荐

  1. 极客”一词,来自于美国俚语“geek”的音译,一般理解为性格古怪的人

    起源 " 极客"一词,来自于美国俚语" geek"的音译,一般理解为性格古怪的人.数学"极客"大多是指,并不 一定是数学专业但又对数学等技术 ...

  2. 进化:从孤胆极客到高效团队_极客历史记录的本周:Gmail公开,国际象棋获得深蓝胜利以及托马斯·爱迪生的诞生...

    进化:从孤胆极客到高效团队 Every week we bring you a snapshot of the week in Geek History. This week we're taking ...

  3. 塞尔达 amiibo_极客历史的本周:塞尔达(Zelda)25岁,印刷机的诞生,以及ENIAC的揭幕...

    塞尔达 amiibo Every week we bring you interesting highlights from the history of geekdom. This week we ...

  4. 4个小例子告诉你:如何成为一名数据极客

    对于数据岗位的员工,互联网公司颇有些不同的称谓,像统计工程师.大数据工程师.数据分析师.算法工程师.数据科学家等,每一种之间的技能差距简直是风马牛不相及.但我觉得,数据岗位的需求千变万化,真正能通过数 ...

  5. 数字化“团险”黑科技,保险极客技术升级背后心经

    作者 | 宋慧 出品 | CSDN 云计算 疫情之后,一切都在"内卷",HR 也逃不过.初创公司想要招到优秀人才,除了对市场和未来发展的预期和潜力,提供补充医疗险也是对人才重要的保 ...

  6. 【观察】保险极客:技术创新与平台“进化”,应对企业团险的千变万化

    申耀的科技观察 读懂科技,赢取未来! 企业面临最大的威胁不是对手,而是趋势.今天,数字化转型已成为颠覆性力量,很多行业被裹挟其中,或主动或被动,都在惴惴不安地等待一个叫"奇点"的到 ...

  7. [代码审计][PHAR]巅峰极客babyphp2学习压缩过滤器触发phar

    前言:我要当赛棍!!! 文章目录 序列化与反序列化 基本介绍 PHP反序列化漏洞原理 常用的魔法函数 __wakeup()绕过:CVE-2016-7124 __set:巅峰极客babyphp2 解决p ...

  8. 【极客时间】《Java并发编程实战》学习笔记

    目录: 开篇词 | 你为什么需要学习并发编程? 内容来源:开篇词 | 你为什么需要学习并发编程?-极客时间 例如,Java 里 synchronized.wait()/notify() 相关的知识很琐 ...

  9. 11.10极客庙会@北京,邀请500极客逛“庙会”!

    To所有极客.开发者小哥哥小姐姐: 11月10日DoraHacks为你组织了一场专属于极客们的庙会!最新研发成果,也有来自区块链.AI行业中的顶级极客团队介绍他们的新项目.10+顶级资本会在现场助阵: ...

最新文章

  1. Redis分布式锁,看完不懂你打我
  2. InnoDB引擎与MyIASM的一点总结
  3. ffmpeg 截图太模糊了_PPT图片模糊?导师说放大!
  4. 使用LiteOS Studio图形化查看LiteOS在STM32上运行的奥秘
  5. 20190806:字符串解密
  6. 【POJ 1182】食物链【“拓展域”并查集】
  7. 单商户商城系统功能拆解13—分类管理
  8. matlab freqz m,freqz(freqz和freqs区别)
  9. BM3D、域变换与Non-Local
  10. html图片闪光效果,CSS3实现一个效果酷炫的闪光特效代码
  11. 函数签名function signature是什么意思
  12. 华为手机灵敏度设置_和平精英华为手机灵敏度怎么设置 上分灵敏度推荐
  13. 解决:RN和H5之间通信
  14. Codility每周一课:P8.2 Dominator
  15. 厦大计算机学硕考研难度,一个班半数考研 厦大录取仅两成 读研是鸡肋还是围城...
  16. GeoServer中的WPS服务
  17. Ogre骨骼动画分析
  18. 网页、网站和HTML
  19. NOIP2016普及组复赛第一题——买铅笔
  20. 搜索计算机无法输入法,电脑输入法不能切换怎么办 电脑输入法中英文切换不了解决方法...

热门文章

  1. #千锋逆战班,拼搏永向前#
  2. 夜访阿里总部:实拍双11背后神秘的程序员!
  3. 如何替换一个Linux内核函数的实现-热补丁原理
  4. 科研实习 | 新加坡国立大学尤洋老师课题组招收Data-centric AI科研实习生
  5. kinetis晶体谐振器到底要不要加电容和电阻
  6. 如何注册小程序账号和下载小程序开发工具
  7. Redis-PHP实战篇——常用的使用场景
  8. 武汉全款买房,普通人不吃不喝需要10年,这位程序员只用了5年
  9. 你真的会选择前端培训班吗
  10. 计算机死机黑屏怎么办,电脑卡机后黑屏怎么办