json-lib包的坑
1.问题场景
在项目中json字符串转换时,一些属性为null的属性转换时报错。
2.问题原因
json字符串转换时,对值是否为null进行判断时,用的是JsonNULL对象的equals方法
json-lib的最新版本2.4都存在该问题。
3.代码分析
3.1JSONObject对象
net.sf.json.JSONObject实际上实现了Map接口:
map实际上为hashmap
首先json字符串会被解析转换。
传参json字符串和对应的解析规则给本身的_fromJSONTokener方法
3.2_fromJSONTokener方法源码
private static JSONObject _fromJSONTokener(JSONTokener tokener, JsonConfig jsonConfig) {
try {
if (tokener.matches("null.*")) {
fireObjectStartEvent(jsonConfig);
fireObjectEndEvent(jsonConfig);
return new JSONObject(true);
} else if (tokener.nextClean() != '{') {
throw tokener.syntaxError("A JSONObject text must begin with '{'");
} else {
fireObjectStartEvent(jsonConfig);
Collection exclusions = jsonConfig.getMergedExcludes();
PropertyFilter jsonPropertyFilter = jsonConfig.getJsonPropertyFilter();
JSONObject jsonObject = new JSONObject();
while(true) {
while(true) {
char c = tokener.nextClean();
switch(c) {
case '\u0000':
throw tokener.syntaxError("A JSONObject text must end with '}'");
case '}':
fireObjectEndEvent(jsonConfig);
return jsonObject;
default:
tokener.back();
String key = tokener.nextValue(jsonConfig).toString();
c = tokener.nextClean();
if (c == '=') {
if (tokener.next() != '>') {
tokener.back();
}
} else if (c != ':') {
throw tokener.syntaxError("Expected a ':' after a key");
}
char peek = tokener.peek();
boolean quoted = peek == '"' || peek == '\'';
Object v = tokener.nextValue(jsonConfig);
if (!quoted && JSONUtils.isFunctionHeader(v)) {
String params = JSONUtils.getFunctionParams((String)v);
int i = 0;
StringBuffer sb = new StringBuffer();
do {
char ch = tokener.next();
if (ch == 0) {
break;
}
if (ch == '{') {
++i;
}
if (ch == '}') {
--i;
}
sb.append(ch);
} while(i != 0);
if (i != 0) {
throw tokener.syntaxError("Unbalanced '{' or '}' on prop: " + v);
}
String text = sb.toString();
text = text.substring(1, text.length() - 1).trim();
Object value = new JSONFunction(params != null ? StringUtils.split(params, ",") : null, text);
if (jsonPropertyFilter == null || !jsonPropertyFilter.apply(tokener, key, value)) {
if (jsonObject.properties.containsKey(key)) {
jsonObject.accumulate(key, value, jsonConfig);
firePropertySetEvent(key, value, true, jsonConfig);
} else {
jsonObject.element(key, (Object)value, jsonConfig);
firePropertySetEvent(key, value, false, jsonConfig);
}
}
} else {
if (exclusions.contains(key)) {
switch(tokener.nextClean()) {
case ',':
case ';':
if (tokener.nextClean() == '}') {
fireObjectEndEvent(jsonConfig);
return jsonObject;
}
tokener.back();
continue;
case '}':
fireObjectEndEvent(jsonConfig);
return jsonObject;
default:
throw tokener.syntaxError("Expected a ',' or '}'");
}
}
if (jsonPropertyFilter == null || !jsonPropertyFilter.apply(tokener, key, v)) {
if (quoted && v instanceof String && (JSONUtils.mayBeJSON((String)v) || JSONUtils.isFunction(v))) {
v = "\"" + v + "\"";
}
if (jsonObject.properties.containsKey(key)) {
jsonObject.accumulate(key, v, jsonConfig);
firePropertySetEvent(key, v, true, jsonConfig);
} else {
jsonObject.element(key, v, jsonConfig);
firePropertySetEvent(key, v, false, jsonConfig);
}
}
}
switch(tokener.nextClean()) {
case ',':
case ';':
if (tokener.nextClean() == '}') {
fireObjectEndEvent(jsonConfig);
return jsonObject;
}
tokener.back();
break;
case '}':
fireObjectEndEvent(jsonConfig);
return jsonObject;
default:
throw tokener.syntaxError("Expected a ',' or '}'");
}
}
}
}
}
} catch (JSONException var15) {
fireErrorEvent(var15, jsonConfig);
throw var15;
}
}
3.3_fromJSONTokener方法源码解读
大致为,先判断是否为空或者没有{}大括号等等。然后使用wihle(true){}开始遍历json字符串,JSONTokener的nextClean()方法为取json字符串中的每个字符,其中逻辑大概为遇到/就跳过再执行,遇到非法字符或不符合规范就抛出错误,直到返回一个合法字符。后续就是一些把json字符串根据key-value存进map中,也就是对象本身的properties。
关键是这条语句:Object v = tokener.nextValue(jsonConfig);
进入JSONTokener的newValue方法可以看到,当值为null时,取出来的对象类型就为JSONNull了
JSONNull.getInstance()返回的就是一个JSONNull对象
4.结论
非”null”字符串放到JSONObject类中时,取出来使用时是java.lang.String类型
“null”字符串放到JSONObject类中时,取出来的使用会转换成net.sf.json.JSONNull类型:
5.举个例子
import net.sf.json.JSONObject;
public class JsonTest {
public static void main(String[] args) {
String jsonStr = "{\"test\":\"\",\"test1\":null,\"test2\":1}";
JSONObject jsonObject = JSONObject.fromObject(jsonStr);
String test1 = jsonObject.get("test1") == null ? "" : (String) jsonObject.get("test1");
System.out.println(test1);
}
}
按照现在常用的json转换工具,都会自动帮忙转成""空字符串
模拟该过程,断点到jsonObject.get(“test1”) == null这行时,就会报错。因为取出来的数值类型为net.sf.json.JSONNull并不等于null,但是转成string时就不支持。
原因很简单,因为test1的对象类型为JSONNull,那么在 == null ? 这步时调用的是JSONNull的equals方法。那我们看看JSONNull的equals方法。
public boolean equals(Object object) {
return object == null || object == this || object == instance || object instanceof JSONObject && ((JSONObject)object).isNullObject() || "null".equals(object);
}
很明显,JSONNull对象不等于null,所以返回值为false,所以就在后面强转String的代码会抛出错误。
6.解决方法
1.将json字符串中的null改成传""即可。
2.使用别的json转换包