资讯动态

UE5中Struct与JSON双向转换全指南:从蓝图到C++实现

发布时间:2026/9/3 3:13:44 来源:尧图企业网站定制
在 UE5 项目里把数据从结构体Struct转成 JSON或者把 JSON 还原成结构体是一个非常高频的需求。存档系统、配置表加载、HTTP 请求响应解析、WebSocket 消息收发、服务端数据同步几乎每一个接入外部通信的项目都会遇到这套转换问题。我早期在做一个联机功能时为了把一份玩家属性结构体发给服务端不得不在蓝图中手写几十个 Append String 节点去拼 JSON解析响应时又要用 Get Field 一层层往下取代码又长又容易错。后来开始使用 StructJsonString 这类“结构体与 JSON 双向转换”的插件把整个流程简化成两个节点Struct 转 Json String、Json String 转 Struct。本文会把这类插件的完整能力拆开讲清楚包括它能处理哪些字段类型、蓝图中怎么调用、C 里底层是如何实现的、常见报错如何排查以及项目落地时应该注意的设计规范。不管你是刚开始接触 UE5 关卡蓝图的小白还是已经在做网络同步、存档系统的进阶开发者本文这套教程都适用。1. 为什么要做 Struct 与 JSON 的双向转换1.1 Struct 和 JSON 的天然差异在 UE5 中Struct结构体是蓝图和 C 中非常常用的数据容器。它可以把相互关联的一组字段组织在一起例如一个角色存档可能包含玩家名字、等级、金币、背包物品列表、最后位置坐标。Struct 在内存中的布局是紧凑的二进制结构访问字段时通过编译期确定的偏移量直接读取性能很好。JSON 则是一种纯文本的数据交换格式全称是 JavaScript Object Notation。它可以被几乎任何编程语言解析结构由键值对、数组、嵌套对象组成可读性很强也方便在日志、数据库、网络请求中传递。问题就在这里Struct 是内存中的二进制结构JSON 是文本。两者之间不能直接画等号必须经过“序列化”和“反序列化”两步转换。序列化Struct - JSON 字符串反序列化JSON 字符串 - Struct如果不借助插件或工具库蓝图里只能手动对字段做取值、类型转换、字符串拼接C 中虽然可以用 UE 自带的 Json 模块但代码量不小字段一变序列化代码也要跟着改。StructJsonString 这类插件的目的就是用通用节点自动完成这个过程让开发者不用关心每个字段怎么拼接只关心数据流。1.2 常见应用场景我在项目里整理了几类最常用的场景你可以对照自己的工作场景说明玩家存档把存档 Struct 序列化成 JSON 字符串写入本地文件或数据库读档时再还原成 Struct网络通信客户端与服务端通过 HTTP 或 WebSocket 交互请求体和响应体都是 JSON配置读取策划把 JSON 配置表导出到项目程序运行时解析成 Struct 供逻辑读取编辑器工具编辑器批处理时把批量生成的资源数据写成 JSON 报表数据对接UE5 作为客户端与 Python 数据分析服务、Web 后端、小程序后台交换数据调试日志把 Struct 序列化成字符串打印到 Output Log排查状态异常你会发现这些场景的共同点是UE5 需要和“外部世界”做数据交换。Struct 只在 UE 内部认识JSON 外部也认识双向转换就是一座桥。2. 环境准备与版本说明2.1 引擎与系统环境本文的实操以 Windows 环境下 UE5.x 为例。UE5.0 到 UE5.4 期间的接口整体变化不大但插件可能存在版本适配问题需要根据你的实际引擎小版本进行调整。如果你要在 C 中使用原生 Json 模块需要确认项目已经启用了以下模块JsonJsonUtilities可能需要 JsonObjectConverter这些模块通常都是引擎内置模块不需要额外下载只需要在项目的YourProject.Build.cs文件中声明。2.2 获取并启用 StructJsonString 插件不同作者发布的 JSON 转换插件在名称和节点命名上可能略有差异。本文以最常见的 StructJsonString 插件能力为例一般在虚幻商城Unreal Marketplace搜索 “StructJsonString” 或 “JSON Serialization” 就可以找到类似功能的插件。安装完成后在编辑器的 “Edit - Plugins” 中搜索插件名称确保勾选 Enabled然后重启编辑器。如果你是 UE5 源码版也可以直接把插件目录放到项目的Plugins文件夹下YourProject/Plugins/StructJsonString/StructJsonString.uplugin重启编辑器后插件一般会在蓝图的 “UI / Utilities / JSON” 或插件自定义分类下出现相关节点。2.3 版本兼容提醒如果插件在 UE5.3 上编译报错先检查是否是最新版本。如果使用 C 原生 Json 模块UE5.0 以后FJsonObjectConverter的命名空间和头文件路径基本一致但个别重载函数签名在不同版本有调整使用时以引擎源码为准。蓝图项目中如果只使用插件节点不写 C那么只需要关心插件的蓝图书点是否有对应版本的重新编译版本。版本需要根据你的项目实际情况调整本文示例以一种常见环境为例重点演示配置思路和转换流程。3. StructJsonString 插件的核心功能拆解3.1 插件的两个核心节点StructJsonString 这一类插件核心思路是把 UStruct 转换为 JSON 字符串再把 JSON 字符串转换回 UStruct。它们通常暴露两个蓝图节点节点输入输出作用Struct To Json StringStruct 类型变量、是否格式化Json String把结构体序列化为 JSON 文本Json String To StructJson String、目标 Struct 类型Struct 类型变量、Bool 结果把 JSON 文本反序列化回结构体在实际蓝图调用时你需要先声明一个具体类型的 Struct 变量再连接到节点的输入引脚。节点内部会通过反射机制读取 Struct 的属性逐一生成 JSON 键值对。3.2 支持的数据类型一个合格的双向转换插件至少要覆盖 UE5 中常见的字段类型类型JSON 中的表示说明booltrue / false布尔值int32123整型float / double3.14浮点数注意精度FString / FName / FTextplayer_name字符串类型TArray[1,2,3]数组TMapFString, T{key:value}键值对Key 通常建议用字符串嵌套 Struct{inner:{...}}递归处理子结构体FVector / FRotator{x:1.0,y:2.0,z:3.0}引擎内置数学结构体枚举Level_1默认按名称序列化了解支持范围有一个实际意义如果在项目里定义了很复杂的嵌套 Struct你需要提前确认插件是否能正确递归处理避免到运行时才发现字段丢失。3.3 结构化输出与紧凑输出JSON 有两种常见输出格式紧凑格式没有多余空格和换行便于网络传输和存储格式化格式带换行和缩进便于阅读和调试大多数插件节点会提供一个类似 “Pretty Print” 或 “FormatJSON” 的布尔参数。即使节点不提供你也可以把紧凑 JSON 字符串丢到在线 JSON 格式化工具里手动整理只是没有节点内直接输出方便。在项目开发阶段我通常会打开格式化输出方便直接在日志里查看数据上线后如果对性能有要求改成紧凑输出减少包体体积和传输流量。3.4 底层原理简述虽然你用蓝图节点时看不到底层实现但了解原理对排查问题很有帮助。UE5 引擎本身提供了 Json 模块核心类包括FJsonObjectJSON 对象的内存表示FJsonValueJSON 值的基类FJsonSerializer负责内存对象与文本之间的序列化/反序列化FJsonObjectConverter负责 UStruct 与 FJsonObject 之间的转换StructJsonString 插件本质上就是在这些引擎类之上做了蓝图封装。它拿到一个 UStruct 对象后利用 UE 的反射机制遍历结构体中的 UPROPERTY() 字段把它写入 FJsonObject再由 FJsonSerializer 输出为字符串。反序列化方向则相反先用 FJsonSerializer 把字符串解析成 FJsonObject再通过 FJsonObjectConverter 填充到 Struct 的字段中。这就是为什么你必须在 Struct 字段上标记UPROPERTY()没有标记的属性不会进入反射系统插件也就无法序列化它。4. 完整实战案例玩家存档数据的双向转换下面用一个玩家存档的例子完整演示从 Struct 定义到蓝图调用、再到 C 原生化实现的全流程。4.1 第 1 步定义存档数据结构假设我们需要保存一份玩家存档包含以下字段玩家 ID字符串玩家昵称字符串等级整型金币浮点型背包物品列表字符串数组出生位置FVector是否新手布尔当前副本地图枚举这里我用一个枚举EMapType和两个 StructFItemData、FSaveData。蓝图方式中你可以直接在 Content Browser 里右键创建 Blueprint Struct打开后添加字段。字段类型对应如下字段名类型PlayerIdStringNickNameStringLevelIntegerGoldFloat / DoubleItemsString ArraySpawnLocationVectorIsNewbieBooleanCurrentMapEnumEMapType如果使用 C 创建可以定义在头文件中// 文件路径Source/YourProject/Public/Data/SaveData.h #pragma once #include CoreMinimal.h #include SaveData.generated.h UENUM() enum class EMapType : uint8 { Village, Forest, Castle, BossRoom }; USTRUCT(BlueprintType) struct FItemData { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite) FString ItemName; UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 Count 1; }; USTRUCT(BlueprintType) struct FSaveData { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite) FString PlayerId; UPROPERTY(EditAnywhere, BlueprintReadWrite) FString NickName; UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 Level 1; UPROPERTY(EditAnywhere, BlueprintReadWrite) float Gold 0.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite) TArrayFItemData Items; UPROPERTY(EditAnywhere, BlueprintReadWrite) FVector SpawnLocation FVector::ZeroVector; UPROPERTY(EditAnywhere, BlueprintReadWrite) bool IsNewbie true; UPROPERTY(EditAnywhere, BlueprintReadWrite) EMapType CurrentMap EMapType::Village; };这个例子特意加入了嵌套结构体FItemData和数组TArrayFItemData因为实际项目中数据结构的复杂度通常会超过单层。4.2 第 2 步蓝图节点实现 Struct 转 JSON在关卡蓝图或 PlayerController 蓝图中创建一个 FSaveData 类型的变量并把字段设置成实际数据新建变量 SaveData类型选择 SaveData。在 BeginPlay 中用 Set 节点给各个字段赋值。连线到插件提供的 Struct To Json String 节点。整个流程的节点连接顺序如下BeginPlay - Set PlayerId - Set NickName - Set Level - Set Gold - Set Items (Add Element) - Set SpawnLocation - Set IsNewbie - Set CurrentMap - Struct To Json String : Target Struct SaveData bFormatJson true Output JsonString - Print String JsonString这里有一个细节需要注意不同插件节点对输入引脚的处理方式不完全一样。有的节点要求你把 Struct 变量先做一个变量连线作为输入有的插件则通过 Exec 引脚和结构体引用来完成。如果看到节点没有你要的结构体类型引脚可以先在蓝图里把变量拖出来再连接到节点上编译器会提示你选择哪个 UStruct 类型。运行时Print String 输出的大致内容如下{ PlayerId: P1001, NickName: Alex, Level: 10, Gold: 1500.0, Items: [ { ItemName: Sword, Count: 1 }, { ItemName: Potion, Count: 5 } ], SpawnLocation: { x: 100.0, y: 200.0, z: 300.0 }, IsNewbie: false, CurrentMap: Forest }这个 JSON 就已经可以被写入存档文件或者通过 HTTP 请求发送给服务端了。4.3 第 3 步蓝图节点实现 JSON 转 Struct反方向的操作是从外部拿到一段 JSON 字符串还原成 FSaveData 变量。在蓝图中创建变量 JsonString粘贴下面一段 JSON。调用 Json String To Struct 节点。输入 JsonString 和目标类型 SaveData。节点会输出一个转换成功标志和一个转换后的 Struct 变量。从结果 Struct 中读取字段打印验证。如果使用的是上面生成的 JSON在蓝图中读取ResultSaveData.Items[0].ItemName应该得到Sword。这里需要提醒一点JSON 里的字段名必须与 Struct 的属性名保持一致。默认情况下插件会按属性名严格匹配如果 JSON 字段名不同转换会失败或字段保持默认值。有的插件支持给 UPROPERTY 加JsonName元数据来映射字段名具体需要参照插件文档。4.4 第 4 步C 原生实现方式如果你不使用第三方插件UE5 引擎自带能力也完全可以实现同样功能。下面给出一个通用工具方法放在项目中的任意类中即可// 文件路径Source/YourProject/Public/JsonUtilLibrary.h #pragma once #include CoreMinimal.h #include Kismet/BlueprintFunctionLibrary.h #include JsonUtilLibrary.generated.h UCLASS() class YOURPROJECT_API UJsonUtilLibrary : public UBlueprintFunctionLibrary { GENERATED_BODY() public: UFUNCTION(BlueprintCallable, Category JsonUtil) static FString StructToJsonString(const FSaveData InData); UFUNCTION(BlueprintCallable, Category JsonUtil) static bool JsonStringToStruct(const FString InJsonString, FSaveData OutData); };// 文件路径Source/YourProject/Private/JsonUtilLibrary.cpp #include JsonUtilLibrary.h #include JsonObjectConverter.h #include Dom/JsonObject.h #include Serialization/JsonSerializer.h FString UJsonUtilLibrary::StructToJsonString(const FSaveData InData) { FString ResultJson; // UStructToJsonObjectString 是引擎封装的便捷方法底层走 FJsonObjectConverter if (FJsonObjectConverter::UStructToJsonObjectStringFSaveData(InData, ResultJson)) { return ResultJson; } return TEXT({}); } bool UJsonUtilLibrary::JsonStringToStruct(const FString InJsonString, FSaveData OutData) { if (InJsonString.IsEmpty()) { return false; } TSharedRefTJsonReader Reader TJsonReaderFactory::Create(InJsonString); TSharedPtrFJsonObject JsonObject MakeSharedFJsonObject(); if (FJsonSerializer::Deserialize(Reader, JsonObject) JsonObject.IsValid()) { if (FJsonObjectConverter::JsonObjectToUStructFSaveData(JsonObject.ToSharedRef(), OutData)) { return true; } } return false; }注意示例中的UStructToJsonObjectStringFSaveData模板是插件和引擎版本中常见的简化形式具体函数签名请以你当前引擎版本的头文件为准。如果版本较旧可以使用更通用的非模板版本FJsonObjectConverter::UStructToJsonObject(FSaveData::StaticStruct(), InData, JsonObject, 0, 0);这个代码的核心价值在于它展示了协议背后的原理即使你在实际项目中继续使用 StructJsonString 插件遇到问题也知道问题可能出在哪个环节。4.5 运行验证在蓝图中运行项目后应该依次看到Struct 转出的格式化 JSON。把 JSON 手动修改某个字段比如 Level 改成 99后再转回 Struct。读取 Struct 的 Level 字段输出 99。通过这个验证流程可以确认插件在你项目中的双向转换链路是通的。完整链路推荐用下面顺序检查数据结构是否定义正确。插件节点输入输出是否连接正确。JSON 字符串是否格式合法。JSON 字段名和 Struct 属性名是否一致。读取结果是否符合预期。5. 常见问题与排查思路5.1 表格速查问题现象常见原因解决思路Struct 转 JSON 后某些字段丢失字段没有添加 UPROPERTY()给字段添加 UPROPERTY(EditAnywhere, BlueprintReadWrite)JSON 转 Struct 后字段全为默认值JSON 字段名与 Struct 属性名不一致对比字段名大小写检查是否有拼写差异转换结果为空白字符串插件节点没有正确识别 Struct 类型重建变量连接确保传入的是对应类型 Struct嵌套 Struct 无法解析插件版本不支持深层次嵌套查看插件文档或手动把嵌套结构拆成扁平字段枚举转换失败JSON 中枚举以数字表示而非名称在 JSON 中使用枚举字符串如 Forest中文乱码文件写入或 HTTP 传输时编码不一致统一使用 UTF-8 编码不要使用本地 ANSI 编码FVector 精度丢失默认序列化精度不足使用 FVector 自带 JSON 序列化或手动格式化到指定小数位数组为空时被序列化为 null插件把空数组错误处理确认插件对 TArray 空数组的兼容性必要时给字段设置默认空数组输出 JSON 格式不易读使用了紧凑格式打开 PrettyPrint / FormatJSON 参数或用在线工具格式化插件编译失败插件版本与引擎版本不兼容更新插件版本或从源码重新编译5.2 重点排查场景详解第一个高频场景是“字段丢失”。如果你在 C 中定义了一个结构体但没有给某个属性添加UPROPERTY()序列化时插件会完全忽略这个字段因为反射系统里根本没有它的信息。反序列化时这个字段也会保持构造时的默认值不会报任何错误。这种问题最难察觉因为它不报错只是数据静默异常。排查方法是在输出 JSON 前先打印一份完整字段列表与 Struct 定义对比确认所有字段都在。第二个高频场景是“嵌套类型转换失败”。TArray 和嵌套 Struct 对插件的底层处理逻辑要求较高。如果你发现单个 Struct 转换正常加上 TArray 就失败可以先测试一个只包含 TArray 的最小结构体。这样能快速定位是数组支持的问题还是嵌套对象的问题。第三个场景是“中文乱码”。JSON 本身是 Unicode 文本但如果文件读写没有指定 UTF-8或者 HTTP 请求中 Content-Type 编码不是 UTF-8中文就会出现乱码。UE5 的 FString 内部是 UTF-16输出 UTF-8 时一般没问题关键是要读写文件时使用正确的编码标志。5.3 避免问题再次出现建立数据结构规范所有需要序列化的字段统一加UPROPERTY()。在开发期打开格式化输出方便肉眼检查。每次修改 Struct 结构时重新生成一组测试 JSON做正向反向校验。用自动化测试或断言验证关键字段而不是只看人工打印。将 JSON 样例保存为项目文档方便前后端同事对齐字段。6. 最佳实践与工程建议6.1 结构体设计规范所有参与序列化的字段必须标记UPROPERTY()否则字段静默丢失。字段命名遵循 UE5 规范C 中通常使用PascalCase例如PlayerId。如果你希望 JSON 中使用player_id这样的命名查看插件是否支持JsonName元数据。尽量使用UStruct而不是普通 C 结构体这样才能被蓝图使用并接入反射系统。嵌套层数控制在 2 到 3 层以内。层数过深会让 JSON 可读性和维护性变差。避免在 Struct 中直接使用TSharedPtr、UObject*等非 JSON 友好类型。除非你想自定义序列化逻辑否则它们很容易在转换时报错。6.2 容错与默认值JSON 反序列化时外部数据不可信。一定要给 Struct 字段设置安全默认值UPROPERTY() int32 Level 1; UPROPERTY() TArrayFItemData Items;如果 JSON 中没有某个字段反序列化会保留默认值而不会让程序崩溃。在实际项目中我还会在转换前对 JSON 字符串做一次基本校验if (!JsonString.StartsWith(TEXT({)) !JsonString.StartsWith(TEXT([))) { return false; }这种提前校验能在源头拦截大量无效输入避免每次进入完整的 JSON 解析流程。6.3 性能优化JSON 序列化和反序列化是 CPU 密集型操作不要在游戏主线程的 Tick 中频繁执行。推荐做法存档只在保存和读取时执行不放在每帧逻辑中。网络通信使用异步后台线程解析 JSON或者把 JSON 处理放在 receive 回调之后。配置表程序启动时一次性加载并缓存结果运行期间直接读取内存中的 Struct。日志调试阶段可以打印完整 JSON正式版关闭或只输出摘要。如果需要在存量项目中频繁转换大量对象还可以考虑把 Struct 缓存成二进制格式减少序列化开销只在跨平台通信时使用 JSON。6.4 安全边界JSON 数据可能来自服务端、玩家抓包修改、本地文件篡改。反序列化时不要盲目信任对数值型字段做范围校验比如Level不能为负数Gold不能超过上限。对数组长度做上限限制防止超长数组导致刷屏或内存异常。对字符串字段做长度限制防止脏数据进入日志或 UI。服务端下发数据时客户端只接收必要字段不要把所有字段原样写回存档。如果用于存档本地 JSON 文件建议配合校验码或签名防止玩家修改。生产环境的变更和存档校验逻辑需要根据项目实际情况设计务必保证数据合法、可回滚、可备份。6.5 日志与可观测性在开发期我会封装一个通用日志接口把 Struct 转 JSON 后的字符串统一打印方便排查。格式类似SaveData Serialized: {PlayerId:P1001,Level:10}反序列化失败时不要只打印“失败”要把输入的 JSON 字符串也打印出来方便定位是字段名不匹配还是 JSON 语法错误。6.6 数据契约管理如果 UE5 客户端需要和服务端联调建议在项目文档中维护一份 JSON 数据契约明确字段名、类型、允许范围、是否必填。前后端同时开发时用契约驱动开发能减少大量沟通成本。即使是纯客户端项目把 Struct 字段命名为PlayerId而策划导出的配置表里叫player_id也会导致转换失败。这种问题最好在定义阶段通过统一规范避免。7. 总结与下一步学习路线StructJsonString 这类插件真正解决的是 UE5 中“结构体”与“外部世界文本格式”之间的最后一公里问题。通过本文的演示你已经掌握了Struct 与 JSON 双向转换的适用场景和核心原理。如何在蓝图中用两个核心节点完成序列化和反序列化。C 中使用引擎原生 Json 模块实现同样功能的方法。常见转换错误和排查思路。结构体设计、容错处理、性能优化、安全边界的最佳实践。下一步建议你动手做一个 20 分钟左右的小实验定义一个包含数组和枚举的 Struct在蓝图里正向转一次、反向转一次验证数据一致性。然后在此基础上尝试把 JSON 字符串写入本地文件再读回来模拟一个完整存档流程。如果你准备继续深入可以按以下方向扩展学习 UE5 的反射系统理解UPROPERTY到底如何驱动序列化。研究FJsonObjectConverter的完整源码了解引擎如何处理复杂类型。结合 HTTP 请求把 Struct 序列化后发送到服务器并把响应解析为 Struct。结合 WebSocket实现基于 JSON 的实时消息推送和状态同步。了解项目中的自动同步机制思考 JSON 数据与属性复制之间的异同。把本文的示例改成你自己的项目结构跑通一次完整链路之后后续再遇到 JSON 转换的问题你就能比较从容地定位是插件能力、数据类型、字段命名还是编码问题导致的了。现在就可以打开编辑器先建一个测试 Struct 试试。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价