记一次fastjson转jackson的生产事故

之前暴露出fastjson存在漏洞,虽然后期fastjson团队对于相关漏洞有修复,但是为了确保服务和数据的安全性,公司还是决定所有项目弃用fastjson,改用jackson。因为之前项目很多地方都运用了fastjson,改起来算是一个大工程了,有些地方没注意可能就会导致出错,这不,在换了以后的确遇到了问题。
客户需要下单,具体逻辑我就不讲了,里面有一个json字符串转对象的操作,然后获取对象中的data属性用于后续操作。用过fastjson的伙伴都知道可以使用JSON.parseObject(String content)将json字符串转换为对象:

String jsonString="{"data":"testFastjson"}"
String body = String.valueOf(JSON.parseObject(jsonString).get("data"));

公司代码不便公开,在这里简要的进行了更改。通过程序调试我们可以看到,最终body的值为"testFastjson",为正确的值。
在这里插入图片描述
然后在使用jackson替换fastjson,需要实现同样的功能,我们使用了jackson提供的new ObjectMapper().readValue(String content, Class valueType)。因为对jackson的用法不是很清楚,然后就直接运用了,代码如下:

String jsonString="{"data":"testFastjson"}"
String body = String.valueOf(new ObjectMapper().readValue(jsonString, ObjectNode.class).get("data"))

同样debug以后查看转换后的body值,发现body的值为"“testFastjson”":
在这里插入图片描述
大家有没有发现有什么不一样?没错,jackson最后竟然的数据是带""的。what?除了问题当然是需要解决,不仅要解决,还要搞明白这里为什么会是这个样子。本着怀疑的态度,我debug进了jackson相关方法的readValue源码进行查看:

 public <T> T readValue(String content, Class<T> valueType)
        throws JsonProcessingException, JsonMappingException
    {
        _assertNotNull("content", content);
        return readValue(content, _typeFactory.constructType(valueType));
    } 

继续进去:

public <T> T readValue(String content, JavaType valueType)
        throws JsonProcessingException, JsonMappingException
    {
        _assertNotNull("content", content);
        try { // since 2.10 remove "impossible" IOException as per [databind#1675]
            return (T) _readMapAndClose(_jsonFactory.createParser(content), valueType);
        } catch (JsonProcessingException e) {
            throw e;
        } catch (IOException e) { // shouldn't really happen but being declared need to
            throw JsonMappingException.fromUnexpectedIOE(e);
        }
    } 

重点来了,里面调用的是_readMapAndClose(JsonParser p0, JavaType valueType)方法,我们继续进去;

protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
        throws IOException
    {
        try (JsonParser p = p0) {
            Object result;
            JsonToken t = _initForReading(p, valueType);
            final DeserializationConfig cfg = getDeserializationConfig();
            final DeserializationContext ctxt = createDeserializationContext(p, cfg);
            if (t == JsonToken.VALUE_NULL) {
                // Ask JsonDeserializer what 'null value' to use:
                result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt);
            } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
                result = null;
            } else {
                JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
                if (cfg.useRootWrapping()) {
                    result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
                } else {
                    //走这里
                    result = deser.deserialize(p, ctxt);
                }
                ctxt.checkUnresolvedObjectId();
            }
            if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
                _verifyNoTrailingTokens(p, ctxt, valueType);
            }
            return result;
        }
    }

我们看一下最后的返回结果:
在这里插入图片描述
发现返回的结果是一个LinkedHashMap,其中的key是data,value是"testFastJson":

key:"data"
value:""testFastJson""

到这里我们发现了,如果以ObjectMapper作为返回类的话,它的本质是最终转换成键值对的LinkedHashMap,且key是属性名称,值是属性对应的值但是加上了双引号,这就是问题的本质了,然后我们再依据这个key去获取值的话,那就肯定是带引号的值了:
在这里插入图片描述
所以不对,最终导致问题的出现。在这里记录一下以防大家跟我有同样问题的伙伴提个醒。如果大家想要获取正确的数据,如果已经知道需要转换的结构,大家可以先自定义个具体的类别,将ObjectMapper.class替换成对应的类别即可,如果不知道需要具体转换的类别,可以在转换完成之后,调用对应的asText()方法即可获得正确的值:

String jsonString="{"data":"testFastjson"}"
String body = new ObjectMapper().readValue(jsonString, ObjectNode.class).get("data").asText()

我么看一下改完之后获取的情况:
在这里插入图片描述
至此问题得到解决。虽然看起来很简单,但是在实际操作中还是需要注意。在这里虽然只是多了一个引号,但是如果不及时发现处理将会导致大量订单的失败,会带来很大的损失。记录此次的问题过程,给自己一个提醒,以后小问题也需要好好的对待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值