vlambda博客
学习文章列表

json,你真的足够了解吗?

json作为独立于语言的轻量级数据交换格式,在如今互联网开发中数据交互中有着非凡对意义。如果是基于Restful风格开发的程序,基本上都使用json进行端与端的数据通信。从我入行到今天,不知不觉已经和它打了将多年交道。可以说接触json的机会数不胜数。殊不知,原来它是我最熟悉的陌生人。

背景

先看一段代码

public class PushSink extends RichSinkFunction<Row> { private PushEmitter emitter;
@Override public void open(Configuration parameters) { emitter = new PushEmitter(); }
@Override public void close() {
}
/** * 每条数据的插入都要调用一次 invoke() 方法 * * @param value Kafka json消息 * @throws Exception 异常 */ @Override public void invoke(Row value, Context context) throws Exception { String gifts = value.getField(0).toString();
log.debug("row: {}, str: {}", value, gifts);
if(!JSON.isValid(gifts)) { return; }
if(JSON.isValidArray(gifts)){ emitter.push(JSON.parseObject(gifts, new TypeReference<List<Gift>>(){})); } else { Gift gift = JSON.parseObject(gifts, Gift.class); emitter.push(ImmutableList.of(gift)); } }
}

该段代码的业务背景是在使用FlinkKafkaConsumer对指定topic进行实时流消费,Kafka topic 中的数据为json字符串。这是一段简单的flink sink 对经过etl后的流数据进行真实处理业务处理前异常校验的代码,本来该段代码的本意是验证输入数据是否为标准的json数据,防止最终落库的时候写入脏数据。在对该段代码进行实现逻辑测试的时候,乍一看没有啥问题,写的也很简洁。但是由于平常很少用JSON.isValid()这个方法,所以上“一查你就GG的-百度”查了一下它的用法。这不查不知道,一查真的吓一跳。

‘坑’

对于JSON.isValid(gifts),是com.alibaba.fastjson.JSON类下的一个静态方法。网络上有很多人对其进行吐槽,甚至于被爆有严重bug

json,你真的足够了解吗?

“严重bug”

还有跟帖

json,你真的足够了解吗?

实际测试结果

json,你真的足够了解吗?

测试时候版本已经升级到1.2.70,也就是目前到最新版本。看起来阿里巴巴并没有及时修复该“严重BUG”。

我把此事及时告知了负责写该块业务逻辑的阿G同学,G同学的反应也是

json,你真的足够了解吗?

显然,如果有这种问题,那么在该业务场景下的校验可能失效。无法剥离原意中非标准json(jsonObject)的脏数据。那么可能会有部分数据例如:3.14,123456 等数值型的数据进入下游环节,导致最终落盘数据有误。

之后大概5分钟,我和阿G陷入了对fastjson的吐槽中。。。

原因

不过转过头来想,为啥结果会是这样的???

阿G是个实干派,说改就改。但是要怎么改?于是去翻阅了fastjson的源码。我们发现在1.2.70版本的JSONValidator抽象类中,有个内部枚举类Type。显示了json的三种结构类型

public static enum Type { Object, Array, Value;
private Type() { } }

等一下,Value是个什么玩意?Object和Array我们都知道,也是用的最经常的两种类型。甚至在此之前,可能很多人以为json就只有这两种类型(Object和Array)。为了弄清楚这个问题,我又查了json的官方 文档。文档中明确提及到json的Value数据类型json官方链接[1]

所以看起来也许不可思议。像3.14,123456这样的单个数值也是可以通过json校验的,是合法的json的格式。那么像之前测试代码中的字符串呢?文档中也告诉我们字符串是需要进行转义才可以通过校验。

A string is a sequence of zero or more Unicode characters, wrapped in double quotes, using backslash escapes. A character is represented as a single character string. A string is very much like a C or Java string.

所以修改后的测试代码是

System.out.println(JSON.isValid("123456"));System.out.println(JSON.isValid("\"hello json\""));

这样测试出来的结果才是正常的json

尾声

即便现在对json有了更为深入的了解,但是显然在此前的业务场景中,使用JSON.isValid()方法无法有效保证所有的非jsonObejct 数据被过滤。其中一种可能的解决方法是像阿G后面改成的这样

JSONValidator validator = JSONValidator.from(json); if (!validator.validate()) { return; }
if (validator.getType() == JSONValidator.Type.Array) { .... })); } else if (validator.getType() == JSONValidator.Type.Object) { .... }

具体的校验方法还是要根据实际的业务场景进行制定。在此次问题发现、解决的过程中,弥补了不少对json的知识点。但是对我而言,精神上的收获远比知识提升来得愉悦。在实际工作中,对于不熟悉的事物,一定要有刨根到底的研究精神,才能把未知的隐藏bug消灭于底层之中。这大概就是代码走读所带给我的最大的快乐。

References

[1] json官方链接: https://www.json.org/json-en.html