Eiceblue 2019-10-04
本篇内容包括如下部分内容:JSON编码指南;JSON应用场景;实践经验之谈
这里的JSON指南,主要推荐的是谷歌的Google JSON风格指南。无论什么语言编程,也通常遵循2/8法则,即遵循好的设计与编码风格,能提前解决80%的问题。关于谷歌的这个JSON编程风格指南手册,可以在如下两个地方找到:
请主要核心点,简单摘录如下:
另外,需要关注的内容是JSON API规范,目前该项工作在推进中,取得了很多可喜成果。关于JSON API,这里提几点:
规范详情可以参考整理:http://jsonapi.org.cn/format/。
JSON的使用,依据不同用途,有几个典型的场景:
在借助各类工具处理JSON数据时,最常见的其实是大家使用的不规范性,这样碰到各种坑的可能性就很大。根据平时使用Fastjson的经验以及工程师的应用反馈,这里简要总结一下大家常见的问题,归纳如下:
实践告诉我们:遵循beans规范和JSON规范的方式,能减少大部分的问题,比如正确实现setter、getter,用别名就加annotation。注意基本类型的匹配转换,比如在fastjson的issue见到试图把”{“a”:{}}”中的a转换成List的。
尽量不要使用数字等字符开头的key,尽量使用符合Java的class或property命名规范的key,这样会减少不必要的冲突。在jsonpath或js里,a.1可能会被解释成a[1]或a[“1”],这些都会带来不必要的麻烦。
这一点前面的Google JSON风格指南里也提到了,尽量使用标准的日期格式。或者序列化和反序列化里都是用同样的datePattern格式。
对于新手来说,自定义序列化是一切罪恶的根源。
尽量不要使用自定义序列化,除非万不得已,优先考虑使用注解过滤,别名等方式,甚至是重新建一个VO类来组装实际需要的属性。使用自定义序列化时一切要小心,因为这样会导致两个问题:
如果只是序列化发出去(响应)的是JSON数据、传过来(请求)的数据格式跟JSON无关或者是标准的,此时自定义序列化就无所谓了,反正是要接收方来处理。
JSONObject是JSON字符串与pojo对象转换过程中的中间表达类型,实现了Map接口,可以看做是一个模拟JSON对象键值对再加上多层嵌套的数据集合,对象的每一个基本类型属性是map里的一个key-value,一个非基本类型属性是一个嵌套的JSONObject对象(key是属性名称,value是表示这个属性值的对象的JSONObject)。如果以前用过apache beanutils里的DynamicBean之类的,就知道JSONObject也是一种动态描述Bean的实现,相当于是拆解了Bean本身的结构与数据。这时候由于JSONObject里可能会没有记录全部的Bean类型数据,例如泛型的具体子类型之类的元数据,如果JSONObject与正常的POJO混用,出现问题的概率较高。
下列方式尽量不要使用:
public class TestBean{ @Setter @Getter private TestBean1 testBean1; @Setter @Getter private JSONObject testBean2; // 尽量不要在POJO里用JSONObject } ``` 应该从设计上改为都用POJO比较合适: ```java public class TestBean{ @Setter @Getter private TestBean1 testBean1; @Setter @Getter private TestBean2 testBean2;; // 使用POJO } ``` 相对的,写一些临时性的测试代码、demo代码,可以直接全部用JSONObject先快速run起来。 同理,jsonstring中嵌套jsonstring也尽量不要用,例如: ```javascript { "name":"zhangsan", "score":"{\"math\":78,\"history\":82}" }
应该改为全部都是JSON风格的结构:
{ "name":"zhangsan", "score":{ "math":78, "history":82 } }
另外,对于jsonstring转POJO(或POJO转jsonstring),尽量使用直接转的方式,而不是先转成JSONObject过渡的方式。特别是对于Fastjson,由于性能优化的考虑,这两个执行的代码是不一样的,可能导致不一样的结果。
String jsonstring = "{\"a\":12}"; // 不推荐这种方式 // 除非这里需要对jsonObject做一些简单处理 JSONObject jsonObject = JSON.parseObject(jsonstring); A a = jsonObject.toJavaObject(A.class); // 推荐方式 A a = JSON.parseObject(jsonstring, A.class);
懒加载与级联,可能导致出现问题,例如hibernate,建议封装一层VO类型来序列化。使用VO类还有一个好处,就是可以去掉一些没用的属性,减少数据量,同时可以加上额外的属性。
尽量不要在使用过多的层次嵌套时使用泛型(List、Map等),可能导致类型丢失,而且问题比较难查。
尽量不要在同一个Bean的层次结构里使用多个子类型对象,可能导致类型丢失,而且问题比较难查。当然我们可以通过代码显示的传递各种正确的类型,但是这样做引入了更多的不确定性。良好的做法应该是一开始设计时就避免出现这些问题。
尽量避免循环引用,这个虽然可以通过序列化特性禁掉,但是如果能避免则避免。
对于InputStream、OutputStream的处理,有时候会报一些奇怪的错误,not match之类的,这时候也许我们看日志里的json字符串可能很正常,但就是出错。
这时可能就是编码的问题了,可能是导致字符错乱,也可能是因为UTF-8文件的BOM头,这些潜在的问题可能在二进制数据转文本的时候,因为一些不可见字符无法显示,导致日志看起来只有正常字符而是正确的,问题很难排查。
处理办法就是按二进制的方式把Stream保存起来,然后按hex方式查看,看看是否有多余字符,或者其他错误。
以上为我们在实际JSON编程中应该关注和推荐的方面和方法,希望对你有帮助。