解析错误是在API时代不断给予的礼物。我们使用一项服务;它在调试、QA等方面工作得很好。然后,一些用户的输入进入了网络请求,返回一个我们无法解析的结果。不幸的是,在这个阶段,我们能做的并不多。我们需要了解失败发生的原因,以及我们如何能够绕过它并修复它。
在生产中,这并不是一件小事。我们可以记录我们所做的所有调用和所有传递的对象。但这将破坏我们的性能,并使我们的日志存储费用上升到屋顶。它还可能使我们违反隐私法,因为JSON数据可能包括私人用户信息。这是生产中的一个常见问题,因为网络服务可能会改变,并在运行时触发序列化失败。这在多语言环境中尤其如此,一个使用Jackson等API映射的Java类可能会在NodeJS对象的反序列化中失败。
对于JSON处理,Java有三个常用的库。
- Jackson - 这三个库中最古老、最稳定的解析器API
- Gson--来自谷歌的一个更轻量级的API
- Moshi--在服务器中不那么出名(在Android上更受欢迎)。由Gson的作者编写,应该被认为是它的继承者,因为Gson不再被积极维护。
Java中JSON解析的基础知识
这些库在概念上是非常相似的。它们支持通用类型的序列化并将其映射到对象。我们可以在任意的Java对象或对象的列表中进行转换。为了简单起见,我将专注于字符串,因为这是一个关于调试的教程。
在下面的代码片段中,我们使用标准的Java POJO(Plain Old Java Object)来表示User和DBObject的值。这些是琐碎的对象,包含相对简单的字段,如字符串、基元、字节数组等。在演示代码中,为了方便,我用Lombok创建了它们,但它们用getter/setters也能很好地工作。这也可以适用于具有深度嵌套的复杂对象,集合类型等。
使用Jackson,你可以用这样的代码生成一个JSON字符串。
String json = mapper.writeValueAsString(user);
你可以用Jackson执行对象序列化。
DBObject object = mapper.readValue(json, DBObject.class);
对于Gson,我们可以用同样的方式完成。
String json = gson.toJson(user);
而这就是Gson转换为对象的方式。
DBObject object = gson.fromJson(json, DBObject.class);
Moshi是一个比较冗长的程序,但支持更多的定制功能和类型安全。
String json = moshi.adapter(User.class).toJson(user);
这就是在Moshi中获得一个对象的代码。
DBObject object = moshi.adapter(DBObject.class).fromJson(json);
请注意,Moshi开箱即用类型适配器。类型适配器 "的概念并不是Moshi独有的,它存在于所有这些工具中。它让我们执行自定义的反序列化,特别是 "低级 "对象/JSON映射。在这种情况下,Moshi把这个API放在最前面,以
演示
为了演示调试,我创建了一个简单的JSON数据库应用程序,让我们把元素保存为JSON文件。你可以在这里找到完整的源代码。这个演示涵盖了所有三个JSON解析器和它们的大部分基本功能,如类型适配器。
我们可以通过提交一个HTTP头来挑选要在请求中使用的API,DatabaseWS类中的一个工厂方法会挑选正确的实现实例。这样,我们可以将任何请求作为Gson、Jackson或Moshi来处理。默认是Moshi。
这是一个标准的Spring Boot项目,你可以在IntelliJ/IDEA中打开。这篇文章的其余部分假设你已经安装并运行了Lightrun。如果没有,请在这里免费安装它。你可以在这里阅读它的相关信息。
创建一个数据库用户
在我们开始之前,我们需要创建一个新的数据库用户。我们可以用下面的命令来做。
curl -X PUT -H "Content-Type: application/json" -H "Authorization: 45971c45-4049-48f8-970f-04d47be2defc" -d '{"login":"user", "password":"123456", "givenName":"Shai", "surname":"Almog"}' "http://localhost:8080/addUser"
一旦执行,你应该看到一个名为"~/myDB/user.user "的文件。注意,~代表当前用户的主目录。该文件应该包含类似于以下JSON的内容。
{
"login": "user",
"givenName": "Shai",
"surname": "Almog",
"email": null,
"hashedPassword": "$2a$10$DeBGvevs8RiHCIdRqa9fo.ED.6K2UYXXgXYF1.6uLxU1yxmq9c8ZK",
"token": "7e5a50db-f44a-4177-af48-6fa39c127810",
"password": null
}
该文件是由Moshi生成的。如果我们希望对解析器/生成器使用其他选项,我们可以添加参数。
-H "type: jackson" or -H "type: gson" respectively.
认证
一旦我们创建了一个用户,我们就可以使用认证API,用类似这样的curl命令来登录数据库。
curl -H "Content-Type: application/json" -H "Authorization: 45971c45-4049-48f8-970f-04d47be2defc" -H "type: jackson" -d '{"login":"shai","password":"123456"}' "http://localhost:8080/auth"
在这种情况下,我选择使用Jackson解析器而不是moshi的默认值来登录。这个命令在响应中返回一个令牌,然后我们可以用它来执行操作。
添加一条记录
我们可以使用一个命令向 "数据库 "添加一条记录,例如。
curl -H "Content-Type: application/json" -H "Authorization: 45971c45-4049-48f8-970f-04d47be2defc" -H "type: jackson" -d '{"coreData":"FBYWFiEs"}' "http://localhost:8080/create"
注意,我们上面发送的认证调用的响应被用在授权头中。
数据库中还有一些其他的功能,但我现在先跳过它们......
读取一个条目
使用反序列化从数据库中读取元素更加容易。这是一个简单的REST GET方法。
curl -H "Content-Type: application/json" -H "Authorization: 45971c45-4049-48f8-970f-04d47be2defc" -H "type: jackson" "http://localhost:8080/read?id=0ca3edb8-37db-4d97-bbc4-e5222e94db17"
在这种情况下,我又使用了Jackson,但它对Moshi/Gson也应该是一样的。注意读取调用的参数是对象的ID。我们从创建调用中收到ID作为响应。
检查和调试
调试Gson、Jackson或Moshi这样的API是非常具有挑战性的。与典型的Java APIs不同,这些工具在失败时将抛出一个检查过的异常,这些工具通常记录解析错误。此外,许多序列化错误或问题会以缺失选项的形式出现,而不是一个可见的错误。
一个类可能有不同的字段名,或者可能缺少一个额外的字段,结果对象字段在反序列化过程后可能包括部分信息。不幸的是,当我们拥有类型对象时,已经有点太晚了。JSON已经消失了,我们无法将其与结果进行比较。我们想在序列化过程中检查对象。
在我们继续之前,你需要确保你安装了Lightrun,并且对它的使用有基本的了解。有一个免费的版本,你可以从这里安装。
一旦我们有了这个,我们就可以开始调试...
调试序列化的行动
生成JSON文件的代码通常更容易调试,问题也更少。这是一个简单的开始。我们可以在写对象的地方放置一个快照。例如,在这个例子中,我使用了JSONDatabaseService类,并在writeString方法调用中放置了一个快照。
在栈中,我可以看到JSON数据和原始对象。这样一来,我们就可以检查两者之间的任何差异。通常情况下,这可能与Jackson或Gson等实现的默认行为有关,涉及到空对象、日期、时间等。这些都是各种实现之间的细微差别。
反序列化
将JSON字符串或文件读入一个类是我们大多数人失败的地方。缺少的名字会被跳过,一个小的错别字也会成为一个巨大的问题。
这就是在我们将字符串转换为对象之前检查JSON的能力的真正价值所在。在这个例子中,我们可以通过在JacksonDatabaseService 类中放置一个快照来轻松做到这一点。注意,这是Jackson特有的。我们可以通过在Gson和moshi各自的抽象类中放置快照,对它们做同样的事情。
一旦放置了这个,我们可以尝试读取一个对象,我们会看到收到的JSON。在这个特殊的例子中,从方法参数中抓取JSON是很简单的。然而,这并不是例外,在大多数情况下抓取这些数据是可能的。
这是回顾字段名和各种类型适配器的地方,以了解产生的对象字段。
最后的话
无论你使用Jackson、Gson还是moshi,在Java中处理JSON总是一个挑战。他们都以大致相同的方式失败,而且经常是在生产数据中失败。由于记录数据可能会使我们面临责任和不断增加的成本,我们调试这些API的唯一选择是通过开发者观察工具,如Lightrun。
对于大多数的调试情况,我们可以只在API访问上放置一个快照,并检查所产生的节点、列表等。这通常是帮助我们转换类型表示的通用代码,如foo类型的对象。到一个我们可以发送/存储的JSON文件,等等。