Java中net.sf.json包关于JSON与对象互转的坑
在Web开发过程中离不开数据的交互,这就需要规定交互数据的相关格式,以便数据在客户端与服务器之间进行传递。数据的格式通常有2种:1、xml;2、JSON。通常来说都是使用JSON来传递数据。本文正是介绍在Java中JSON与对象之间互相转换时遇到的几个问题以及相关的建议。 首先明确对于JSON有两个概念:
JSON对象(JavaScript Object Notation,JavaScript对象表示法)。这看似只存是位JavaScript所定制的,但它作为一种语法是独立于语言以及平台的。只是说通常情况下我们在客户端(浏览器)向服务器端传递数据时,使用的是JSON格式,而这个格式是用于表示JavaScript对象。它是由一系列的“key-value”组成,如 {“id”: 1, “name”: “kevin”},这有点类似Map键值对的存储方式。在Java中所述的JSON对象,实际是指的JSONObject类,这在各个第三方的JSONjar包中通常都以这个名字命名,不同jar包对其内部实现略有不同。
JSON字符串。JSON对象和JSON字符串之间的转换是序列化与反序列化的过程,这就是好比Java对象的序列化与反序列化。在网络中数据的传递是通过字符串,或者是二进制流等等进行的,也就是说在客户端(浏览器)需要将数据以JSON格式传递时,此时在网络中传递的是字符串,而服务器端在接收到数据后当然也是字符串(String类型),有时就需要将JSON字符串转换为JSON对象再做下一步操作(String类型转换为JSONObject类型)。
以上两个概念的明确就基本明确了JSON这种数据格式,或者也称之为JSON语法。Java中对于JSON的jar包有许多,最最“常用”的是“net.sf.json”提供的jar包了,本文要着重说的就是这个坑包,虽然坑,却有着广泛的应用。其实还有其他优秀的JSON包供我们使用,例如阿里号称最快的JSON包——fastjson,还有谷歌的GSON,还有jackson。尽量,或者千万不要使用“net.sf.json”包,不仅有坑,而且已经很老了,老到都没法在IDEA里下载到源码,Maven仓库里显示它2010年在2.4版本就停止更新了。下面就谈我已知的“net.sf.json”的2个bug(我认为这是bug),以及这2个bug是如何产生的。
Java中的JSON坑包——net.sf.json
1. 在Java对象转换JSON对象时,get开头的所有方法会被转换
这是什么意思呢,例如现有以下Java对象。
package sfjson; import java.util.List; /**
* Created by Kevin on 2017/12/1.
*/
public class Student {
private int id;
private List<Long> courseIds; public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public List<Long> getCourseIds() {
return courseIds;
} public void setCourseIds(List<Long> courseIds) {
this.courseIds = courseIds;
} public String getSql() { //此类中获取sql语句的方法,并没有对应的属性字段
return "this is sql.";
}
}
在我们将Student对象转换成JSON对象的时候,希望转换后的JSON格式应该是:
{
"id": 1,
"courseIds": [1, 2, 3]
}
然而在使用“net.sf.json”包的JSONObject json = JSONObject.fromObject(student); API转换后的结果却是:

也就是说可以猜测到的是,“net.sf.json”获取Java对象中public修饰符get开头的方法,并将其后缀定义为JSON对象的“key”,而将get开头方法的返回值定义为对应key的“value”,注意是public修饰符get开头的方法,且有返回值。
我认为这是不合理的转换规则。如果我在Java对象中定义了一个方法,仅仅因为这个方法是“get”开头,且有返回值就将其作为转换后JSON对象的“key-value”,那岂不是暴露出来了?或者在返回给客户端(浏览器)时候就直接暴露给了前端的Console控制台?作者规定了这种转换规则,我想的大概原因是:既然你定义为了public方法,且命名为get,那就是有意将此方法暴露出来让调用它的客户端有权获取。但我仍然认为这不合理,甚至我定义它是一个bug。我这么定义也许也不合理,因为据我实测发现,不仅是“net.sf.json”包会按照这个规则进行转换,fastjson和jackson同样也是照此规则,唯独谷歌的GSON并没有按照这个规则进行对象向JSON转换。
通过JSONObject json = JSONObject.fromObject(student);将构造好的Student对象转换为JSON对象,Student如上文所述。 进入此方法后会继续调用fromObject(Object, JsonConfig)的重载方法,在此重载方法中会通过instanceOf判断待转换的Object对象是否是枚举、注解等类型,这些特殊类型会有特别的判断方法。在这里是一个普通的Java POJO对象,所以会进入到_fromObject(Object, JsonConfig),在这个方法中会有一些判断,而最后则通过调用defaultBeanProcessing创建JSON对象。这个方法是关键,在里面还继续会通过PropertyUtils.getPropertyDescriptors(bean)方法获取“属性描述符”,实际上就是获取带get的方法,它在这里封装成了PropertyDescriptor。这Student这个类中会获取4个,分别是:getClass、getId、getCourseIds、getSql。

