The pit of the net.sf.json package in java about the interchange between JSON and objects

 

 Reprinted from: https://www.cnblogs.com/yulinfeng/archive/2017/12/03/7967603.html

 

  In the process of Web development, data interaction is inseparable, which requires specifying the relevant format of interactive data so that data can be transmitted between the client and the server. There are usually two formats of data: 1. xml; 2. JSON. Usually JSON is used to pass data. This article introduces several problems and related suggestions encountered when converting between JSON and objects in Java. First of all, it is clear that there are two concepts for JSON:

  1. JSON object (JavaScript Object Notation, JavaScript Object Notation). This seems to be a bit of custom JavaScript, but as a syntax it is language and platform independent. It just means that usually when we pass data from the client (browser) to the server, the JSON format is used, and this format is used to represent JavaScript objects. It consists of a series of "key-value", such as {"id": 1, "name": "kevin"}, which is somewhat similar to the way Map key-value pairs are stored. The JSON object described in Java actually refers to the JSONObject class, which is usually named by this name in various third-party JSON jar packages, and different jar packages have slightly different internal implementations.

  2. JSON string. The conversion between JSON objects and JSON strings is the process of serialization and deserialization, which is like the serialization and deserialization of Java objects. The transmission of data in the network is carried out through strings, binary streams, etc., that is to say, when the client (browser) needs to transmit the data in JSON format, the string is transmitted in the network at this time. The server side is of course a string (String type) after receiving the data. Sometimes it is necessary to convert the JSON string to a JSON object and then do the next step (String type is converted to JSONObject type).

  The clarity of the above two concepts basically clarifies the data format of JSON, or JSON syntax. There are many jar packages for JSON in Java. The most "common" is the jar package provided by "net.sf.json". This article will focus on this pit package. Although it is pit, it has a wide range of applications. In fact, there are other excellent JSON packages for us to use, such as Ali's fastest JSON package - fastjson, Google's GSON, and jackson. Try your best, or never use the "net.sf.json" package, it not only has pits, but also is very old, so old that you can't download the source code in IDEA, the Maven repository shows that it stopped in version 2.4 in 2010 updated. Let's talk about the 2 bugs of "net.sf.json" that I know (I think they are bugs), and how these 2 bugs came about.

JSON pit package in Java - net.sf.json

1. When a Java object converts a JSON object, all methods starting with get will be converted

  What does this mean, for example the following Java objects exist.

copy code
 1 package sfjson;
 2 
 3 import java.util.List;
 4 
 5 /**
 6  * Created by Kevin on 2017/12/1.
 7  */
 8 public class Student {
 9     private int id;
10     private List<Long> courseIds;
11 
12     public int getId() {
13         return id;
14     }
15 
16     public void setId(int id) {
17         this.id =id;
 18      }
 19  
20      public List<Long> getCourseIds() {
 21          return courseIds;
 22      }
 23  
24      public  void setCourseIds(List<Long> courseIds) {
 25          this .courseIds = courseIds;
 26      }
 27  
28      public String getSql() {         // The method of obtaining sql statement in this class has no corresponding attribute field 
29          return "this is sql." ;
 30      }
 31 }
copy code

  When we convert the Student object into a JSON object, we hope that the converted JSON format should be:

1 {
2     "id": 1,
3     "courseIds": [1, 2, 3]
4 }

  然而在使用“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();不断深入最后就会到达如下方法。

copy code
private BeanInfo getBeanInfo() throws IntrospectionException {
    MethodDescriptor mds[] = getTargetMethodInfo();    //这个方法中会调用getPublicDeclaredMethods,可以看到确实是查找public方法,而且是所有public方法,包括wait等
    PropertyDescriptor pds[] = getTargetPropertyInfo();    //按照一定的规则进行过滤,过滤规则全在这个方法里了,就是选择public修饰符带有get前缀和返回值的方法
copy code

  对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型,从这里来看最后的结果应该是对的啊。

copy code
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.");
    }
copy code

  的确到目前为止net.sf.json通过数字后的标识符准确地判断了数据类型,问题出就出在获得了这个值以及它的数据类型后需要将它存入JSONObject中,而存入的过程中有JSONUtils#transformNumber这个方法的存在,这个方法的存在,至少在目前看来纯属画蛇添足。

copy code
 1 public static Number transformNumber(Number input) {
 2     if (input instanceof Float) {
 3         return new Double(input.toString());
 4     } else if (input instanceof Short) {
 5         return new Integer(input.intValue());
 6     } else if (input instanceof Byte) {
 7         return new Integer(input.intValue());
 8     } else {
 9         if (input instanceof Long) {
10             Long max = new Long(2147483647L);
11             if (input.longValue() <= max.longValue() && input.longValue() >= -2147483648L) {    //就算原类型是Long型,但是只要它在Integer范围,那么就最终还是会转换为Integer。
12                 return new Integer(input.intValue());
13             }
14         }
15 
16         return input;
17     }
18 }
copy code

  上面的这段代码很清晰的显示了元凶所在,不论是Long型(Integer范围内的Long型),包括Byte、Short都会转换为Integer。尚不明白这段代码的意义在哪里。前面又要根据数字后的字母确定准确的数据类型,后面又要将准确的数据类型转换一次,这就导致了开头提到的那个bug。这个问题几乎是无法回避,所以最好的办法就是不要用。

  These two pits were discovered by accident. It is recommended not to use the JSON package of net.sf.json that has not been maintained. Another point is that the verification of the JSON format by the net.sf.json package is not so strict. The format "{"id": 1, "courseIds": "[1,2,3]"}" will throw exceptions in the other three packages, but not in net.sf.json.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326105425&siteId=291194637