Fastjson反序列化随机性失败示例详解

目录
  • 前言
  • 问题代码
    • StewardTipItem
    • StewardTipCategory
    • StewardTip
    • JSON字符串
    • FastJSONTest
    • 堆栈信息
  • 问题排查
    • JavaBeanInfo:285行
    • JavaBeanInfo:492行
    • JavaBeanDeserializer:49行
    • JavaBeanDeserializer:838行
    • DefaultFieldDeserializer:53行
    • DefaultFieldDeserializer:34行
    • MapDeserializer:228行
    • JavaBeanDeserializer:838行
  • 问题解决
    • 代码
    • 调试
  • 总结
    • 开发过程中尽量遵照规范/规约,不要特立独行
    • 专业有深度
    • Fastjson

前言

本文主要讲述了一个具有"随机性"的反序列化错误!

Fastjson作为一款高性能的JSON序列化框架,使用场景众多,不过也存在一些潜在的bug和不足。本文主要讲述了一个具有"随机性"的反序列化错误!

问题代码

为了清晰地描述整个报错的来龙去脉,将相关代码贴出来,同时也为了可以本地执行,看一下实际效果。

StewardTipItem

package test;
import java.util.List;
public class StewardTipItem {
    private Integer type;
    private List<String> contents;
    public StewardTipItem(Integer type, List<String> contents) {
        this.type = type;
        this.contents = contents;
    }
}

StewardTipCategory

反序列化时失败,此类有两个特殊之处:

  • 返回StewardTipCategory的build方法(忽略返回null值)。
  • 构造函数『C1』Map<Integer, List> items参数与List items属性同名,但类型不同!
package test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class StewardTipCategory {
    private String category;
    private List<StewardTipItem> items;
    public StewardTipCategory build() {
        return null;
    }
    //C1 下文使用C1引用该构造函数
    public StewardTipCategory(String category, Map<Integer,List<String>> items) {
        List<StewardTipItem> categoryItems = new ArrayList<>();
    for (Map.Entry<Integer, List<String>> item : items.entrySet()) {
        StewardTipItem tipItem = new StewardTipItem(item.getKey(), item.getValue());                   categoryItems.add(tipItem);
    }
    this.items = categoryItems;
    this.category = category;
}
    // C2 下文使用C2引用该构造函数
    public StewardTipCategory(String category, List<StewardTipItem> items) {
        this.category = category;
        this.items = items;
    }
    public String getCategory() {
        return category;
    }
    public void setCategory(String category) {
        this.category = category;
    }
    public List<StewardTipItem> getItems() {
        return items;
    }
    public void setItems(List<StewardTipItem> items) {
        this.items = items;
    }
}

StewardTip

package test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class StewardTip {
    private List<StewardTipCategory> categories;
    public StewardTip(Map<String, Map<Integer, List<String>>> categories) {
        List<StewardTipCategory> tipCategories = new ArrayList<>();
        for (Map.Entry<String, Map<Integer, List<String>>> category : categories.entrySet()) {             StewardTipCategory tipCategory = new StewardTipCategory(category.getKey(), category.getValue());
            tipCategories.add(tipCategory);
        }
        this.categories = tipCategories;
    }
    public StewardTip(List<StewardTipCategory> categories) {
        this.categories = categories;
    }
    public List<StewardTipCategory> getCategories() {
        return categories;
    }
    public void setCategories(List<StewardTipCategory> categories) {
        this.categories = categories;
    }
}

JSON字符串

{
    "categories":[
        {
             "category":"工艺类",
             "items":[
                 {
                     "contents":[
                         "工艺类-提醒项-内容1",
                         "工艺类-提醒项-内容2"
                     ],
                     "type":1
                },
                {
                     "contents":[
                         "工艺类-疑问项-内容1"
                     ],
                     "type":2
                }
            ]
        }
    ]
}

FastJSONTest

package test;
import com.alibaba.fastjson.JSONObject;
public class FastJSONTest {
    public static void main(String[] args) {
        String tip = "{"categories":[{"category":"工艺类","items":[{"contents":["工艺类-提醒项-内容1","工艺类-提醒项-内容2"],"type":1},{"contents":["工艺类-疑问项-内容1"],"type":2}]}]}";
        try {
            JSONObject.parseObject(tip, StewardTip.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

堆栈信息

当执行FastJSONTest的main方法时报错:

com.alibaba.fastjson.JSONException: syntax error, expect {, actual [
    at com.alibaba.fastjson.parser.deserializer.MapDeserializer.parseMap(MapDeserializer.java:228)
    at com.alibaba.fastjson.parser.deserializer.MapDeserializer.deserialze(MapDeserializer.java:67)
    at com.alibaba.fastjson.parser.deserializer.MapDeserializer.deserialze(MapDeserializer.java:43)
    at com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer.parseField(DefaultFieldDeserializer.java:85)
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:838)
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:288)
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:284)
    at com.alibaba.fastjson.parser.deserializer.ArrayListTypeFieldDeserializer.parseArray(ArrayListTypeFieldDeserializer.java:181)
    at com.alibaba.fastjson.parser.deserializer.ArrayListTypeFieldDeserializer.parseField(ArrayListTypeFieldDeserializer.java:69)
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:838)
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:288)
    at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:672)  at com.alibaba.fastjson.JSON.parseObject(JSON.java:396)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:300)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:573)
    at test.FastJSONTest.main(FastJSONTest.java:17)

