ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Unity 2022 Newtonsoft.Json安装与实战指南:序列化、IL2CPP避坑

Unity 2022 Newtonsoft.Json安装与实战指南:序列化、IL2CPP避坑 1. 为什么Unity项目离不开Newtonsoft.Json相信在Unity 2022里折腾过数据解析的同学多多少少都碰到过这个场景明明项目里已经用上了JsonUtility结果一遇到Dictionary序列化、多态继承、或者字段名跟服务器对不上的情况就瞬间卡壳。这时候Newtonsoft.Json就会成为你绕不开的选择也就是大家口里常说的JSON.NET。它不是Unity官方的内置方案但凭借极其灵活的API和强大的类型处理能力几乎成了Unity客户端开发里的事实标准。先说个最直观的感受Unity自带的JsonUtility只能处理基础对象和数组遇到Dictionarystring, object这种动态结构直接躺平更别提支持JsonProperty特性、自定义JsonConverter、忽略空值、格式化输出这些JSON.NET里随手可用的功能。在实际项目中服务器下发的数据往往是动态的字段名可能用下划线风格类型可能是多态基类这些用JsonUtility写起来非常痛苦而Newtonsoft.Json一行配置就能搞定。这篇文章不是给你读API文档的而是针对Unity 2022这个特定版本把Newtonsoft.Json的安装方式、版本坑、代码裁剪、程序集引用、常见报错一条龙梳理清楚。内容里有我实际踩坑的记录也有可以直接抄作业的代码示例。不管你是刚入门的Unity开发还是已经在做商业化项目的客户端老手相信都能在里面找到有用的东西。2. Unity 2022下的三种安装方式全解析2.1 官方UPM包最省心的路径Unity官方其实已经提供了一个包com.unity.nuget.newtonsoft-json这个包就是把Newtonsoft.Json的源码和打包逻辑放进了Unity的包管理体系中默认支持Unity 2018.4之后的版本Unity 2022自然是完全兼容的。安装步骤很简单。打开Window - Package Manager点左上角的加号选择“Add package by name”然后输入包名com.unity.nuget.newtonsoft-json再填版本号。如果不填版本号Unity 2022默认拿到的通常就是12.0.1版本也就是官方推荐搭配的分支。等待包加载完代码里直接using Newtonsoft.Json;就能用了。这里有个小细节值得注意com.unity.nuget.newtonsoft-json这个包的本质是从NuGet拉取Newtonsoft.Json 12.0.1的源码进行编译而不是直接内置dll。这意味着它的行为跟NuGet上的12.0.1版本基本一致但它被打包成了一个Unity可识别的包天然解决了程序集引用、平台兼容、dll被意外裁剪的问题。所以如果你没有特殊需求这条路是首选。还有一种方式是直接在manifest.json文件里加依赖。找到项目根目录的Packages/manifest.json在dependencies节点里加上一条{ dependencies: { com.unity.nuget.newtonsoft-json: 3.2.1 } }注意这里的版本号是Unity包本身的版本号不是Newtonsoft.Json的12.0.1。com.unity.nuget.newtonsoft-json的3.x版本对应的Newtonsoft.Json 12.0.12.x时代稍微乱一点有的对应Newtonsoft 12.0.1有的对应10.x尽量选最新就好。保存文件后切回Unity窗口让它自动刷新包就会被加载进来。2.2 从NuGet手动导入dll适合对版本有严格需求的场景官方包版本固定在12.0.1有些老项目依赖的是Newtonsoft.Json 13.x的特性比如对某些API的改进、对DateParseHandling的调整、或者特定行为差异这时候直接引用NuGet上的独立dll反倒更灵活。操作流程是打开NuGet官网或者用Visual Studio的NuGet包管理器找到Newtonsoft.Json选择需要的版本比如13.0.3点击下载。下载下来的是一个.nupkg文件本质上是zip包直接用解压工具解压在里面找到lib/netstandard2.0/Newtonsoft.Json.dll把这个dll复制到Unity项目的Assets/Plugins目录下或者放到你自己的Plugins子目录里。但是手动导dll有个常见的坑如果你想用它兼容netstandard2.0版本unity的API Compatibility Level需要设置成.NET Standard 2.1或更高。在Player Settings里找到Api Compatibility Level选.NET Standard 2.1。如果你平时用的是.NET Framework那个dll对应的是lib/net40或lib/net45下的版本需要按实际选择正确的目录结构。手动导入还有一个隐藏问题Unity 2022对插件目录下的dll会根据平台删选比如你在Assets/Plugins下放了这个dll默认会在所有平台生效。如果碰到包冲突比如另一个插件也内嵌了Newtonsoft.Json很可能编译时报“类型存在于两个程序集中”的错误这种情况后面会专门讲解决办法。2.3 通过OpenUPM命令行安装适合自动化环境如果你的团队已经把项目接入了OpenUPM或者用CI打包那么命令行安装更合适。openupm add com.unity.nuget.newtonsoft-json这条命令会自动修改manifest.json并添加对应版本。其实OpenUPM执行的操作跟手动在Package Manager里加包是一样的只是走的是命令行脚本方便跑批处理。个人看法是能不手动下dll就不手动下优先用官方包。为什么因为Unity包管理器会把程序集跟asmdef绑定在一起方便代码裁剪、依赖管理而且升级回滚都干净。手动导入dll一旦混入多个副本排查成本很高。但是确实有些老项目因为历史原因直接改不了manifest那至少手动导入的时候把多余的dll统一删干净不要出现一模一样的同名程序集。3. Unity 2022专属的版本坑与兼容性细节3.1 12.0.1与13.x到底有什么区别很多人一看到NuGet上最新是13.x就以为Unity官方包12.0.1过时了。其实不是这么回事。Newtonsoft.Json维护团队推出12.0.1的时候专门为Unity做了适配。到了13.x虽然API大体一致但底层行为上做了不少调整比如对NullableT的序列化形式、DateTime的解析策略、以及某些边界输入的处理方式。Unity官方包之所以停在12.0.1是经过兼容性测试的直接拿来用最稳。从功能开发角度看绝大多数Unity项目用12.0.1已经绰绰有余。除非你的代码里明确依赖13.x新增的API比如JsonSerializerSettings里新增的几个属性否则没必要冒险升级到13.x。如果一定想用13.x那只能走手动导入dll的方案而且做好应对兼容性问题的准备。3.2 IL2CPP与代码裁剪RTTI缺失导致的坑Unity 2022打包时普遍推荐使用IL2CPP。IL2CPP会把C#编译成C再编译成机器码好处是性能好、防破解性强但坏处是它对反射的支持比Mono弱很多。Newtonsoft.Json大量依赖反射来解析类型信息所以在IL2CPP下容易出问题。典型现象是编辑器里运行一切正常打包出来之后反序列化某些特定对象时返回全空、或者直接抛SerializationException。原因就是Unity的代码裁剪把某些只被反射引用的类型标记成“未使用”然后在IL2CPP阶段删掉了。解决办法一般有两个第一个是在link.xml文件里手动保留相关类型。比如你反序列化的是PlayerData这个类就在Assets/link.xml里加一句linker assembly fullnameAssembly-CSharp preserveall / /linker但说句实在话preserveall太粗暴会增大包体和内存。更精准的做法是按类型保留linker assembly fullnameAssembly-CSharp type fullnameGame.Model.PlayerData preserveall / /assembly /linker第二种是给需要序列化的类型添加[Preserve]特性。很多Unity插件都自带这个特性但如果你项目里没有定义可以先手动加一个简单的PreserveAttribute类。这个方法在编辑器下不会起作用但IL2CPP构建时能看到这个特性不会把类型裁掉。我个人在项目里是把需要通过网络传输的模型类统一放在一个程序集里然后在link.xml里对这个程序集的preserve设为all。因为这块数据量本身不大裁掉几个类的收益远小于打包后出Bug的代价。3.3 程序集定义asmdef的引用问题Unity 2022默认项目有一个Assembly-CSharp程序集如果你项目用了asmdef比如把代码按功能模块拆分那么在asmdef里使用Newtonsoft.Json之前必须在对应的asmdef文件里加上对Unity.Nuget.Newtonsoft.Json程序集的引用。这个操作卡住过不少人。因为Unity.Nuget.Newtonsoft.Json是包管理器为dll定义的程序集名称它在Inspector的asmdef引用列表里叫Unity.Nuget.Newtonsoft.Json而不是Newtonsoft.Json。如果你不加上这个引用代码里就算写了using Newtonsoft.Json编译依然会报“找不到类型或命名空间”。操作步骤就是双击你的asmdef文件在“Assembly Definition References”一栏点加号搜索Unity.Nuget.Newtonsoft.Json选中确认。如果确实找不到这个引用多半是因为官方包没装成功回Package Manager检查一下包状态。3.4 .NET Framework与.NET Standard 2.1下的行为差异Unity 2022的Api Compatibility Level默认是.NET Standard 2.1这个设置影响的不只是可用的API还会改变Newtonsoft.Json的某些默认解析行为。比如在.NET Framework下DateTime解析会自动支持很多区域性格式但在.NET Standard下部分DateTime格式解析会严格一些。要保证不同设置下行为一致建议统一用ISO 8601格式字符串即yyyy-MM-ddTHH:mm:ss。如果项目里同时用了UnityWebRequest做网络请求返回text之后直接JsonConvert.DeserializeObjectT那要特别注意编码问题。服务器返回的JSON如果带BOM头偶尔会导致解析报错。稳妥的做法是先对字符串做一次Trim()或者用StreamReader的时候指定Encoding.UTF8。4. 上手实操序列化、反序列化与JsonSerializerSettings实操4.1 最基础用法安装和引用都搞定之后最常用的就是JsonConvert.SerializeObject和JsonConvert.DeserializeObjectT这两个方法。using Newtonsoft.Json; using UnityEngine; public class PlayerData { public string PlayerName; public int Level; public float HP; public bool IsOnline; } public class JsonTest : MonoBehaviour { void Start() { PlayerData data new PlayerData { PlayerName Monster, Level 32, HP 999.5f, IsOnline true }; string json JsonConvert.SerializeObject(data); Debug.Log(json); PlayerData restored JsonConvert.DeserializeObjectPlayerData(json); Debug.Log(restored.PlayerName); } }这段代码输出的json会是{PlayerName:Monster,Level:32,HP:999.5,IsOnline:true}。可以看到字段名是直接按C#字段名输出的公有字段和公有属性都能被序列化私有成员默认不处理除非加特性。4.2 JsonSerializerSettings几乎每次都要用到实际项目中直接调用不带设置的SerializeObject其实很少见。因为服务器返回的JSON经常带有null、默认值、或者循环引用不加设置处理起来会非常痛苦。看一个相对完整的示例using Newtonsoft.Json; using Newtonsoft.Json.Converters; string json JsonConvert.SerializeObject(data, new JsonSerializerSettings { NullValueHandling NullValueHandling.Ignore, DefaultValueHandling DefaultValueHandling.Ignore, Formatting Formatting.Indented, Converters new ListJsonConverter { new StringEnumConverter() } });这里有几个值得细说的配置NullValueHandling.Ignore序列化时忽略值为null的字段。很多人喜欢传一个全量对象给服务器但部分字段没赋值null不传能省流量同时避免服务器端部分逻辑因为null字段报错。DefaultValueHandling.Ignore值等于默认值的字段int为0、float为0f、bool为false不序列化。这个很实用比如客户端往服务器同步增量字段0值不传。Formatting.Indented输出格式化后的JSON调试的时候非常有用把字符串打印出来能直接观察字段对应关系。StringEnumConverter让枚举在序列化时保留为字符串而不是数字。如果不加这个枚举会被直接转成int人眼没法直接读服务器那边也容易因为枚举顺序调整导致数值对不上。反序列化也一样可以设置MissingMemberHandling默认是忽略JSON里出现了但C#类型里没有的字段。如果想严格检查字段匹配把它改成Error但实际项目里很少这么设因为服务器经常多给字段。另外NullValueHandling在反序列化时也会影响成员初始化的行为字段缺失时保持C#的默认值字段为null并把NullValueHandling设为Ignore那么该字段也会保持默认值而不是被塞进null。4.3 字典、多态和自定义转换器JsonUtility最让人难受的地方之一是不能直接序列化Dictionarystring, object。这个场景在Unity项目里太常见了比如记录道具ID到数量的映射或者埋点数据里带动态属性。Dictionarystring, int itemCounts new Dictionarystring, int(); itemCounts[sword] 3; itemCounts[shield] 1; string json JsonConvert.SerializeObject(itemCounts);输出结果是{sword:3,shield:1}。注意Dictionary的key会被序列化成JSON对象的字段名所以key必须是可以作为字段名的字符串如果是枚举或者复杂对象需要有对应的JsonConverter来辅助。多态继承的场景更值得留意。假设有一个BaseMessage基类和几个子类直接DeserializeObjectBaseMessage会把子类里多出来的字段全丢掉。要解决这个问题一般借助JsonSerializerSettings里的TypeNameHandlingJsonSerializerSettings settings new JsonSerializerSettings { TypeNameHandling TypeNameHandling.Auto };TypeNameHandling.Auto会在序列化时自动为多态对象添加$type字段反序列化时据此还原为实际类型。但这类设置有个安全风险如果你直接从不可信来源获取JSON恶意构造的$type可能导致未知类型被实例化。在Unity客户端里如果反序列化的是自家服务器的数据问题不大如果是玩家自制的mod或配置数据建议关掉TypeNameHandling改用手动过滤字段或者自定义Converter。自定义JsonConverter是Newtonsoft.Json能力最强的部分。比如你有个Vector3类型默认序列化成{x:0,y:0,z:0}嫌它太啰嗦想压成0,0,0字符串可以写一个转换器public class Vector3Converter : JsonConverterVector3 { public override void WriteJson(JsonWriter writer, Vector3 value, JsonSerializer serializer) { writer.WriteValue(${value.x},{value.y},{value.z}); } public override Vector3 ReadJson(JsonReader reader, Type objectType, Vector3 existingValue, bool hasExistingValue, JsonSerializer serializer) { string val reader.Value.ToString(); string[] parts val.Split(,); return new Vector3( float.Parse(parts[0]), float.Parse(parts[1]), float.Parse(parts[2]) ); } }然后在JsonSerializerSettings的Converters里加上new Vector3Converter()即可。这种做法在网络传输时能显著减小数据体积日志里看起来也干净很多。5. 高频报错与排查实录这一part把我自己在Unity 2022项目里碰到过的报错和网上的高频问题整理成速查表对号入座能省不少排查时间。报错表现可能原因解决办法The type or namespace name Newtonsoft could not be found包没装好或asmdef引用缺失确认com.unity.nuget.newtonsoft-json已安装检查asmdef引用加上了Unity.Nuget.Newtonsoft.JsonThe type Newtonsoft.Json.JsonConvert exists in both Newtonsoft.Json.dll and Unity.Nuget.Newtonsoft.Json.dll项目里存在多个Newtonsoft.Json dll副本手动删除Plugins下多余的Newtonsoft.Json.dll保留一个来源编辑器里正常打包后反序列化结果全NULLIL2CPP裁剪掉了反射使用的类型写link.xml保留对应类型或者添加Preserve特性SerializationException: Type X is not marked as serializable某些平台限制或类型缺少特性给类加上[Serializable]或者检查是否绕过了IL2CPP裁剪Unexpected character encountered while parsing valueJSON字符串里混入了BOM头或多余字符json.Trim(), 或者确保字符串从UTF-8读取循环引用序列化报错对象互相引用导致递归无限设置ReferenceLoopHandling.Ignore/ReferenceLoopHandling.Serialize并配合PreserveReferencesHandlingJsonProperty特性不生效你的类可能用了匿名类型检查类型定义匿名类型不支持JsonProperty换成具名类再单独说一下循环引用的问题。比如你有个Player类里面有个Weapon对象Weapon又反过来引用了Player直接序列化时Newtonsoft会检测到循环引用并抛异常。最简单的方式是配置ReferenceLoopHandling.Ignore遇到循环引用时就不序列化那个关联字段。但如果业务逻辑确实需要把关联关系完整传递可以加ReferenceLoopHandling.Serialize配合PreserveReferencesHandling.Objects这样会把引用关系写成$ref结构反序列化时能还原引用关系。不过后者生成的JSON可读性很差一般不太推荐。还有一个小细节需要提醒Unity的序列化系统要求字段是public或者标了[SerializeField]。Newtonsoft.Json自己有一套规则默认序列化public字段和public属性。如果你的类里有一个public属性序列化它的时候会自动调用getter这时getter内部如果有副作用会带来莫名其妙的Bug。一个真实例子是曾经一个同事在一个public属性里做了动态字符串拼接导致序列化后日志里字符串被更新了。排查了半天最后就是把这个属性改成[JsonIgnore]单独序列化一个私有字段。6. 最后分享几个安装与使用的实用技巧装完Newtonsoft.Json之后建议先写一个简单的自检用例确认包加载、程序集引用都到位构建一个最简单的数据类序列化一次、反序列化一次并打印结果。这个操作看起来傻但在编辑器里跑一遍不到一分钟能帮你排除掉最基础的配置问题。如果你用的Unity版本是2022.1之前的万一官方包com.unity.nuget.newtonsoft-json在Package Manager里搜不到检查一下Project Settings - Package Manager里的注册表设置。有时候公司内部搞私有包源把官方源冲掉了把scopedRegistries配置正确加回去就能解决。打包到Android真机之前至少用一次Build App Bundle或者IL2CPP构建跑通一版。很多人在编辑器里测试一切正常一上真机就崩崩的位置就在反序列化。真机上的崩溃日志如果指向Newtonsoft.Json内部先查link.xml不用怀疑其他。最后一件事如果项目里还有其他插件也带了Newtonsoft.Json比如某些SDK、某些广告聚合平台它们可能在Assets/Plugins下放了另一个版本的Newtonsoft.Json.dll。这种情况下我的处理方式是优先用Unity官方包然后找到所有重复dll除了保留官方包其余全部删除。删之前注意备份确认第三方SDK不会因为缺少dll而异常。如果第三方SDK跟官方包冲突太深那只能手动导入与SDK一致版本的dll但需要拿掉官方包二选一。我在实际项目里长期用官方包方案编辑器写自动化测试、Android真机打包、iOS提交审核都稳定运行。希望这篇内容能让你在Unity 2022跟Newtonsoft.Json打交道的路上少踩几个坑。
返回列表