1. 项目概述:为什么Unity开发者绕不开Newtonsoft.Json
如果你在Unity项目里处理过稍微复杂一点的JSON数据,比如从服务器拉取的玩家背包信息、复杂的配置表,或者需要保存一个包含嵌套列表和自定义类的游戏存档,那你大概率已经和Newtonsoft.Json(现在也叫Json.NET)打过交道,甚至可能已经“踩过坑”了。这个在.NET生态中几乎成为事实标准的JSON库,以其强大的功能和灵活性著称,但它在Unity这个特殊环境下的表现,却常常让开发者感到“又爱又恨”。
简单来说,这个库能让你用一两行代码就把一个C#对象变成JSON字符串(序列化),或者把JSON字符串变回对象(反序列化)。听起来很美,对吧?但在Unity里,事情没那么简单。Unity的运行时环境、脚本编译后端(Mono vs IL2CPP)、平台差异(尤其是WebGL和移动端),以及版本迭代,都给这个“外来”库带来了独特的挑战。你可能遇到过:在编辑器里运行得好好的,一打包到手机或WebGL上就报错;序列化一个Vector3结果出来一堆奇怪的字段;或者只是想忽略循环引用,却引发了性能问题。
网上能找到的教程,大多是基于标准.NET环境,直接套用到Unity里,十有八九会出问题。这就是为什么我们需要一份专门针对Unity的“避坑指南”。这份指南不打算重复那些基础的API用法,而是聚焦于那些只有在你真正用Newtonsoft.Json-for-Unity(这是官方为Unity适配的分支)进行项目开发时才会遇到的“深水区”问题。我会结合自己多个项目的实战经验,从环境配置、核心配置、性能优化到平台适配,一步步带你安全趟过所有雷区。
2. 环境准备与包导入的正确姿势
在Unity中使用Newtonsoft.Json,第一步就不是简单的“点击安装”。错误的方式会导致后续无尽的麻烦。
2.1 选择正确的包:Newtonsoft.Json-for-Unity
绝对不要直接从NuGet下载标准的Newtonsoft.JsonDLL扔进Plugins文件夹,也谨慎使用Asset Store上那些来源不明的版本。正确且唯一推荐的方式是使用官方为Unity维护的版本:Newtonsoft.Json-for-Unity。
为什么?
- Unity版本兼容性:这个分支专门处理了Unity不同运行时版本(如.NET Standard 2.0, .NET 4.x)下的兼容性问题。
- AOT编译支持:对于使用IL2CPP后端(尤其是为了发布到iOS、WebGL等平台)的项目,标准版可能因为反射和泛型的使用而导致AOT(预先编译)时出错。Unity分支做了针对性处理。
- 问题修复:它包含了针对Unity环境的一些特定Bug修复。
安装方法: 推荐通过Unity的Package Manager进行安装,这是最干净、最易管理的方式。
- 打开Package Manager (
Window->Package Manager)。 - 点击左上角的“+”按钮,选择“Add package from git URL...”。
- 输入官方仓库地址:
https://github.com/jilleJr/Newtonsoft.Json-for-Unity.git#upm - 点击“Add”。Unity会自动解析并导入这个包。
注意:有时直接使用Git URL可能会因为网络问题失败。备选方案是从GitHub Releases页面下载对应的
.unitypackage文件进行手动导入。但请始终从上述官方仓库获取,以确保版本正确。
2.2 命名空间与版本确认
导入成功后,你需要在代码文件中引用正确的命名空间:
using Newtonsoft.Json;为了确认一切正常,可以在脚本的Start方法里写一句简单的调试代码:
Debug.Log($"Newtonsoft.Json 版本: {typeof(JsonConvert).Assembly.GetName().Version}");在Unity编辑器的Console中,你应该能看到类似13.0.1或更高的版本号。如果出现错误,请检查包是否成功导入,以及是否有多个不同版本的Newtonsoft.Json DLL冲突(检查Assets文件夹和Plugins文件夹)。
2.3 处理潜在的DLL冲突(常见大坑)
这是Unity项目中最常见的问题之一。如果你的项目还引用了其他第三方插件或框架(例如某些网络库、SDK),它们可能自带了一个旧版本或标准版的Newtonsoft.Json.dll。这会导致“类型重复定义”的编译错误,或者运行时行为不可预测。
排查与解决:
- 搜索:在Project窗口,搜索
Newtonsoft.Json.dll。查看所有找到的文件及其路径。 - 评估:如果除了通过Package Manager导入的版本外,在其他插件(如
Assets/Plugins/SomeSDK/Newtonsoft.Json.dll)里发现了该DLL,冲突就发生了。 - 解决:
- 最佳方案:联系插件作者,询问其是否依赖特定版本,或是否有不包含Newtonsoft.Json的“轻量版”插件。许多现代插件会声明对
Newtonsoft.Json的依赖,而让Unity的包管理器去解决版本问题。 - 妥协方案:如果插件自带的DLL版本较旧,且插件代码没有用到Newtonsoft.Json的高级特性,你可以尝试删除插件目录下的那个DLL。但这有风险,可能会使插件功能失效。务必先备份,并在删除后进行充分测试。
- 强绑定方案:对于无法避免的冲突,可以考虑使用Assembly Definition File (asmdef)和Assembly Version Override。为你自己的核心代码(需要使用Newtonsoft.Json的部分)创建一个程序集定义,并为其显式指定引用Newtonsoft.Json-for-Unity包。这需要更深入的设置,但可以隔离依赖。
- 最佳方案:联系插件作者,询问其是否依赖特定版本,或是否有不包含Newtonsoft.Json的“轻量版”插件。许多现代插件会声明对
3. 核心配置:JsonSerializerSettings详解与避坑
JsonSerializerSettings是你与Newtonsoft.Json交互的核心控制器。大部分“坑”都源于对它的配置不当。下面我们针对Unity环境,逐一拆解关键配置项。
3.1 处理Unity特有类型(如Vector3, Quaternion, Color)
这是Unity开发者的第一个拦路虎。直接序列化一个包含Vector3的类,你会得到类似这样的JSON:
{ "position": { "x": 1.0, "y": 2.0, "z": 3.0, "magnitude": 3.7416573867739413, "normalized": { ... }, "sqrMagnitude": 14.0 } }这包含了大量我们通常不需要的派生属性(如magnitude),不仅使JSON体积膨胀,反序列化时还可能因为循环引用而出错。
解决方案:使用自定义转换器(JsonConverter)这是最灵活、最推荐的方式。你需要为Unity的常用值类型编写转换器。
using Newtonsoft.Json; using Newtonsoft.Json.Converters; using UnityEngine; public class Vector3Converter : JsonConverter<Vector3> { public override void WriteJson(JsonWriter writer, Vector3 value, JsonSerializer serializer) { // 序列化为简单的数组 [x, y, z] writer.WriteStartArray(); writer.WriteValue(value.x); writer.WriteValue(value.y); writer.WriteValue(value.z); writer.WriteEndArray(); } public override Vector3 ReadJson(JsonReader reader, System.Type objectType, Vector3 existingValue, bool hasExistingValue, JsonSerializer serializer) { // 从数组 [x, y, z] 反序列化 if (reader.TokenType == JsonToken.StartArray) { reader.Read(); // 读入 StartArray float x = (float)reader.ReadAsDouble().Value; float y = (float)reader.ReadAsDouble().Value; float z = (float)reader.ReadAsDouble().Value; reader.Read(); // 读入 EndArray return new Vector3(x, y, z); } // 也可以处理旧格式的对象 {x, y, z} else if (reader.TokenType == JsonToken.StartObject) { // 简单实现,使用默认反序列化到匿名对象再提取 var obj = new { x = 0f, y = 0f, z = 0f }; serializer.Populate(reader, obj); return new Vector3(obj.x, obj.y, obj.z); } return Vector3.zero; } } // 同理,可以编写 QuaternionConverter, ColorConverter 等使用方式:
var settings = new JsonSerializerSettings { Converters = new List<JsonConverter> { new Vector3Converter(), new ColorConverter() }, Formatting = Formatting.Indented }; string json = JsonConvert.SerializeObject(myObject, settings); MyClass obj = JsonConvert.DeserializeObject<MyClass>(json, settings);实操心得:为
Vector3,Quaternion,Color,Color32这几个最常用的类型编写转换器,并封装到一个静态的UnityJsonSettings类中,全局使用。这能一劳永逸地解决序列化问题,并极大优化网络传输和存储空间。
3.2 循环引用与引用循环处理
在Unity中,循环引用非常常见。例如,Monster类有一个Target属性指向另一个Monster,而对方也可能指向它。直接序列化会导致栈溢出。
配置项:ReferenceLoopHandling
var settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore // 或 Serialize };Ignore:遇到循环引用时,直接忽略该成员(输出null)。这是最安全、最常用的选项,避免无限递归。Serialize:会输出一个特殊的$id和$ref结构来表示引用关系。虽然信息完整,但会使得JSON结构复杂,且很多前端解析库不支持这种格式,不推荐在前后端通信中使用。
在Unity中的特殊考量:对于MonoBehaviour或ScriptableObject,它们内部由Unity引擎管理,包含大量引擎内部的引用和状态。绝对不要尝试直接序列化整个GameObject或MonoBehaviour实例。你永远应该序列化一个纯C#的、只包含数据的模型类(DTO),然后在需要时用这些数据去初始化或更新Unity组件。
3.3 空值处理与默认值配置
服务器返回的数据字段可能为null,或者你希望忽略某些默认值的字段以减少数据量。
关键配置:
NullValueHandling:控制如何处理null值。NullValueHandling = NullValueHandling.Ignore // 序列化时跳过值为null的字段DefaultValueHandling:控制如何处理默认值(如int的0,bool的false)。DefaultValueHandling = DefaultValueHandling.Ignore // 序列化时跳过等于默认值的字段ContractResolver:更细粒度的控制。例如,配合DefaultContractResolver的IgnoreSerializableAttribute可以忽略标记了[NonSerialized]的字段(这在Unity中与System.NonSerialized不同,需要注意)。
注意事项:
Ignore选项在序列化时节省空间,但在反序列化时,被忽略的字段将保持其初始值(对于引用类型是null,值类型是默认值)。如果你的业务逻辑依赖于区分“字段不存在”和“字段值为默认值”,就需要小心使用。通常,在网络通信中,为了节省流量,推荐同时设置NullValueHandling.Ignore和DefaultValueHandling.Ignore。
3.4 日期格式与自定义格式化
不同系统、不同语言对日期时间的格式处理千差万别。为了确保一致性,必须显式指定。
最佳实践:统一使用ISO 8601格式
var settings = new JsonSerializerSettings { DateFormatHandling = DateFormatHandling.IsoDateFormat, DateTimeZoneHandling = DateTimeZoneHandling.Utc // 明确使用UTC时间,避免时区混乱 };这样,DateTime会被序列化为类似"2023-10-27T12:00:00Z"的字符串。无论在服务器(可能是Java/Python/Go)还是各种客户端,这个格式都是被广泛支持和推荐的。
4. 性能优化实战:让序列化在移动端飞起来
在PC上,序列化/反序列化一点数据你可能感觉不到延迟。但在移动设备,尤其是低端安卓机或WebGL环境下,不当的使用会成为性能瓶颈,导致卡顿。
4.1 缓存你的JsonSerializerSettings
这是一个成本极低但收益巨大的优化。不要每次序列化都new JsonSerializerSettings()。
public static class JsonSettingsCache { private static JsonSerializerSettings _cachedSettings; public static JsonSerializerSettings Default { get { if (_cachedSettings == null) { _cachedSettings = new JsonSerializerSettings { Converters = new List<JsonConverter> { new Vector3Converter() }, ReferenceLoopHandling = ReferenceLoopHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, // ... 其他配置 }; } return _cachedSettings; } } } // 使用 string json = JsonConvert.SerializeObject(data, JsonSettingsCache.Default);4.2 使用流式API处理超大JSON
当你需要处理一个非常大的JSON文件(如整个游戏世界的配置),一次性读入内存进行反序列化可能导致内存峰值和卡顿。此时可以使用JsonTextReader进行流式读取。
using (StreamReader sr = new StreamReader(filePath)) using (JsonTextReader reader = new JsonTextReader(sr)) { JsonSerializer serializer = JsonSerializer.CreateDefault(JsonSettingsCache.Default); // 假设JSON根是一个数组 reader.Read(); // 读入 StartArray while (reader.Read() && reader.TokenType != JsonToken.EndArray) { if (reader.TokenType == JsonToken.StartObject) { // 每次只反序列化数组中的一个对象 MyDataObject item = serializer.Deserialize<MyDataObject>(reader); ProcessItem(item); // 处理这个对象,然后它可以被GC回收 } } }这种方式可以显著降低内存占用,特别适合在资源受限的移动设备上加载大型配置。
4.3 避免频繁的小对象序列化
不要在每帧的Update里对复杂对象进行完整的JSON序列化/反序列化。例如,不要用JSON来传递每帧的玩家位置更新。对于高频实时数据,应该使用更轻量的二进制格式或专门设计的网络消息包。
4.4 针对IL2CPP的AOT编译优化
当项目使用IL2CPP后端发布时,所有代码会被预先编译(AOT)。Newtonsoft.Json大量使用反射和泛型,这可能在AOT编译时因为无法推断出所有可能的类型而导致运行时错误。
解决方案:
- 使用
[JsonConverter(typeof(...))]属性:在自定义类上直接标注使用的转换器,这为AOT编译器提供了明确的类型信息。[JsonConverter(typeof(MyCustomClassConverter))] public class MyCustomClass { ... } - 在链接器(Linker)配置中保留类型:如果你的类型是通过泛型参数动态传入的,AOT可能将其剪裁掉。需要在
link.xml文件中保留这些类型。
编写<!-- Assets/link.xml --> <linker> <assembly fullname="MyGameAssembly" preserve="all"/> <!-- 谨慎使用,会增大包体 --> <!-- 更精确的方式 --> <assembly fullname="Newtonsoft.Json"> <type fullname="Newtonsoft.Json.Serialization.DefaultContractResolver" preserve="all"/> </assembly> <assembly fullname="MyGame.Data"> <type fullname="MyGame.Data.PlayerData" preserve="all"/> <type fullname="MyGame.Data.InventoryData" preserve="all"/> </assembly> </linker>link.xml需要一定的经验,通常是从运行时错误日志中分析出缺失的类型,再将其加入。
5. 平台特异性问题与疑难排查
不同的发布平台,问题也各不相同。
5.1 WebGL平台的“巨坑”与解决方案
WebGL是问题重灾区,主要因为其单线程和特殊的JavaScript桥接环境。
问题:初始化慢,首次调用卡顿。这对应了热词中的“unity webgl初始化很久”。Newtonsoft.Json在WebGL中首次进行序列化/反序列化时,需要大量的反射操作,这些操作在JavaScript环境中异常缓慢。
解决方案:预热(Pre-warm)。
IEnumerator PrewarmJsonSerializerInWebGL() { // 在游戏加载初期,在一个不卡顿的时机(如加载界面)进行预热 if (Application.platform == RuntimePlatform.WebGLPlayer) { // 序列化和反序列化一个简单的、包含你常用所有数据类型的“样本对象” var sampleData = CreateSampleData(); string dummyJson = JsonConvert.SerializeObject(sampleData, JsonSettingsCache.Default); var dummyObj = JsonConvert.DeserializeObject<SampleData>(dummyJson, JsonSettingsCache.Default); // 可以多执行几次,确保JIT(在WebGL中是解释执行)缓存生效 for (int i = 0; i < 3; i++) { JsonConvert.SerializeObject(sampleData, JsonSettingsCache.Default); yield return null; // 分帧进行,避免单帧卡死 } } Debug.Log("JSON Serializer预热完成。"); }通过预热,将最耗时的反射和代码生成操作提前完成,后续的业务操作就会流畅很多。
问题:线程安全。WebGL不支持多线程,而Newtonsoft.Json的某些全局设置(如
JsonConvert.DefaultSettings)如果在多处修改,可能引发竞态条件。在WebGL中,避免使用或修改全局默认设置,始终显式传递JsonSerializerSettings实例。
5.2 iOS/Android (IL2CPP) 的代码剪裁问题
如前所述,IL2CPP会进行积极的代码剪裁。除了使用link.xml,还可以:
- 在代码中显式引用:在某个一定会执行的代码路径(如游戏启动的
Awake方法中),创建一份你可能用到的所有泛型类型的列表。这相当于给链接器一个“提示”。void ForcePreserveTypesForAOT() { // 这些类型可能通过反射被Newtonsoft.Json使用,确保它们不被剪裁 var preserveList = new List<object> { new System.Collections.Generic.List<int>(), new System.Collections.Generic.Dictionary<string, object>(), new MyCustomDataClass(), // 添加所有你通过JsonConvert反序列化的数据类型 }; Debug.Log("AOT类型保留提示已执行。"); } - 使用
Preserve属性:在可能被剪裁的类或方法上添加[System.Runtime.CompilerServices.Preserve]特性。
5.3 常见错误与异常处理
JsonSerializationException: Self referencing loop detected...原因:对象存在循环引用,且未设置
ReferenceLoopHandling.Ignore。解决:检查设置,并确保没有尝试序列化Unity引擎对象(如GameObject、Component)。JsonSerializationException: Error getting value from ‘XXX’ on ‘YYY’.原因:通常发生在AOT/IL2CPP环境下,尝试反序列化到一个没有无参构造函数的类,或者类的属性
getter/setter访问器涉及复杂的逻辑导致反射失败。解决:- 确保所有需要反序列化的类都有
public的无参构造函数。 - 简化属性的
get/set访问器,避免在其中进行复杂的业务逻辑或访问未初始化的对象。 - 考虑为该类编写自定义的
JsonConverter,完全控制序列化和反序列化过程。
- 确保所有需要反序列化的类都有
内存泄漏(WebGL/移动端显著)原因:频繁创建大的JSON字符串或
JObject,且没有及时释放引用。解决:- 对于临时字符串,确保其作用域结束后能被GC回收。避免在长期存活的对象(如单例)中持有对大JSON字符串的引用。
- 使用
using语句确保JsonTextReader/JsonTextWriter被正确释放。 - 在性能关键处,考虑使用对象池来重用
StringBuilder(用于构建JSON字符串)或简单的数据模型对象。
6. 进阶技巧:自定义转换器与合约解析器实战
当你需要完全掌控序列化的过程时,自定义转换器(JsonConverter)和合约解析器(IContractResolver)是你的终极武器。
6.1 编写健壮的自定义转换器
之前我们写了Vector3Converter,这是一个针对具体类型的转换器。有时你需要处理更通用的场景。
场景:你有一个接口IWeapon,有多种实现类Sword,Bow。序列化一个List<IWeapon>时,需要保留具体的类型信息以便反序列化。
public class WeaponConverter : JsonConverter { public override bool CanConvert(Type objectType) { return typeof(IWeapon).IsAssignableFrom(objectType); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { // 写入类型鉴别器 var type = value.GetType(); writer.WriteStartObject(); writer.WritePropertyName("$type"); // 使用一个特殊字段存储类型 writer.WriteValue(type.FullName); // 注意:FullName在AOT下可能不稳定,可用自定义别名 writer.WritePropertyName("$data"); serializer.Serialize(writer, value); // 序列化对象本身 writer.WriteEndObject(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); string typeName = jo["$type"].Value<string>(); JToken dataToken = jo["$data"]; Type concreteType = Type.GetType(typeName); // 根据类型名获取类型 if (concreteType == null) { throw new JsonSerializationException($"无法找到类型: {typeName}"); } return serializer.Deserialize(dataToken.CreateReader(), concreteType); } }注意:使用类型全名(
FullName)在IL2CPP中可能因为代码剪裁而出问题。更稳健的做法是维护一个Dictionary<string, Type>的映射,使用简短的别名。
6.2 使用合约解析器实现动态规则
IContractResolver允许你在运行时动态决定如何序列化一个对象。例如,你想根据当前平台(移动端/PC)决定是否忽略某些体积大的字段。
public class PlatformAwareContractResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { JsonProperty property = base.CreateProperty(member, memberSerialization); // 如果是移动平台,且字段标记了 [MobileIgnore] 特性,则忽略 if (Application.isMobilePlatform && member.GetCustomAttribute<MobileIgnoreAttribute>() != null) { property.ShouldSerialize = instance => false; } // 可以在这里添加更多规则,例如重命名属性名等 // if (member.Name == "internalField") property.PropertyName = "publicField"; return property; } } // 自定义特性 [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class MobileIgnoreAttribute : Attribute { } // 使用 var settings = new JsonSerializerSettings { ContractResolver = new PlatformAwareContractResolver() };6.3 性能关键路径:避免反射
在极致性能要求下(例如每帧处理大量网络消息),反射可能成为瓶颈。你可以使用JsonSerializer预生成序列化/反序列化委托。
// 为特定类型创建并缓存序列化器 private static JsonSerializer _cachedSerializer; public static JsonSerializer GetOptimizedSerializer() { if (_cachedSerializer == null) { var settings = JsonSettingsCache.Default; // 关键:使用 DefaultContractResolver 并缓存合约 var contractResolver = new DefaultContractResolver(); // 预解析MyDataClass的合约 var contract = (JsonObjectContract)contractResolver.ResolveContract(typeof(MyDataClass)); _cachedSerializer = JsonSerializer.CreateDefault(settings); _cachedSerializer.ContractResolver = contractResolver; } return _cachedSerializer; } // 使用预生成的序列化器 void SerializeData(MyDataClass data, Stream stream) { using (var sw = new StreamWriter(stream)) using (var jw = new JsonTextWriter(sw)) { GetOptimizedSerializer().Serialize(jw, data); } }这种方式通过缓存合约信息,减少了每次序列化时的反射开销,在批量处理数据时效果显著。
7. 实战:构建一个Unity可用的全局JSON工具类
最后,我们把所有最佳实践封装成一个易于使用的工具类,作为项目的坚实基础。
using System; using System.Collections.Generic; using System.IO; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using UnityEngine; public static class UnityJsonToolkit { private static JsonSerializerSettings _settings; private static JsonSerializer _cachedSerializer; static UnityJsonToolkit() { InitializeSettings(); WarmUp(); } private static void InitializeSettings() { _settings = new JsonSerializerSettings { // 1. 转换器 Converters = new List<JsonConverter> { new Vector3Converter(), new QuaternionConverter(), new ColorConverter(), new StringEnumConverter() // 优雅地序列化枚举为字符串 }, // 2. 循环引用 ReferenceLoopHandling = ReferenceLoopHandling.Ignore, // 3. 空值与默认值 NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore, // 4. 日期格式 DateFormatHandling = DateFormatHandling.IsoDateFormat, DateTimeZoneHandling = DateTimeZoneHandling.Utc, // 5. 格式化(开发时美化,发布时压缩) Formatting = Debug.isDebugBuild ? Formatting.Indented : Formatting.None, // 6. 合约解析器(可根据平台扩展) ContractResolver = new CustomContractResolver() }; // 创建预缓存的序列化器(用于性能关键路径) _cachedSerializer = JsonSerializer.Create(_settings); } // WebGL预热 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void WarmUp() { if (Application.platform == RuntimePlatform.WebGLPlayer) { try { var dummy = new { test = 123, vec = Vector3.one }; var json = Serialize(dummy); var obj = Deserialize<dynamic>(json); Debug.Log("JSON Toolkit预热完成。"); } catch (Exception e) { Debug.LogWarning($"JSON Toolkit预热失败: {e.Message}"); } } } // 对外提供的简单API public static string Serialize(object obj) { return JsonConvert.SerializeObject(obj, _settings); } public static T Deserialize<T>(string json) { return JsonConvert.DeserializeObject<T>(json, _settings); } public static object Deserialize(string json, Type type) { return JsonConvert.DeserializeObject(json, type, _settings); } // 流式处理大文件 public static IEnumerable<T> DeserializeStream<T>(string filePath) where T : class { using (var sr = new StreamReader(filePath)) using (var reader = new JsonTextReader(sr)) { reader.SupportMultipleContent = true; // 支持读取多个JSON对象 while (reader.Read()) { if (reader.TokenType == JsonToken.StartObject) { yield return _cachedSerializer.Deserialize<T>(reader); } } } } // 高性能序列化到Stream public static void SerializeToStream(object obj, Stream stream) { using (var sw = new StreamWriter(stream, new System.Text.UTF8Encoding(false), 1024, true)) using (var jw = new JsonTextWriter(sw)) { _cachedSerializer.Serialize(jw, obj); } } } // 示例:自定义合约解析器(可根据项目需求扩展) public class CustomContractResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { JsonProperty property = base.CreateProperty(member, memberSerialization); // 示例:忽略标记了 [JsonIgnore] 的字段(Unity自己的[NonSerialized]用于Inspector,不用于JSON) if (member.GetCustomAttribute<JsonIgnoreAttribute>() != null) { property.ShouldSerialize = instance => false; property.Ignored = true; } // 示例:在移动端忽略标记了[MobileIgnore]的字段 if (Application.isMobilePlatform && member.GetCustomAttribute<MobileIgnoreAttribute>() != null) { property.ShouldSerialize = instance => false; } return property; } }这个工具类集成了缓存、平台感知、性能优化和安全配置。在项目中,你只需要调用UnityJsonToolkit.Serialize()和UnityJsonToolkit.Deserialize<T>()即可,绝大部分坑都已经提前被填平。记住,在Unity中处理JSON,选择正确的包、进行恰当的配置、理解平台差异并做好性能优化,远比单纯学会API调用重要得多。希望这份指南能让你在未来的项目中,面对JSON序列化时更加从容。