问题排查

排查过程有两个难点:

  • 不能根据报错信息得到异常时JSON字符串的key,position或者其他有价值的提示信息。
  • 报错并不是每次执行都会发生,存在随机性,执行十次可能报错两三次,没有统计失败率。

经过多次执行之后还是找到了一些蛛丝马迹!下面结合源码对整个过程进行简单地叙述,最后也会给出怎么能在报错的时候debug到代码的方法。

JavaBeanInfo:285行

clazz是StewardTipCategory.class的情况下,提出以下两个问题:

Q1:Constructor[] constructors数组的返回值是什么?

Q2:constructors数组元素的顺序是什么?

参考java.lang.Class#getDeclaredConstructors的注释,可得到A1:

  • A1

public test.StewardTipCategory(java.lang.String,java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>)『C1』

public test.StewardTipCategory(java.lang.String,java.util.List<test.StewardTipItem>)『C2』

  • A2

build()方法,C1构造函数,C2构造函数三者在Java源文件的顺序决定了constructors数组元素的顺序!

下表是经过多次实验得到的一组数据,因为是手动触发,并且次数较少,所以不能保证100%的准确性,只是一种大概率事件。

java.lang.Class#getDeclaredConstructors底层实现是native getDeclaredConstructors0,JVM的这部分代码没有去阅读,所以目前无法解释产生这种现象的原因。

数组元素顺序
build() C1 C2 随机
C1 build() C2 C2,C1
C1 C2 build() C2,C1
build() C2 C1 随机
C2 build() C1 C1,C2
C2 C1 build() C1,C2
C1   C2 C2,C1
C2 C1 C1,C2  

正是因为java.lang.Class#getDeclaredConstructors返回数组元素顺序的随机性,才导致反序列化失败的随机性!

  • [C2,C1]反序列化成功!
  • [C1,C2]反序列化失败!

[C1,C2]顺序下探寻反序列化失败时代码执行的路径。

JavaBeanInfo:492行

