记一次JSON.toJSONString()转换时非属性方法空指针异常排查及toJSONString保留null值属性
异常详情
有一个类,里面有两个属性和一个类似工具的getRealName()方法如下:
getRealName()方法就是获取这个人的真实名字,如果获取不到就以name返回
class JSONTest {
String name;
String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getRealName() {
try {
return "real" + this.name.substring(0, 1);
} catch (Exception e) {
e.printStackTrace();
}
return this.name;
}
}
然后Controller中有两个url使用了这个类,并转成JSONString返回。下面就以两个方法模仿这两个url。
public class JsonMainTest {
public static void main(String[] args) throws Exception {
System.out.println(method01());
System.out.println(method02());
}
static String method01() {
JSONTest jsonTest = new JSONTest();
jsonTest.setName("lowkey");
jsonTest.setAge("18");
jsonTest.setName(jsonTest.getRealName());
return JSON.toJSONString(jsonTest);
}
static String method02() {
JSONTest jsonTest = new JSONTest();
jsonTest.setAge("18");
return JSON.toJSONString(jsonTest);
}
}
接下来运行这个main方法,可以看到第二个方法报错了
异常原因
一开始很好奇为什么会异常,因为空指针,name为空了,但是我method02方法并没有调用getRealName(),为什么会调用了它,并空指针了呢。
后面细究发现是因为JSON.toJSONString()的时候是根据getter方法进行的,也就是下面这行
它会把所有符合以get开头的方法拿出来然后把它转成属性进行设置,所有他会在转method02方法的时候调用了getRealName()方法,而method02方法中name并没有设置值,所有才出现了空指针异常。在过程中还发现它会扫描以is开头的方法。
从打印的JSONString串也可以看出,我的属性里面并没有realName属性它却打印了出来。
解决方案
-
第一种
就是规范命名,与类属性无关的方法不要以get/is开头,向我这个类里面getRealName只是将name进行了处理,并不是作为一个类属性使用,所以我们将该方法改成handleRealName()或者其他即可。如下所示:再次运行便正常了,打印中也没有除属性外的字段。
public class JsonMainTest { public static void main(String[] args) throws Exception { System.out.println(method01()); System.out.println(method02()); } static String method01() { JSONTest jsonTest = new JSONTest(); jsonTest.setName("lowkey"); jsonTest.setAge("18"); jsonTest.setName(jsonTest.handletRealName()); return JSON.toJSONString(jsonTest); } static String method02() { JSONTest jsonTest = new JSONTest(); jsonTest.setAge("18"); return JSON.toJSONString(jsonTest); } } class JSONTest { String name; String age; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public String handletRealName() { try { return "real" + this.name.substring(0, 1); } catch (Exception e) { e.printStackTrace(); } return this.name; } }
-
第二种
忽略JSON转换,即在进行JSON.toJSONString的时候忽略getRealName方法。在方法上添加@JSONField(serialize = false)注解,如下所示:
public class JsonMainTest { public static void main(String[] args) throws Exception { System.out.println(method01()); System.out.println(method02()); } static String method01() { JSONTest jsonTest = new JSONTest(); jsonTest.setName("lowkey"); jsonTest.setAge("18"); jsonTest.setName(jsonTest.getRealName()); return JSON.toJSONString(jsonTest); } static String method02() { JSONTest jsonTest = new JSONTest(); jsonTest.setAge("18"); return JSON.toJSONString(jsonTest); } } class JSONTest { String name; String age; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } @JSONField(serialize = false) public String getRealName() { try { return "real" + this.name.substring(0, 1); } catch (Exception e) { e.printStackTrace(); } return this.name; } }
-
不过建议还是规范命名
其他问题:空属性不打印的情况
我们还会发现为空的属性,toJSONStirng的时候不打印。我们可以使用时添加SerializerFeature.WriteMapNullValue属性:
JSON.toJSONString(jsonTest, SerializerFeature.WriteMapNullValue);