☰
Unity2022安装与使用Newtonsoft.Json实战避坑:从Package Manager到APK导出
2026/10/6 16:29:47 网站建设 项目流程

刚跨到Unity2022那会儿,我写过一段处理后端接口返回的代码,用JsonUtility.FromJson解析,结果一运行直接给我扔回来一堆空对象。数组套对象还好说,一旦遇到嵌套的Dictionary类型,Unity自带的JsonUtility根本不管,字段悄悄丢了连个报错都不给。翻了一圈社区,发现所有人给的答案都指向同一个方向:去装NewtonsoftJson。可问题来了,在Unity2022里装这个库,网上说法五花八门,有说Package Manager直接搜的,有说要去Asset Store下载的,还有人说直接扔一个DLL进Plugins目录就行。我先后把这几条路都趟了一遍,期间还踩了导出APK时编译报错、IL2CPP裁剪把库裁没、包版本号对不上等一堆坑。这篇就把Unity2022安装和使用NewtonsoftJson这件事完整说透,覆盖几种安装方式的差别、序列化配置的注意点、以及导出APK前后最容易出问题的三个典型场景,给正准备在Unity2022里做Json处理的人一份能直接照着走的参考。

1. 为什么Unity2022装个Json库还要纠结半天——先搞清楚"为什么是Newtonsoft"

1.1 用Unity自带JsonUtility处理复杂数据时,我遇到的憋屈时刻

Unity从2017年开始内置了JsonUtility,初衷是给开发者一个轻量级的数据绑定方案。单看名字很诱人,"内置""零依赖""直接可用",但真正上手写过几个接口的人都知道它的边界有多窄。我第一次在项目里解析一个常见的订单数据时,字段结构长这样:外层是一个订单对象,里面有商品列表、金额、时间,还有一个附加信息字段是Dictionary<string, string>,用来放一些活动标签。我老老实实按照JsonUtility的要求定义了POCO类,用FromJson反序列化,结果一跑数据全空了。

最让人头疼的不是它报错,而是它不报错。JsonUtility遇到无法映射的字段会直接跳过,遇到Dictionary类型干脆返回一个空对象,整个过程静默完成,你根本不知道数据在哪一步丢了。调试时我不得不把每个字段逐个打印出来比对,才发现问题出在字典类型上。后来我查了Unity的文档,里面写得很清楚:JsonUtility支持[Serializable]标记的类、数组和List<T>,继承字段、接口类型、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 版本号的"两个3":Unity包版本和NuGet版本别再搞混

我把这个单独拿出来说,是因为几乎每个新人都踩过。Unity的com.unity.nuget.newtonsoft-json包版本号和Newtonsoft.Json的版本号是两个独立体系,在排查编译报错时如果把两个号混在一起,会在版本兼容性上浪费大量时间。

我整理了一张常用对应表:

Unity包版本对应Newtonsoft.Json(NuGet)版本备注
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.DeserializeObject<Order>(json, JsonConfig.Settings);

这种做法的好处是全局风格一致,不会出现一个接口用驼峰、另一个接口用PascalCase的割裂状态。另外,开发过程中调试序列化结果时,我习惯临时把Formatting.Indented加进去,方便肉眼检查字段是否正确映射。

高级一点的需求,比如某个字段在后端叫product_id,而C#类里叫ProductId,可以用[JsonProperty("product_id")]挂在字段上,让Newtonsoft自动做重命名映射。这比写完类再手工做一层字段转换干净得多。

3.3 JsonUtility和Newtonsoft.Json混用,表面编译正常,运行起来悄悄丢字段

这个坑的隐蔽性极强,我说个真实场景:安卓端接口里一个用户信息对象,有一个字段是动态的Dictionary<string, 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.DeserializeObject<T>()去解析某个自定义类型,立刻抛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 fullname="Newtonsoft.Json" preserve="all"/> </linker>

preserve="all"表示整程序集全部保留,不让裁剪器动它。这样既保住了序列化能力,又不用把全局裁剪等级关掉,包体大小的损失可控。如果你嫌全保留还是太大,可以只保留特定命名空间或类型,但调试成本会上去,我没觉得那点包体优化值得。谨慎起见,等后面讲到导出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,这属于上级依赖重复,处理起来稍麻烦。

我的排查步骤是这样的:

  1. 打开Project Settings > Player > Android,在Build下方找到Custom Main Gradle Template,勾选启用,Unity会生成mainTemplate.gradle文件。
  2. 打开mainTemplate.gradle,在dependencies块检查是否有多处显式声明了com.newtonsoft.json相关依赖。如果有,删掉其中一份,保留官方包那条路。
  3. 检查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补上之后,打包到真机上测试,顺带做了一个最小复现验证。这里分享一下我的完整排查链路,如果你也遇到了"编辑器正常、真机崩溃"的序列化问题,可以直接按这个顺序走:

  1. 先把Build Settings里的Managed Stripping Level临时改成Disabled,重新打包装到真机上。如果问题消失,基本锁定是裁剪问题。
  2. 再改回原来的等级,在Assets目录下新建link.xml,写入我3.4节的那段内容,重新打包验证。
  3. 如果还崩,打开Window > Analysis > Build Report,在Build Report里找到App Size相关页面,看看Newtonsoft.Json这个程序集是否被打进包体,以及有没有被奇怪地裁剪掉。
  4. 最后检查是否在代码里用了某个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 List<OrderItem> Items { get; set; } public Dictionary<string, object> Extras { get; set; } public OrderStatus Status { get; set; } public DateTime CreatedAt { get; set; } public decimal? Discount { get; set; } public Order() { Items = new List<OrderItem>(); Extras = new Dictionary<string, 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 List<OrderItem> { new OrderItem { Sku = "SKU-A", Count = 2, Price = 19.9f } }, Extras = new Dictionary<string, 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.DeserializeObject<Order>(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提前配上,剩下的大部分坑就绕开了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询