资讯专栏INFORMATION COLUMN

Java常用json序列化工具的部分局限性

elliott_hu / 3365人阅读

摘要:我最终的解决方案是用反序列化,再利用反射工具来对某些数值进行到的转化。速度快,但是对反序列化的类也有一定要求,而且在反序列化复杂的时较多阿里云的很多也由于这个原因并未使用。

Problem

在写Java的Unit Test时,对于Unit Test所期望的值,一般是利用工具从test/resources目录下将expectResult.json读取进来并用json序列化工具进行反序列化来获得Unit Test所期望的结果,并与测试的实际结果进行对比。然而若是反序列化所用的类来源于第三方库(即不能更改任何代码),会遇到很大的问题。以下以本人对阿里云的StreamRecord类进行反序列化时遇到的问题进行描述:

StreamRecord类的定义如下:

</>复制代码

  1. public class StreamRecord {
  2. public enum RecordType {
  3. /**
  4. * PUT类型
  5. * 如果对应行已存在,该Record需要覆盖原有数据。
  6. */
  7. PUT,
  8. /**
  9. * UPDATE类型
  10. * 如果对应行已存在,该Record是在原有数据上的更新。
  11. */
  12. UPDATE,
  13. /**
  14. * DELETE类型
  15. * 表明要删除对应的行。
  16. */
  17. DELETE
  18. }
  19. /**
  20. * Record的类型
  21. */
  22. private RecordType recordType;
  23. /**
  24. * 对应行的主键
  25. */
  26. private PrimaryKey primaryKey;
  27. /**
  28. * 对应行的时序信息
  29. */
  30. private RecordSequenceInfo sequenceInfo;
  31. /**
  32. * 该Record包含的属性列,为RecordColumn类型
  33. */
  34. private List columns;
  35. /**
  36. * 获取Record的类型
  37. * @return Record的类型
  38. */
  39. public RecordType getRecordType() {
  40. return recordType;
  41. }
  42. public void setRecordType(RecordType recordType) {
  43. this.recordType = recordType;
  44. }
  45. /**
  46. * 获取对应行的主键
  47. * @return 对应行的主键
  48. */
  49. public PrimaryKey getPrimaryKey() {
  50. return primaryKey;
  51. }
  52. public void setPrimaryKey(PrimaryKey primaryKey) {
  53. this.primaryKey = primaryKey;
  54. }
  55. /**
  56. * 获取该行的时序信息
  57. * @return 该行的时序信息
  58. */
  59. public RecordSequenceInfo getSequenceInfo() {
  60. return sequenceInfo;
  61. }
  62. public void setSequenceInfo(RecordSequenceInfo sequenceInfo) {
  63. this.sequenceInfo = sequenceInfo;
  64. }
  65. /**
  66. * 获取该Record包含的属性列列表
  67. * @return 该Record包含的属性列列表
  68. */
  69. public List getColumns() {
  70. if (columns != null) {
  71. return columns;
  72. } else {
  73. return new ArrayList();
  74. }
  75. }
  76. public void setColumns(List columns) {
  77. this.columns = columns;
  78. }
  79. @Override
  80. public String toString() {
  81. StringBuilder sb = new StringBuilder();
  82. sb.append("[RecordType:]");
  83. sb.append(this.recordType);
  84. sb.append("
  85. [RecordSequenceInfo:]");
  86. sb.append(this.sequenceInfo);
  87. sb.append("
  88. [PrimaryKey:]");
  89. sb.append(this.primaryKey);
  90. sb.append("
  91. [Columns:]");
  92. for (RecordColumn column : this.getColumns()) {
  93. sb.append("(");
  94. sb.append(column);
  95. sb.append(")");
  96. }
  97. return sb.toString();
  98. }
  99. }

本工程原先只用Jackson进行序列化和反序列化,但Jackson的ObjectMapper在对此类进行反序列化时,报了No suitable constructor的错误,经过调查发现Jackson进行反序列化需要默认的构造函数(如果有带参数的构造函数,还要用@JsonCreator修饰构造函数,用@JsonProperty修饰构造函数参数),而上述类没有,即使有我们也不能对阿里云等第三方库进行更改,遂放弃Jackson,转而考虑阿里自己的fastjson。fastjson的确能对该类进行反序列化,但是当我仔细分析反序列化后的对象时,发现有些深层的字段的值为null,又经过一番调查,了解到fastjson虽然对反序列化的类没有构造函数的要求,但对字段有要求,反序列化的private字段要有setter方法才能正常的反序列化(或者有一个带有所有字段参数的构造函数),若是private字段缺少setter方法,则该字段的值为默认值。最后考虑用Google的Gson,Gson没有上述这些问题,但是若反序列化类有Object类型的字段,而该字段的值为数值型,则Gson都会转为Double型,比如你有个字段为

</>复制代码

  1. private Map map;

json文件:

</>复制代码

  1. {
  2. "age": 24,
  3. "height": 1.81
  4. }

当把上述json文件反序列化为map字段时,直觉上会认为“age”字段的值的类型应该为Integer或Long型,然而Gson这里有点反常,由于map的value为Object类型,并未明确指定具体的数值类型,它会将key为“age”的字段会变为Double类型(并不是我们直觉上所期望的Integer或Long型),给后续编程带来麻烦。关于Gson的这个“特性”,可以参考https://github.com/google/gso... 上面的“debate”,比较有趣的“网友怼作者”。

我最终的解决方案是用Gson反序列化,再利用反射工具ReflectionTestUtils.setField来对某些数值进行Double到Long的转化。

Conclusion

Jackson 功能强大,但对反序列化的类的要求较高(要有默认的constructor)。

Fastjson 速度快,但是对反序列化的类也有一定要求,而且在反序列化复杂的json时bug较多(阿里云的很多sdk也由于这个原因并未使用fastjson)。

Gson 比较全面,对反序列化的类的要求最低,但是对于Object类型的数值字段处理不够友好。

以上实验所用版本:

</>复制代码

  1. compile group: "com.google.code.gson", name: "gson", version: "2.8.5"
  2. compile group: "com.alibaba", name: "fastjson", version: "1.2.56"

因此,如果没有遇到序列化和反序列化第三方库的model的情况下(即代码无法更改的情况),首选Jackson,否则选Gson。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76143.html

相关文章

  • 数人云工程师手记 | 容器日志管理实践

    摘要:容器内文件日志平台支持的文件存储是,避免了许多复杂环境的处理。以上是数人云在实践容器日志系统过程中遇到的问题,更高层次的应用包括容器日志分析等,还有待继续挖掘和填坑,欢迎大家提出建议,一起交流。 业务平台每天产生大量日志数据,为了实现数据分析,需要将生产服务器上的所有日志收集后进行大数据分析处理,Docker提供了日志驱动,然而并不能满足不同场景需求,本次将结合实例分享日志采集、存储以...

    saucxs 评论0 收藏0
  • 百度java学习笔记

    摘要:一般在存当前含有当前时间的实体时,只需要配置好数据库的存储字段即可。基本代码部分循环的写法 这几天初步了解了百度云的后台架构部分,当然了,自己了解的仅限于后台java相关的部分,先说一下客户端这边使用的技术:1、spring boot : 与前端进行直接交互的服务是用spring来实现的(后台服务还需要调用其他的基础服务,如redis 数据库服务 订单服务 cdn服务 openstac...

    codeGoogle 评论0 收藏0
  • java常用列化与反列化方法

    摘要:序列化工具类序列化工具的序列化与反序列化使用实现序列化和反序列化反序列化时,必须要有默认构造函数,否则报错使用序列化缓存此类分别包含序列化序列化序列化三种序列化方式。 序列化工具类 序列化即将对象序列化为字节数组,反序列化就是将字节数组恢复成对象。主要的目的是方便传输和存储。 序列化工具类: public class SerializeUtil { private stati...

    zhkai 评论0 收藏0
  • 开发NEO智能合约步骤流程

    摘要:在社区开发的一些最新工具集的帮助下,出现了四步流程法,从而进一步加快了开发效率。两步流程法传统上来说,智能合约开发有两步开发流程编码和测试。四步工作流程法开发智能合约对于编辑和调试阶段,我建议使用两种方法和。 摘要:开发NEO智能合约的典型开发流程有两个实际阶段:编码(在IDE中编码并将源码编译为.avm文件)以及测试(在测试网上部署、调用、检查结果)。这个工作流需要编译和部署来调试任...

    I_Am 评论0 收藏0
  • 开发NEO智能合约步骤流程

    摘要:在社区开发的一些最新工具集的帮助下,出现了四步流程法,从而进一步加快了开发效率。两步流程法传统上来说,智能合约开发有两步开发流程编码和测试。四步工作流程法开发智能合约对于编辑和调试阶段,我建议使用两种方法和。 摘要:开发NEO智能合约的典型开发流程有两个实际阶段:编码(在IDE中编码并将源码编译为.avm文件)以及测试(在测试网上部署、调用、检查结果)。这个工作流需要编译和部署来调试任...

    zhou_you 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<