【线上踩坑分享】使用fastjson中JSONObject.parse方法遇到的问题

问题现象

线上有一段代码使用fastjson中的JSONObject.parse方法来完成数据格式化,刚上线时没有任何问题,但过了一段时间后发现该方法偶尔有JSON解析异常的现象发生,分析日志后发现问题原因大致如下示例所示:

java 复制代码
public class TestJson {
    public static void main(String[] args) {
        // 不报错
        Object parse = JSONObject.parse("12345678");
        System.out.println(parse + ", classType: " + parse.getClass());
        
        // 报错
        Object parse1 = JSONObject.parse("A12345678");
        System.out.println(parse1 + ", classType: " + parse1.getClass());
    }
}

输出结果如下:

java 复制代码
12345678, classType: class java.lang.Integer
Exception in thread "main" com.alibaba.fastjson.JSONException: syntax error, pos 1, json : A12345678
	at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1436)
	at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1322)
	at com.alibaba.fastjson.JSON.parse(JSON.java:152)
	at com.alibaba.fastjson.JSON.parse(JSON.java:162)
	at com.alibaba.fastjson.JSON.parse(JSON.java:131)
	at com.ykc.TestJson.main(TestJson.java:12)

Process finished with exit code 1

问题分析

其实我本以为上面两个方法是都应该报错的,因为正常理解JSONObject.parse方法应该只能解析Json格式的字符串才对。

当然,为了搞清楚原因,于是就查看了一下相关源码,发现原来解析时还兼容了一些其他类型的参数,以下是关键源码的处理逻辑。

java 复制代码
   public final void nextToken() {
        sp = 0;

        for (;;) {
            pos = bp;

            if (ch == '/') {
                skipComment();
                continue;
            }

            if (ch == '"') {
                scanString();
                return;
            }

            if (ch == ',') {
                next();
                token = COMMA;
                return;
            }
			
	    // 支持数值
            if (ch >= '0' && ch <= '9') {
                scanNumber();
                return;
            }

            // 这个是负号的意思。。。所以还支持负数
            if (ch == '-') {
                scanNumber();
                return;
            }

            switch (ch) {
                case '\'':
                    if (!isEnabled(Feature.AllowSingleQuotes)) {
                        throw new JSONException("Feature.AllowSingleQuotes is false");
                    }
                    scanStringSingleQuote();
                    return;
                case ' ':
                case '\t':
                case '\b':
                case '\f':
                case '\n':
                case '\r':
                    next();
                    break;
                // 还有一些true,false,null这样的
                case 't': // true
                    scanTrue();
                    return;
                case 'f': // false
                    scanFalse();
                    return;
                case 'n': // new,null
                    scanNullOrNew();
                    return;
                case 'T':
                case 'N': // NULL
                case 'S':
                case 'u': // undefined
                    scanIdent();
                    return;
                case '(':
                    next();
                    token = LPAREN;
                    return;
                case ')':
                    next();
                    token = RPAREN;
                    return;
                case '[':
                    next();
                    token = LBRACKET;
                    return;
                case ']':
                    next();
                    token = RBRACKET;
                    return;
                case '{':
                    next();
                    token = LBRACE;
                    return;
                case '}':
                    next();
                    token = RBRACE;
                    return;
                case ':':
                    next();
                    token = COLON;
                    return;
                case ';':
                    next();
                    token = SEMI;
                    return;
                case '.':
                    next();
                    token = DOT;
                    return;
                case '+':
                    next();
                    scanNumber();
                    return;
                case 'x':
                    scanHex();
                    return;
                default:
                    if (isEOF()) { // JLS
                        if (token == EOF) {
                            throw new JSONException("EOF error");
                        }

                        token = EOF;
                        pos = bp = eofPos;
                    } else {
                        if (ch <= 31 || ch == 127) {
                            next();
                            break;
                        }

                        lexError("illegal.char", String.valueOf((int) ch));
                        next();
                    }

                    return;
            }
        }

    }

根据源码处理逻辑,于是进行如下实验:

java 复制代码
public class TestJson {

    public static void main(String[] args) {

        Object parse1 = JSONObject.parse("true");
        System.out.println(parse1 + ", classType: " + parse1.getClass());

        Object parse2 = JSONObject.parse("1");
        System.out.println(parse2 + ", classType: " + parse2.getClass());

        Object parse3 = JSONObject.parse("-1");
        System.out.println(parse3 + ", classType: " + parse3.getClass());

        Object parse4 = JSONObject.parse("1.1");
        System.out.println(parse4 + ", classType: " + parse4.getClass());

        Object parse5 = JSONObject.parse("null");
        System.out.println(parse5);
    }
}
java 复制代码
true, classType: class java.lang.Boolean
1, classType: class java.lang.Integer
-1, classType: class java.lang.Integer
1.1, classType: class java.math.BigDecimal
null

Process finished with exit code 0

所以,实际上JSONObject.parse隐藏了不少数据类型的解析逻辑,要小心使用。

解决方式

这种问题根本原因在于协议约定方面,如果约定了数据交互格式为JSON,那就应该严格按照JSON格式来。

延伸思考

首先 ,找找自身的问题,我认为确实是使用方式有点非主流了,JSONObject.parse就应该只用来解析JSON格式,哪怕你已经查阅源码知道它也可以兼容其他格式,例如:"12345678",那也请不要这样做,千万不要在这种事情上炫技(显摆)

其次 ,可以吐槽一下JSONObject.parse这个方法,如此隐蔽的逻辑,在方法上居然找不到任何注释说明,这对于使用者来说非常不友好。

相关推荐
Hui Baby20 小时前
springAi+MCP三种
java
hsjcjh20 小时前
【MySQL】C# 连接MySQL
java
敖正炀20 小时前
LinkedBlockingDeque详解
java
wangyadong31720 小时前
datagrip 链接mysql 报错
java
untE EADO20 小时前
Tomcat的server.xml配置详解
xml·java·tomcat
ictI CABL20 小时前
Tomcat 乱码问题彻底解决
java·tomcat
敖正炀20 小时前
DelayQueue 详解
java
uzong20 小时前
最新:阿里正式发布首款AI开发工具Meoo(秒悟),0门槛、一键部署上线
人工智能·后端
用户83562907805120 小时前
Python 操作 PowerPoint:添加与设置文本框完整教程
后端·python
HuaidongLi20 小时前
三级缓存与循环依赖
后端