ARTICLE DETAIL

资讯详情

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

Unity2022安装与使用Newtonsoft.Json实战避坑:从Package Manager到APK导出

Unity2022安装与使用Newtonsoft.Json实战避坑:从Package Manager到APK导出 刚跨到Unity2022那会儿我写过一段处理后端接口返回的代码用JsonUtility.FromJson解析结果一运行直接给我扔回来一堆空对象。数组套对象还好说一旦遇到嵌套的Dictionary类型Unity自带的JsonUtility根本不管字段悄悄丢了连个报错都不给。翻了一圈社区发现所有人给的答案都指向同一个方向去装NewtonsoftJson。可问题来了在Unity2022里装这个库网上说法五花八门有说Package Manager直接搜的有说要去Asset Store下载的还有人说直接扔一个DLL进Plugins目录就行。我先后把这几条路都趟了一遍期间还踩了导出APK时编译报错、IL2CPP裁剪把库裁没、包版本号对不上等一堆坑。这篇就把Unity2022安装和使用NewtonsoftJson这件事完整说透覆盖几种安装方式的差别、序列化配置的注意点、以及导出APK前后最容易出问题的三个典型场景给正准备在Unity2022里做Json处理的人一份能直接照着走的参考。1. 为什么Unity2022装个Json库还要纠结半天——先搞清楚为什么是Newtonsoft1.1 用Unity自带JsonUtility处理复杂数据时我遇到的憋屈时刻Unity从2017年开始内置了JsonUtility初衷是给开发者一个轻量级的数据绑定方案。单看名字很诱人内置零依赖直接可用但真正上手写过几个接口的人都知道它的边界有多窄。我第一次在项目里解析一个常见的订单数据时字段结构长这样外层是一个订单对象里面有商品列表、金额、时间还有一个附加信息字段是Dictionarystring, string用来放一些活动标签。我老老实实按照JsonUtility的要求定义了POCO类用FromJson反序列化结果一跑数据全空了。最让人头疼的不是它报错而是它不报错。JsonUtility遇到无法映射的字段会直接跳过遇到Dictionary类型干脆返回一个空对象整个过程静默完成你根本不知道数据在哪一步丢了。调试时我不得不把每个字段逐个打印出来比对才发现问题出在字典类型上。后来我查了Unity的文档里面写得很清楚JsonUtility支持[Serializable]标记的类、数组和ListT继承字段、接口类型、Dictionary等都不在支持范围内。也就是说在Unity2022里你选了JsonUtility就等于默认放弃了和现代API交互时最常见的那批数据结构。1.2 Newtonsoft.Json在Unity项目里到底承担哪些活Newtonsoft.Json通常也叫Json.NET是.NET生态里的事实标准Json库Unity官方从2018年起也意识到光靠JsonUtility撑不起真实业务的复杂度于是在Unity 2018.4/2019.2之后的版本里开始以官方包的形式把Newtonsoft.Json集成进来也就是com.unity.nuget.newtonsoft-json。这个包本质上是把NuGet上的Newtonsoft.Json原封不动地打包成Unity可识别的包格式你在Unity里写JsonConvert.SerializeObject、JsonConvert.DeserializeObject时用到的就是它。在我做过的Unity项目里Newtonsoft.Json承担了这几类核心工作网络接口数据解析后端返回的JSON结构往往不规整比如字段命名是驼峰、包含可空字段、有多态类型需要根据某个字段值分发到不同的子类这些用JsonUtility几乎无法优雅处理而Newtonsoft有JsonProperty、JsonConverter、ContractResolver等全套工具。本地存档系统存档文件通常涉及大量对象嵌套、枚举类型、DateTime时间戳JsonUtility不支持DateTime存档时还得自己做一层字符串转换用Newtonsoft直接在对象图上序列化一声SerializeObject把整个存档类扔进去就完事。动态Json对象操作有些业务场景不想为每种数据结构都建一个类直接操作JObject、JArray更灵活比如处理一些三方平台回调的webhook数据或者写编辑器工具时临时解析一批配置。这块也是Newtonsoft最顺手的地方JsonUtility完全做不到。1.3 Unity2022对Newtonsoft.Json的官方态度不在默认仓库但对它是亲儿子待遇Unity2022的Package Manager默认有一个Unity Registry内置包源所有官方发布的包都能在里面搜到Newtonsoft.Json的官方包也在这里。但有个细节很多人容易忽略Package Manager左上角的下拉菜单如果停留在My Registries你自己配置的私有注册表你就搜不到Newtonsoft Json这个包。必须切换成Unity Registry才会出现在搜索结果里。还有一个更常见的误解Asset Store里搜Newtonsoft Json也能找到同名的资源包不少人以为这是某个第三方开发者打包上传的插件实际上它就是官方那个com.unity.nuget.newtonsoft-json包的另一种分发渠道。导入Asset Store版本和用Package Manager安装最终写入工程Packages/manifest.json的内容完全一样。这一点后面我会展开讲。2. 在Unity2022里装NewtonsoftJson的几种路子——我四种都试过给你一份对照2.1 方法一Package Manager里搜Newtonsoft Json先切对下拉菜单最标准的安装路径是走Package Manager步骤看起来不多但入口很容易搞错。打开Unity2022工程依次点击Window Package Manager这时默认界面左上角会有一个下拉框通常显示的是My Registries。这个状态下你直接在搜索框输入Newtonsoft大概率什么都搜不到或者只能搜到一些不属于官方源的名字。解决方法特别简单把这个下拉框切换成**Unity Registry**。切换之后再在搜索框输入Newtonsoft Json列表里就会弹出Newtonsoft.Json这个包发布者是Unity Technologies Inc.说明它是官方维护的包。选好包后右侧信息面板会显示当前最新版本号我记得Unity2022下一般能选到3.2.1。点Install按钮Unity会自动解析依赖并写入Packages/manifest.json。这一过程有无必要多解释一句很多教程会让你直接改manifest.json但如果你能通过界面操作还是优先走界面因为Unity会自动检查包之间的依赖关系避免手写JSON时把版本号写错导致resolve失败。安装完成后你可以验证一下回到代码编辑器新建一个C#脚本输入using Newtonsoft.Json;如果编译器不再飘红说明包已经生效。2.2 方法二从Asset Store导入Newtonsoft Json和Package Manager是同一个东西如果你习惯用Asset Store也可以走这条路。在Asset Store里搜索Newtonsoft Json会看到一个名为Newtonsoft Json的免费资源作者显示为Unity Technologies。点击Add to My Assets后打开Window Package Manager把左上角下拉菜单从Unity Registry切换到My Assets我的资源就能看到刚才添加的包点Download再点Import。重点来了导入完成后你打开Packages/manifest.json看一眼会发现dependencies字段里多了一行com.unity.nuget.newtonsoft-json: 3.2.1。它和我在2.1节里用Package Manager装出来的结果一模一样。所以这两条路本质上没有任何区别只是入口不同Asset Store那个页面相当于官方包的一个推广位。唯一要注意的是不要手抖把同一个包通过两条路同时装进工程有些人在Package Manager里装了一次又从Asset Store导入了一次虽然不太会出事但多一个步骤就多一处发生版本冲突的可能。2.3 方法三直接改manifest.json加依赖适合离线环境或批量部署如果你的开发机网络不稳定或者公司内部有统一的工程模板需要批量接入手动修改manifest.json是一条更可控的路。用任何文本编辑器打开工程根目录下的Packages/manifest.json在dependencies的大括号里追加一行{ dependencies: { com.unity.nuget.newtonsoft-json: 3.2.1 } }保存后切回Unity窗口稍等几秒Unity会自动检测到manifest.json变化并完成包解析。这个过程不需要额外点安装按钮只要Unity处于打开状态它会在后台重新resolve包列表。这里有一个特别容易让人犯迷糊的点manifest.json里写的版本号3.2.1不是Newtonsoft.Json这个库的NuGet版本号而是Unity包的版本号。Unity包的3.x系列对应的是NuGet上的Newtonsoft 13.x。如果以后你自己去NuGet查Newtonsoft.Json的最新版看到13.0.3别急着把这个数字填进manifest那是另一个维度的事情。具体对应关系我在3.1节里给一个表格。2.4 方法四直接往Plugins里塞DLL不到万不得已别这么做还有一种很野的路子从NuGet下载Newtonsoft.Json的dll手动放到Assets/Plugins目录下。这条路的优点是离线可用、控制力强缺点是坑相当密集。NuGet下载的dll是针对.NET Framework或.NET Standard编译的Unity2022默认使用.NET Standard 2.1兼容级别大多数情况下能通用但偶尔会遇到Unity版本兼容性提示或者某些API在你的目标平台比如Android的IL2CPP下行为不一致。更麻烦的是版本管理的问题。一旦你手里有一个NuGet的13.0.3 dll而项目里某个老插件也捆绑了12.x的Newtonsoft.Json两个dll放在一起打包时就会出现类重复定义的编译错误。我一度觉得这条路径很灵活但经历过一次导出APK时的Duplicate class报错之后我基本把这种方案降级为临时排查问题用了。如果你只是在自己电脑上用几天的工具型脚本塞DLL可以接受如果是正经商业项目老老实实走Package Manager。下表把这四种方式的关键差异列清楚方便你根据场景选安装方式操作入口是否离线版本管理与依赖解析推荐程度Package ManagerWindow Package Manager Unity Registry需要联网由Unity自动解析版本写入manifest首选Asset Store导入Package Manager My Assets需要联网与Package Manager一致本质相同备选手动改manifest.json编辑Packages/manifest.json无需等待界面需要手动确保版本号正确Unity自动resolve批量部署推荐手动放DLL拷贝到Assets/Plugins完全离线无版本管理容易冲突仅临时使用3. 装好之后遇到的第一批雷版本号、序列化配置、JSON库混用3.1 版本号的两个3Unity包版本和NuGet版本别再搞混我把这个单独拿出来说是因为几乎每个新人都踩过。Unity的com.unity.nuget.newtonsoft-json包版本号和Newtonsoft.Json的版本号是两个独立体系在排查编译报错时如果把两个号混在一起会在版本兼容性上浪费大量时间。我整理了一张常用对应表Unity包版本对应Newtonsoft.JsonNuGet版本备注2.0.012.0.3较早版本Unity 2019-2020时期常见3.0.213.0.1首次引入13.x系列3.1.013.0.2修复部分序列化问题3.2.013.0.2小版本更新3.2.113.0.3Unity2022下最常见的最新版本这个表很重要。比如你搜到网上某段代码用了JsonConvert的新特性但你的包是2.x对应Newtonsoft 12某些新API可能不存在。这时候升级Unity包版本比去网上找hack方案要靠谱得多。在Package Manager里选中Newtonsoft.Json点版本号旁边的See all versions选高版本的3.2.1即可。3.2 序列化配置和属性的几个常用项让Newtonsoft按你的规矩出牌安装只是第一步真正决定你代码体验的是序列化配置。默认情况下JsonConvert.SerializeObject会按类的公开属性和字段进行序列化字段名、可空值处理、日期格式这些都是有默认行为的。实际做项目时我基本上会封装一个全局的JsonSerializerSettings统一控制几个关键选项。using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; public static class JsonConfig { public static readonly JsonSerializerSettings Settings new JsonSerializerSettings { // 属性序列化使用驼峰命名方便和后端字段对齐 ContractResolver new CamelCasePropertyNamesContractResolver(), // 不序列化值为null的字段减小数据体积 NullValueHandling NullValueHandling.Ignore, // 枚举序列化为字符串而不是数字 Converters { new StringEnumConverter() }, // 日期格式处理 DateFormatString yyyy-MM-dd HH:mm:ss }; }然后所有序列化调用统一走这个配置string json JsonConvert.SerializeObject(order, JsonConfig.Settings); var order JsonConvert.DeserializeObjectOrder(json, JsonConfig.Settings);这种做法的好处是全局风格一致不会出现一个接口用驼峰、另一个接口用PascalCase的割裂状态。另外开发过程中调试序列化结果时我习惯临时把Formatting.Indented加进去方便肉眼检查字段是否正确映射。高级一点的需求比如某个字段在后端叫product_id而C#类里叫ProductId可以用[JsonProperty(product_id)]挂在字段上让Newtonsoft自动做重命名映射。这比写完类再手工做一层字段转换干净得多。3.3 JsonUtility和Newtonsoft.Json混用表面编译正常运行起来悄悄丢字段这个坑的隐蔽性极强我说个真实场景安卓端接口里一个用户信息对象有一个字段是动态的Dictionarystring, object存储扩展属性。为了快速出包我先用JsonUtility解析了一层基础字段扩展属性只做了简单的空判断没去细究。编译正常功能看起来也正常上线后线上反馈部分用户的一些个性化设置没有生效。查了很久才发现问题不在业务逻辑而在JsonUtility对Dictionary的静默丢弃行为。对象里凡是嵌套Dictionary或接口类型的字段JsonUtility在反序列化时会直接忽略。如果这段代码同时被Newtonsoft.Json的另一套逻辑处理两边的数据结果就会产生差异——同一个对象Newtonsoft能拿到完整字段JsonUtility只能拿到一部分。这个一半能读、一半不能读的割裂状态最烦人因为它在编辑器下和简单测试里几乎发现不了要等到真机上跑复杂数据才暴露。我的建议是在一个工程里同一套数据模型只选一个Json库作为序列化主路径。如果项目里有历史包袱必须混用至少要把涉及Dictionary、DateTime、继承、接口类型的对象全部划归Newtonsoft处理JsonUtility只负责那些它确实支持的最简单的数据交换。边界写清楚后续排查会省掉很多工作量。3.4 编辑器下一切正常打包后类却消失了第一次遇到IL2CPP裁剪这个坑我是从Unity导出APK时才体会到的。编辑器下跑数据一切正常一旦用Unity2022的Android平台打包Build Settings里Scripting Backend选的是IL2CPP安装到真机上运行其他逻辑都好好的只要一调用JsonConvert.DeserializeObjectT()去解析某个自定义类型立刻抛MissingMethodException或直接崩溃日志指向的分分钟是Newtonsoft.Json内部的某个方法。原因要从Unity2022的Android打包机制说起。默认Scripting Backend是IL2CPP配合Managed Stripping Level托管代码裁剪等级可以裁剪掉看起来没有被使用的类和方法以缩小包体。但Newtonsoft.Json的核心机制是反射它通过字符串形式的类型名和属性名去动态查找和调用方法这种建立在反射之上的逻辑静态裁剪工具根本看不见引用关系于是一裁剪就把序列化过程中真正需要的类或方法给裁掉了。常见的处理方式有两个我推荐结合使用。第一个是调整Project Settings里的Managed Stripping Level在Edit Project Settings Player Managed Stripping Level中把等级从Medium或High改成Low甚至改成Disabled来验证。第二个是写一个link.xml文件放在Assets目录下告诉Unity的裁剪器哪些程序集和类型必须完整保留linker assembly fullnameNewtonsoft.Json preserveall/ /linkerpreserveall表示整程序集全部保留不让裁剪器动它。这样既保住了序列化能力又不用把全局裁剪等级关掉包体大小的损失可控。如果你嫌全保留还是太大可以只保留特定命名空间或类型但调试成本会上去我没觉得那点包体优化值得。谨慎起见等后面讲到导出APK的实操排查时我会再提一次link.xml的写法。4. 从Unity2022导出APK看Newtonsoft.Json的三个典型报错——我把排查过程完整走了一遍4.1 编译报错找不到类型或命名空间装完却用不了先别急着重装有次我在别人交接的Unity2022工程里直接改代码导入一个预制脚本时编译器直接报CS0246: The type or namespace name Newtonsoft could not be found。第一反应是包没装但打开Package Manager一看包明明在列表里manifest.json里依赖也写着。这就奇怪了。排查了一圈发现对方是在自己电脑上装的包提交到版本控制时Packages/manifest.json里有记录但Packages/packages-lock.json包锁文件因为gitignore规则没有提交上去。新拉取工程的人首次打开项目Unity需要重新解析所有依赖虽然也会自动把Newtonsoft.Json下载下来但有时候解析时机和新脚本导入的时机是错开的。我的处理办法很简单关掉Unity编辑器删掉工程里的Library文件夹注意不是删Assets和Packages这两块千万别动重新打开工程让Unity完整走一遍导入流程。等右下角那个转圈的资源导入进度条走完再去看编译错误通常就消失了。如果你是因为离线环境装不上包Library删了也没用得先把包文件下载到本地或者按2.3节方法手动改manifest.json配合本地缓存那属于另一个场景的坑了。4.2 打包报错Duplicate class同一个Json库出现了双重人格还有一个我遇到过不止一次的报错发生在导出APK的最后阶段Gradle构建任务爆出一堆Duplicate class日志里能看到多个来自Newtonsoft.Json相关jar或dex的重复类。一看就知道是同一个库以多种形态进入了构建流程。常见原因有两个。第一个我自己在Assets/Plugins/Android下放过一个自定义的AAR里面顺手打包了Newtonsoft.Json的jar而工程里又通过Package Manager正常安装了官方包两套相同类在最终合并dex时撞了。第二个项目里某个第三方SDK的AAR也内置了Newtonsoft.Json这属于上级依赖重复处理起来稍麻烦。我的排查步骤是这样的打开Project Settings Player Android在Build下方找到Custom Main Gradle Template勾选启用Unity会生成mainTemplate.gradle文件。打开mainTemplate.gradle在dependencies块检查是否有多处显式声明了com.newtonsoft.json相关依赖。如果有删掉其中一份保留官方包那条路。检查Assets/Plugins目录下是否有冗余的Newtonsoft.Json.dll或.jar有的话先移动到一个备份目录重新打包看报错是否消失。如果问题是某个三方SDK锁定了特定版本的Newtonsoft.Json而你又不想放弃SDK最省事的办法是把官方包版本调整到和SDK内置版本一致比如都退到12.x让Gradle去重时能合并在一起。真要较真区分不同版本就得自己写Gradle脚本做exclude不是不行但为开个Json库没必要。4.3 运行时菜单里能跑、APK里崩溃IL2CPP裁剪时代的典型死法回到3.4节那个场景我把link.xml补上之后打包到真机上测试顺带做了一个最小复现验证。这里分享一下我的完整排查链路如果你也遇到了编辑器正常、真机崩溃的序列化问题可以直接按这个顺序走先把Build Settings里的Managed Stripping Level临时改成Disabled重新打包装到真机上。如果问题消失基本锁定是裁剪问题。再改回原来的等级在Assets目录下新建link.xml写入我3.4节的那段内容重新打包验证。如果还崩打开Window Analysis Build Report在Build Report里找到App Size相关页面看看Newtonsoft.Json这个程序集是否被打进包体以及有没有被奇怪地裁剪掉。最后检查是否在代码里用了某个Newtonsoft.Json的API但目标版本不支持那种情况会升级Unity包版本解决。还有一个容易被忽略的点如果link.xml放在Assets目录下改了之后Unity有时不会立即触发重新编译最好把文件touch一下随便加个空格保存或者重启一下编辑器确保裁剪配置真正生效。这个细节我亲测过差点以为link.xml没用。4.4 装完怎么快速验证能用一个覆盖常见类型的最小测试给一个我每次装完包、或者升级包版本之后都会跑一遍的验证脚本。它在Android真机上能覆盖序列化最常用的场景嵌套类、数组、字典、枚举、可空类型、日期全过一遍基本能确定Newtonsoft.Json在当前工程环境里是健康的。using System; using System.Collections.Generic; using Newtonsoft.Json; using UnityEngine; public class NewtonsoftSmokeTest : MonoBehaviour { [Serializable] public class Order { public int OrderId { get; set; } public string CustomerName { get; set; } public ListOrderItem Items { get; set; } public Dictionarystring, object Extras { get; set; } public OrderStatus Status { get; set; } public DateTime CreatedAt { get; set; } public decimal? Discount { get; set; } public Order() { Items new ListOrderItem(); Extras new Dictionarystring, object(); } } public enum OrderStatus { Pending, Paid, Shipped, Done } [Serializable] public class OrderItem { public string Sku { get; set; } public int Count { get; set; } public float Price { get; set; } } void Start() { var order new Order { OrderId 10086, CustomerName test-user, Items new ListOrderItem { new OrderItem { Sku SKU-A, Count 2, Price 19.9f } }, Extras new Dictionarystring, object { { vipLevel, gold }, { coupon, 3 } }, Status OrderStatus.Paid, CreatedAt DateTime.Now, Discount 5.5m }; string json JsonConvert.SerializeObject(order); // 先序列化 Debug.Log([SmokeTest] serialized: json); var restored JsonConvert.DeserializeObjectOrder(json); // 再反序列化 Debug.Log([SmokeTest] restored order id: restored.OrderId); Debug.Log([SmokeTest] restored item count: restored.Items.Count); Debug.Log([SmokeTest] restored extras vipLevel: restored.Extras[vipLevel]); } }把这个脚本挂到场景中的任意物体上跑一遍看Logcat里[SmokeTest] restored开头的日志是否正常打出就能确认包的安装、裁剪配置、序列化机制在真机上全部工作正常。这些Log正常了后面写具体业务逻辑才有底气不然每次报错都得怀疑一遍是环境问题还是代码问题。我在实际项目里的体会是装一个Json库的处理方式本身不难真正花时间的往往是把安装机制、Unity包版本体系、IL2CPP裁剪逻辑这三件事彻底搞明白。现在遇到任何Newtonsoft.Json相关的报错我的第一反应都不是重新安装而是按包的来源是不是唯一序列化的对象是不是涉及到反射裁剪版本号是不是Unity包和NuGet版本搞混了这三条线去查排查效率比当年拿着报错信息盲搜要快得多。如果你也在Unity2022里准备用Newtonsoft.Json建议先从Package Manager那个官方入口装好然后把link.xml提前配上剩下的大部分坑就绕开了。
返回列表