com.alibaba.fastjson.util.JavaBeanInfo#build()方法体代码量比较大,忽略执行路径上的无关代码。\

  • [C1,C2]顺序下代码会执行到492行,并执行两次(StewardTipCategory#category, StewardTipCategory#items各执行一次)。
  • 结束后创建一个com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer。

JavaBeanDeserializer:49行

JavaBeanDeserializer两个重要属性:

private final FieldDeserializer[]   fieldDeserializers;

protected final FieldDeserializer[] sortedFieldDeserializers;

反序列化test.StewardTipCategory#items时fieldDeserializers的详细信息。

com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializercom.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer#fieldValueDeserilizer(属性值null,运行时会根据fieldType获取具体实现类)com.alibaba.fastjson.util.FieldInfo#fieldType(java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>)

创建完成执行

com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze(com.alibaba.fastjson.parser.DefaultJSONParser, java.lang.reflect.Type, java.lang.Object, java.lang.Object, int, int[])

JavaBeanDeserializer:838行

DefaultFieldDeserializer:53行

com.alibaba.fastjson.parser.ParserConfig#getDeserializer(java.lang.Class<?>, java.lang.reflect.Type)根据字段类型设置

com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer#fieldValueDeserilizer的具体实现类。

DefaultFieldDeserializer:34行

test.StewardTipCategory#items属性的实际类型是List。

反序列化时根据C1构造函数得到的fieldValueDeserilizer的实现类是com.alibaba.fastjson.parser.deserializer.MapDeserializer。

执行

com.alibaba.fastjson.parser.deserializer.MapDeserializer#deserialze(com.alibaba.fastjson.parser.DefaultJSONParser, java.lang.reflect.Type, java.lang.Object)时报错。

MapDeserializer:228行

JavaBeanDeserializer:838行

java.lang.Class#getDeclaredConstructors返回[C2,C1]顺序,反序列化时根据C2构造函数得到的fieldValueDeserilizer的实现类是

com.alibaba.fastjson.parser.deserializer.ArrayListTypeFieldDeserializer,反序列化成功。

问题解决

代码

  • 删除C1构造函数,使用其他方式创建StewardTipCategory。
  • 修改C1构造函数参数名称,类型,避免误导Fastjson。

调试

package test;
import com.alibaba.fastjson.JSONObject;
import java.lang.reflect.Constructor;
public class FastJSONTest {
    public static void main(String[] args) {
        Constructor<?>[] declaredConstructors = StewardTipCategory.class.getDeclaredConstructors();
        // if true must fail!
       if ("public test.StewardTipCategory(java.lang.String,java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>)".equals(declaredConstructors[0].toGenericString())) {
           String tip = "{"categories":[{"category":"工艺类","items":[{"contents":["工艺类-提醒项-内容1","工艺类-提醒项-内容2"],"type":1},{"contents":["工艺类-疑问项-内容1"],"type":2}]}]}";
           try {
                JSONObject.parseObject(tip, StewardTip.class);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

总结

开发过程中尽量遵照规范/规约,不要特立独行

StewardTipCategory构造函数C1方法签名明显不是一个很好的选择,方法体除了属性赋值,还做了一些额外的类型/数据转换,也应该尽量避免。

专业有深度

开发人员对于使用的技术与框架要有深入的研究,尤其是底层原理,不能停留在使用层面。一些不起眼的事情可能导致不可思议的问题:java.lang.Class#getDeclaredConstructors。

Fastjson

框架实现时要保持严谨,报错信息尽可能清晰明了,StewardTipCategory反序列化失败的原因在于,fastjson只检验了属性名称,构造函数参数个数而没有进一步校验属性类型。

<<重构:改善既有代码的设计>>提倡代码方法块尽量短小精悍,Fastjson某些模块的方法过于臃肿。

以上就是Fastjson反序列化随机性失败示例详解的详细内容,更多关于Fastjson反序列化随机性的资料请关注我们其它相关文章!

(0)

相关推荐

  • SpringBoot Redis配置Fastjson进行序列化和反序列化实现

    FastJson是阿里开源的一个高性能的JSON框架,FastJson数据处理速度快,无论序列化(把JavaBean对象转化成Json格式的字符串)和反序列化(把JSON格式的字符串转化为Java Bean对象),都是当之无愧的fast:功能强大(支持普通JDK类,包括javaBean, Collection, Date 或者enum):零依赖(没有依赖其他的任何类库). 1.写一个自定义序列化类 /** * 自定义序列化类 * @param <T> */ public class FastJ

  • java中fastjson生成和解析json数据(序列化和反序列化数据)

    本文讲解2点: 1. fastjson生成和解析json数据 (举例:4种常用类型:JavaBean,List<JavaBean>,List<String>,List<Map<String,Object>) 2.通过一个android程序测试fastjson的用法. fastjson简介: Fastjson是一个Java语言编写的高性能功能完善的JSON库.fastjson采用独创的算法,将parse的速度提升到极致,超过所有json库,包括曾经号称最快的jack

  • FastJson踩坑:@JsonField在反序列化时失效的解决

    问题描述 一个对象(某个字段为枚举类型,为了不采用默认的序列化过程,用@JSONField指定了序列化器和反序列器,过程见旧博文),将其放到JSONArray中再序列化JSONArray对象,用得到的JSON字符串再反序列化时,发现能够正常反序列化出JSONArray,而对JSONArray中的某个元素再反序列化成类对象时,出错. 示例 同样用旧博文的示例做个简单测试. 基本对象类Article. public class Article { private String title; priv

  • java安全fastjson1.2.24反序列化TemplatesImpl分析

    目录 1. fastjson序列化 2. fastjson反序列化 3. fastjson反序列化漏洞原理 4. fastjson1.2.24漏洞复现 5. fastjson1.2.24漏洞分析 前言 漏洞环境: fastjson1.2.24 jdk1.7.80 新建一个maven项目在pom.xml文件中引入fastjson的依赖: <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjs

  • springmvc fastjson 反序列化时间格式化方法(推荐)

    第一种情况是从后台拿到数据,进行反序列化,反序列化格式时间:试了一下很多网上的方法,最后发现还是在实体类上面的日期字段加上如下注解,可以完成格式化操作,否则默认就都是时间戳的格式: @JSONField (format="yyyy-MM-dd HH:mm:ss")  public Date birthday; @JSONField (format="yyyy-MM-dd HH:mm:ss")  public Date birthday; 第二种情况是:respons

  • Fastjson反序列化随机性失败示例详解

    目录 前言 问题代码 StewardTipItem StewardTipCategory StewardTip JSON字符串 FastJSONTest 堆栈信息 问题排查 JavaBeanInfo:285行 JavaBeanInfo:492行 JavaBeanDeserializer:49行 JavaBeanDeserializer:838行 DefaultFieldDeserializer:53行 DefaultFieldDeserializer:34行 MapDeserializer:22

  • 泛型的类型擦除后fastjson反序列化时如何还原详解

    目录 铺垫 错误写法1 错误写法2 正确写法 TypeReference 验证 扩展 铺垫 在前面的文章中,我们讲过Java中泛型的类型擦除,不过有小伙伴在后台留言提出了一个问题,带有泛型的实体的反序列化过程是如何实现的,今天我们就来看看这个问题. 我们选择fastjson来进行反序列化的测试,在测试前先定义一个实体类: @Data public class Foo<T> { private String val; private T obj; } 如果大家对泛型的类型擦除比较熟悉的话,就会知

  • Go语言基础Json序列化反序列化及文件读写示例详解

    目录 概述 JSON序列化 结构体转JSON map转JSON 切片转JSON JSON反序列化 JSON转map JSON转结构体 JSON转切片 写JSON文件 map写入JSON文件 切片写入JSON文件 结构体写入JSON文件 读JSON文件 解码JSON文件为map 解码JSON文件为切片 解码JSON文件为结构体 示例 概述 JSON(JavaScript Object Notation,JavaScript对象表示法)是一种轻量级的.键值对的数据交换格式.结构由大括号'{}',中括

  • Swift 中的 JSON 反序列化示例详解

    目录 业界常用的几种方案 手动解码方案,如 Unbox(DEPRECATED) 阿里开源的 HandyJSON 基于 Sourcery 的元编程方案 Swift build-in API Codable 属性装饰器,如 BetterCodable 各个方案优缺点对比 Codable 介绍 原理浅析 Decoder.Container 协议 自研方案 功能设计 Decoder.Container 具体实现 再议 PropertyWrapper 应用场景示例 单元测试 性能对比 业界常用的几种方案

  • fastjson序列化时间自定义格式示例详解

    目录 Java8 的日期相关 API 首先建一个项目添加依赖 配置类中注入 Spriing 容器 写个接口做下测试 Java8 的日期相关 API Java8 的日期相关 API用起来是真香,但免不了遇到在用旧版 1.0 API 的情况.这不,跟另一个部门做对接,人家说你发过来的时间怎么带个 T,我这边没法解析...我回头就是一句xxx,情绪发泄完该做的事咱也得做不是,下面就看看怎么处理这个问题. 首先建一个项目添加依赖 <dependencies> <dependency> &l

  • Java HttpClient用法的示例详解

    目录 1.导入依赖 2.使用工具类 3.扩展 1.导入依赖 <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.3</version> </dependency> <dependency> <groupId>com.alibab

  • bat批处理 if 命令示例详解

    if 命令示例详解 if,正如它E文中的意思,就是"如果"的意思,用来进行条件判断.翻译过来的意思就是:如果符合某一条件,便执行后面的命令. 主要用来判断,1.两个"字符串"是否相等:2.两个数值是大于.小于.等于,然后执行相应的命令. 当然还有特殊用法,如结合errorlevel:if errorlevel 1 echo error 或者结合defined(定义的意思):if defined test (echo It is defined) else echo 

  • JavaScript中自带的 reduce()方法使用示例详解

    1.方法说明 , Array的reduce()把一个函数作用在这个Array的[x1, x2, x3...]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果就是: [x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4) 2. 使用示例 'use strict'; function string2int(s){ if(!s){ alert('the params empty'); return; } if

  • .NetCore实现上传多文件的示例详解

    本章和大家分享的是.NetCore的MVC框架上传文件的示例,主要讲的内容有:form方式提交上传,ajax上传,ajax提交+上传进度效果,Task并行处理+ajax提交+上传进度,相信当你读完文章内容后能后好的收获,如果可以不妨点个赞:由于昨天电脑没电了,快要写完的内容没有保存,今天早上提前来公司从头开始重新,断电这情况的确让人很头痛啊,不过为了社区的分享环境,这也是值得的,不多说了来进入今天的正篇环节吧: form方式上传一组图片 先来看看咋们html的代码,这里先简单说下要上传文件必须要

  • Vue中的vue-resource示例详解

    vue-resource特点 vue-resource插件具有以下特点: 1. 体积小 vue-resource非常小巧,在压缩以后只有大约12KB,服务端启用gzip压缩后只有4.5KB大小,这远比jQuery的体积要小得多. 2. 支持主流的浏览器 和Vue.js一样,vue-resource除了不支持IE 9以下的浏览器,其他主流的浏览器都支持. 3. 支持Promise API和URI Templates Promise是ES6的特性,Promise的中文含义为"先知",Pro

随机推荐