【线上踩坑分享】使用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这个方法,如此隐蔽的逻辑,在方法上居然找不到任何注释说明,这对于使用者来说非常不友好。

相关推荐
苹果酱05672 分钟前
「Mysql优化大师一」mysql服务性能剖析工具
java·vue.js·spring boot·mysql·课程设计
_oP_i1 小时前
Pinpoint 是一个开源的分布式追踪系统
java·分布式·开源
mmsx1 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库
武子康1 小时前
大数据-258 离线数仓 - Griffin架构 配置安装 Livy 架构设计 解压配置 Hadoop Hive
java·大数据·数据仓库·hive·hadoop·架构
豪宇刘2 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意3 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
刘大辉在路上3 小时前
突发!!!GitLab停止为中国大陆、港澳地区提供服务,60天内需迁移账号否则将被删除
git·后端·gitlab·版本管理·源代码管理
FF在路上3 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进3 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人4 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言