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

相关推荐
行百里er18 小时前
2026:一名码农的“不靠谱”年度规划
后端·程序员·架构
全靠bug跑20 小时前
Spring Cache 实战:核心注解详解与缓存过期时间配置
java·redis·springcache
聆风吟º20 小时前
【数据结构手札】空间复杂度详解:概念 | 习题
java·数据结构·算法
计算机程序设计小李同学20 小时前
基于SpringBoot的个性化穿搭推荐及交流平台
java·spring boot·后端
是一个Bug20 小时前
50道核心JVM面试题
java·开发语言·面试
用户479492835691520 小时前
同事一个比喻,让我搞懂了Docker和k8s的核心概念
前端·后端
朱朱没烦恼yeye20 小时前
java基础学习
java·python·学习
她和夏天一样热21 小时前
【观后感】Java线程池实现原理及其在美团业务中的实践
java·开发语言·jvm
郑州光合科技余经理21 小时前
技术架构:上门服务APP海外版源码部署
java·大数据·开发语言·前端·架构·uni-app·php
篱笆院的狗21 小时前
Java 中的 DelayQueue 和 ScheduledThreadPool 有什么区别?
java·开发语言