其实PropertyDescriptor封装得已经很详细了,什么读写方法都已经赋值了。

例如这个getSql方法已经被解析成了上图的PropertyDescriptor。之后的通过这个类将一些方法过滤掉,例如getClass方法不是POJO中的方法,所以并不需要将它转换成JSON对象。而PropertyDescriptor的获取是通过BeanInfo#getPropertyDescriptors,而BeanInfo的获取则又是通过new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();不断深入最后就会到达如下方法。
private BeanInfo getBeanInfo() throws IntrospectionException {
…
MethodDescriptor mds[] = getTargetMethodInfo(); //这个方法中会调用getPublicDeclaredMethods,可以看到确实是查找public方法,而且是所有public方法,包括wait等
PropertyDescriptor pds[] = getTargetPropertyInfo(); //按照一定的规则进行过滤,过滤规则全在这个方法里了,就是选择public修饰符带有get前缀和返回值的方法
…
对net.sf.json的源码简要分析了一下,发现确实如猜想的那样,具体的源码比较多篇幅有限需自行查看跟踪。
2. 在JSON对象转换Java对象时,List<Long>会出现转换错误
标题一句话解释不清楚,这个问题,我很确定地认为它是一个bug。
现在有{"id": 1, "courseIds": [1,2,3]}的JSON字符串,需要将它转换为上文中提到的Student对象,在Student对象中有int和List<Long>类型的两个属性字段,也就是说这个JSON字符串应该转换为对应的数据类型。
String json = "{\"id\": 1, \"courseIds\": [1,2,3]}";
Student student = (Student) JSONObject.toBean(JSONObject.fromObject(json), Student.class);
System.out.println(student.getCourseIds().get(0) instanceof Long);
上面的输出结果应该是true,然而遗憾的是却是false。准确来说在编译时是Long型,而在运行时却是Integer。这不得不说就是一个坑了,另外三个JSON包都未出现这种错误。所以我确定它是一个bug。来看看这个bug在net.sf.json是怎么发生的,同样需要自行对比源码进行查看。我在打断点debug不断深入的时候发现了net.sf.json对于整型数据的处理时,发现了这个方法NumberUtils#createNumber,这个类是从字符串中取出数据时判断它的数据类型,本意是想如果数字后面带有“L”或“l”则将其处理为Long型,从这里来看最后的结果应该是对的啊。
case 'L':
case 'l':
if (dec == null && exp == null && (numeric.charAt(0) == '-' && isDigits(numeric.substring(1)) || isDigits(numeric))) {
try {
return createLong(numeric);
} catch (NumberFormatException var11) {
return createBigInteger(numeric);
}
} else {
throw new NumberFormatException(str + " is not a valid number.");
}
的确到目前为止net.sf.json通过数字后的标识符准确地判断了数据类型,问题出就出在获得了这个值以及它的数据类型后需要将它存入JSONObject中,而存入的过程中有JSONUtils#transformNumber这个方法的存在,这个方法的存在,至少在目前看来纯属画蛇添足。
public static Number transformNumber(Number input) {
if (input instanceof Float) {
return new Double(input.toString());
} else if (input instanceof Short) {
return new Integer(input.intValue());
} else if (input instanceof Byte) {
return new Integer(input.intValue());
} else {
if (input instanceof Long) {
Long max = new Long(2147483647L);
if (input.longValue() <= max.longValue() && input.longValue() >= -2147483648L) { //就算原类型是Long型,但是只要它在Integer范围,那么就最终还是会转换为Integer。
return new Integer(input.intValue());
}
}
return input;
}
}
上面的这段代码很清晰的显示了元凶所在,不论是Long型(Integer范围内的Long型),包括Byte、Short都会转换为Integer。尚不明白这段代码的意义在哪里。前面又要根据数字后的字母确定准确的数据类型,后面又要将准确的数据类型转换一次,这就导致了开头提到的那个bug。这个问题几乎是无法回避,所以最好的办法就是不要用。
这两个坑是偶然间发现,建议还是不要使用早已没有维护的net.sf.json的JSON包,另外有一点,net.sf.json包对JSON格式的校验并不那么严格,如果这样的格式“{"id": 1, "courseIds": "[1,2,3]"}”,在其他三个包是会抛出异常的,但net.sf.json则不会。
这是一个能给程序员加buff的公众号

Java中net.sf.json包关于JSON与对象互转的坑的更多相关文章
- Java中net.sf.json包关于JSON与对象互转的问题
在Web开发过程中离不开数据的交互,这就需要规定交互数据的相关格式,以便数据在客户端与服务器之间进行传递.数据的格式通常有2种:1.xml:2.JSON.通常来说都是使用JSON来传递数据.本文正是介 ...
- java中最常用jar包的用途说明
java中最常用jar包的用途说明,适合初学者 jar包 用途 axis.jar SOAP引擎包 commons-discovery-0.2.jar 用来发现.查找和实现可插入式接口,提供一些一般类实 ...
- java中,方法可以访问他的类对象的任何私有特性
java中,方法可以访问他的类对象的任何私有特性 读一本书(Core Java for the Impatient)时,发现这个注意,以前的时候没有在意,今天仔细想想发现记忆不深刻.记录一下 下面代码 ...
- java中Array/List/Map/Object与Json互相转换详解
http://blog.csdn.net/xiaomu709421487/article/details/51456705 JSON(JavaScript Object Notation): 是一种轻 ...
- java中Array/List/Map/Object与Json互相转换详解(转载)
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.易于人阅读和编写.同时也易于机器解析和生成.它基于JavaScript Programming Langu ...
- java中object数据怎么转换成json数据
可以通过这个(json-lib-2.3-jdk15.jar)jar里的方法转换 JSONObject json = JSONObject.fromObject(Object); 如果对象数组 JSON ...
- Java中常见的jar包及其主要用途
jar包 用途 axis.jar SOAP引擎包 commons-discovery-0.2.jar 用来发现.查找和实现可插入式接口,提供一些一般类实例化.单件的生命周 ...
- java中最常用jar包的用途
jar包用途 axis.jarSOAP引擎包commons-discovery-0.2.jar用来发现.查找和实现可插入式接口,提供一些一般类实例化.单件的生命周期管理的常用方法.jaxrpc.jar ...
- Java中反射机制和Class.forName、实例对象.class(属性)、实例对象getClass()的区别
一.Java的反射机制 每个Java程序执行前都必须经过编译.加载.连接.和初始化这几个阶段,后三个阶段如下图: 其中
随机推荐
- 关于css如何让图片文字居中的方法
在将父级转换为单元格形式时,设置的相关属性 可以达到如下效果:
- VSCode插件开发全攻略(六)开发调试技巧
更多文章请戳VSCode插件开发全攻略系列目录导航. 前言 在介绍完一些比较简单的内容点之后,我觉得有必要先和大家介绍一些开发中遇到的一些细节问题以及技巧,特别是后面一章节将要介绍WebView的知识 ...
- Windows下Tomcat内存占用过高问题跟踪(ProcessExplorer+jstack)
一.问题描述 Tomcat下面部署很多个java项目的war包,tomcat启动一段时间后,发现cpu占用过高,整个界面卡死! 二.通过process explorer查看java进程下的线程 pro ...
- 知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路
本文来自知乎官方技术团队的“知乎技术专栏”,感谢原作者陈鹏的无私分享. 1.引言 知乎存储平台团队基于开源Redis 组件打造的知乎 Redis 平台,经过不断的研发迭代,目前已经形成了一整套完整自动 ...
- C++ 基础知识回顾总结
一.前言 为啥要写这篇博客?答:之前学习的C和C++相关的知识,早就被自己忘到一边去了.但是,随着音视频的学习的不断深入,和C/C++打交道的次数越来越多,看代码是没问题的,但是真到自己操刀去写一些代 ...
- Kubenetes---Service--实践
1,编写创建svc的yaml文件 2, 创建service 3, 查看 4,查看svc代理那些pod , 当前没有 先创建deployment --> service 查看pod的label信 ...
- Ubuntu 16.04安装Notepadqq编辑器替代Notepad++
Notepad++只有Windows平台产品,对于Linux下有其替代的产品Notepadqq. 安装: sudo add-apt-repository ppa:notepadqq-team/note ...
- Scala - 快速学习05 - 数据结构
1- 数组(Array) 数组一般包括定长数组和变长数组. 可以不指明数组类型,Scala会自动根据提供的初始化数据来推断出数组的类型. 在Scala中,对数组元素的应用,是使用圆括号,而不是方括号. ...
- [CERC2014] Virus synthesis
设f[i]为形成极长回文串i的最小操作数.答案为min f[i]+n-len[i]. 在不形成偶回文的情况下形成奇回文的最小操作数为该串长度.可以不考虑(但ans赋为len). 正确性基于: 1)奇. ...
- java提高(6)---Serializable
Serializable--初解 一 序列化是干什么的? 我们知道,在jvm中引用数据类型存在于栈中,而new创建出的对象存在于堆中.如果电脑断电那么存在于内存中的对象就会丢失.那么有没有方法将对象保 ...