详谈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则不会。

以上这篇详谈Java中net.sf.json包关于JSON与对象互转的坑就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java线程之锁对象Lock-同步问题更完美的处理方式代码实例

    Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我们拿Java线程之线程同步synchronized和volatile详解中的一个例子简单的实现一下和sychronized一样的效果,代码如下: public class LockTest { public static void main(String[] args) { final Output

  • 浅谈java面向对象中四种权限

    俗话说没有规矩就没有方圆,java作为一门严谨的面向对象的高级编程语言,自然对权限整个重要的问题有严格的控制. Java中,可以通过一些Java关键字,来设置访问控制权限: 主要有 private(私有), package(包访问权限),protected(子类访问权限),public(公共访问权限) 在java里,这些语句都可以修饰类中的成员变量和方法,但是只有public和友好型可以修饰类.举个例子: 接下来就详细解释一下这几种权限的差别(博客最后有表格)按权限由低到高:(高权限有低权限所有

  • 将Java对象序列化成JSON和XML格式的实例

    1.先定义一个Java对象Person: public class Person { String name; int age; int number; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age =

  • java对象初始化代码详解

    本文主要记录JAVA中对象的初始化过程,包括实例变量的初始化和类变量的初始化以及final关键字对初始化的影响.另外,还讨论了由于继承原因,探讨了引用变量的编译时类型和运行时类型 一,实例变量的初始化 这里首先介绍下创建对象的过程: 类型为Dog的一个对象首次创建时,或者Dog类的static字段或static方法首次访问时,Java解释器必须找到Dog.class(在事先设定好的路径里面搜索):  找到Dog.class后(它会创建一个Class对象),它的所有static初始化模块都会运行.

  • Java编程构造方法与对象的创建详解

    java构造方法与对象的创建 可以用类来声明对象,声明对象后必须创建对象 1构造方法 首先,我们来谈谈什么叫构造方法,既然都说了这是一个构造方法,那么很显然,它本质上就是一个方法. 那么,既然作为一个方法,它应该有方法的样子吧.它除了回调一个Class();之后,也没见它有其他的定义方法的代码呀?这是因为,在未对类自定义构造方法的情况下,编译器会自动在编译期为其添加默认的构造方法 (1)程序用类创建对象时,需要使用该类的构造方法 (2)类中构造方法的名字必须和类名完全相同,而且没有类型 (3)允

  • Java编程实现对象克隆(复制)代码详解

    克隆,想必大家都有耳闻,世界上第一只克隆羊多莉就是利用细胞核移植技术将哺乳动物的成年体细胞培育出新个体,甚为神奇.其实在Java中也存在克隆的概念,即实现对象的复制. 本文将尝试介绍一些关于Java中的克隆和一些深入的问题,希望可以帮助大家更好地了解克隆. 假如说你想复制一个简单变量.很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适

  • Java和C++通过new创建的对象有何区别?

    前言 本文我们不去谈int.float.char等基本数据类型,而是用一般的类来说明.因为Java中可以直接通过 int varName 的方式来定义和使用一个基本类型的变量,但对于其它一般类型的对象,必须使用 new 来创建. 因此,为了更一般性地分析,体现两种语言创建对象的差异,我们用自定义的类 Student 进行说明,以下内容均针对一般的类而言. Java 在 Java 中,我们可以通过如下方式定义变量: Student s; //定义标识符s,没有实际空间 Student s = ne

  • 关于Java跨域Json字符转类对象的方法示例

    前言 JSON是JavaScript Object Notation的缩写,是一种轻量级的数据交换形式,是一种XML的替代方案,而且比XML更小,更快而且更易于解析.因为JSON描述对象的时候使用的是JavaScript语法,它是语言和平台独立的,并且这些年许多JSON的解析器和类库被开发出来. JSON具有以下这些形式: 对象是一个无序的"'名称/值'对"集合.一个对象以"{"(左括号)开始,"}"(右括号)结束.每个"名称"

  • 详谈Java中net.sf.json包关于JSON与对象互转的坑

    在Web开发过程中离不开数据的交互,这就需要规定交互数据的相关格式,以便数据在客户端与服务器之间进行传递.数据的格式通常有2种:1.xml:2.JSON.通常来说都是使用JSON来传递数据.本文正是介绍在Java中JSON与对象之间互相转换时遇到的几个问题以及相关的建议. 首先明确对于JSON有两个概念: JSON对象(JavaScript Object Notation,JavaScript对象表示法).这看似只存是位JavaScript所定制的,但它作为一种语法是独立于语言以及平台的.只是说

  • 详谈Java中的二进制及基本的位运算

    二进制是计算技术中广泛采用的一种数制.二进制数据是用0和1两个数码来表示的数.它的基数为2,进位规则是"逢二进一",借位规则是"借一当二",由18世纪德国数理哲学大师莱布尼兹发现.当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码的形式存储的.计算机中的二进制则是一个非常微小的开关,用"开"来表示1,"关"来表示0. 那么Java中的二进制又是怎么样的呢?让我们一起来揭开它神秘的面纱吧. 一.Java内置的进

  • 详谈Java中instanceof和isInstance的区别

    一次性搞定instanceof和isInstance,instanceof和isInstance长的非常像,用法也很类似,先看看这两个的用法: obj.instanceof(class) 也就是说这个对象是不是这种类型, 1. 一个对象是本身类的一个对象 2. 一个对象是本身类父类(父类的父类)和接口(接口的接口)的一个对象 3. 所有对象都是Object 4. 凡是null有关的都是false  null.instanceof(class) class.inInstance(obj) 这个对象

  • 详谈Java中的事件监听机制

    鼠标事件监听机制的三个方面: 1.事件源对象: 事件源对象就是能够产生动作的对象.在Java语言中所有的容器组件和元素组件都是事件监听中的事件源对象.Java中根据事件的动作来区分不同的事件源对象,动作发生在哪个组件上,那么该组件就是事件源对象 2.事件监听方法: addMouseListener(MouseListener ml) ;该方法主要用来捕获鼠标的释放,按下,点击,进入和离开的动作:捕获到相应的动作后,交由事件处理类(实现MouseListener接口)进行处理. addAction

  • 详谈java中File类getPath()、getAbsolutePath()、getCanonical的区别

    简单看一下描述,例子最重要. 1.getPath(): 返回定义时的路径,(就是你写什么路径,他就返回什么路径) 2.getAbsolutePath(): 返回绝对路径,但不会处理"."和".."的情况 3.getCanonicalPath(): 返回的是规范化的绝对路径,相当于将getAbsolutePath()中的"."和".."解析成对应的正确的路径 第一个例子:(使用:".\\src\\test.txt&qu

  • 详谈Java中的Object、T(泛型)、?区别

    因为最近重新看了泛型,又看了些反射,导致我对Object.T(以下代指泛型).?产生了疑惑. 我们先来试着理解一下Object类,学习Java的应该都知道Object是所有类的父类,注意:那么这就意味着它的范围非常广!首先记住这点,如果你的参数类型时Object,那么的参数类型将非常广! <Thinking in Java>中说很多原因促成了泛型的出现,最引人注目的一个原因就是为了创造容器类.这个要怎么来理解呢?我的理解是,可以抛开这个为了创造容器类这个,而是回到泛型的目的是限定某种类型上来.

  • 详谈Java中Object类中的方法以及finalize函数作用

    Object是所有类的父类,任何类都默认继承Object. 一.Object类中的方法 1.clone方法 保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常. 主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里讲参数改变,这是就需要在类中复写clone方法. 2.getClass方法 final方法,获得运行时类型. 3.toString方法 该方法

  • 详谈java中boolean和Boolean的区别

    boolean是基本数据类型 Boolean是它的封装类,和其他类一样,有属性有方法,可以new, 例如: Boolean flag = new Boolean("true"); // boolean 则不可以! Boolean 是boolean 的实例化对象类,和Integer对应int一样自jdk1.5.0以上版本后,Boolean在"赋值"和判断上和boolean一样, 即是你: boolean b1 = true ; 或者 Boolean b2 = true

  • 详谈Java中BigDecimal的一个除法异常

    如下所示: java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. 说明遇到除不尽的情况了,需要指定商的小数精度和舍入模式. 比如: a=b.divide(c,2,RoundingMode.HALF_UP); 以上这篇详谈Java中BigDecimal的一个除法异常就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

  • Java中快速把map转成json格式的方法

    在日常的使用中,我们一般会遇到map转json,如果遍历的话会浪费大量的时间,其实我们拥有这样的jar包 复制代码 代码如下: The method *** is undefined for the type JSONObject 缺哪个包------ json-lib.jar 这样还是不行的 需要一个依赖的jar包要不然会报错 复制代码 代码如下: java.lang.ClassNotFoundException: net.sf.ezmorph.Morpher 当当当当   jar包是ezmo

随机推荐