fastjson使用

发布时间 2023-03-23 15:47:34作者: 顔宸

fastjson使用

0、遇到的问题:

1、基本API和配置

1.0 准备POJO

User和IdCard。可以参见代码部分

准备测试数据:

@Before
public void initObjectAndList(){
    //设置user对象的值
    user = new User();
    user.setId(1);
    user.setUsername("小鲁");

    Calendar calendar = Calendar.getInstance();
    calendar.set(1990,11,20);
    Date birthday = calendar.getTime();
    IdCard idCard = new IdCard("1999-05-10","1",birthday);
    user.getIdCards().add(idCard);

    calendar.set(1995,0,1);
    Date birthday2 = calendar.getTime();
    IdCard idCard2 = new IdCard("2000-10-10","0",birthday2);
    user.getIdCards().add(idCard2);

    //设置users集合的值
    users.add(user);
}

@Before
public void initString(){
    userStr = "{\"ids\":1,\"id\":1,\"idCards\":[{\"birthday\":\"1995-01-01\",\"cardNo\":\"2000134102030010\",\"sex\":\"1\"},{\"birthday\":788929283273,\"cardNo\":\"2000134102030020\",\"sex\":\"0\"}],\"username\":\"小鲁\"}";
    usersStr = "[{\"id\":1,\"idCards\":[{\"birthday\":\"1990-01-01\",\"cardNo\":\"2000134102030010\",\"sex\":\"1\"},{\"birthday\":\"2002-11-11\",\"cardNo\":\"2000134102030020\",\"sex\":\"0\"}],\"username\":\"小鲁\"}]";
}

1.1 序列化

  • 对象/Map -- 字符串
@Test
public void toJsonString(){
    String userString = JSON.toJSONString(user);
    System.out.println(userString);
    String usersString = JSON.toJSONString(users);
    System.out.println(usersString);
}
{
  "id":1,
  "idCards":[
    {
      "birthday":661654769839,
      "cardNo":"2020001",
      "sex":"1"
    },
    {
      "birthday":788921969839,
      "cardNo":"2020002",
      "sex":"0"
    }
  ],
  "username":"小y"
}
[
  {
    "id":1,
    "idCards":[
      {
        "birthday":661654769839,
        "cardNo":"2020001",
        "sex":"1"
      },
      {
        "birthday":788921969839,
        "cardNo":"2020002",
        "sex":"0"
      }
    ],
    "username":"小y"
  }
]
  • 序列化到OutputStream
/**
     * 和OutputStream/Writer对接
     * 应用场景:直接和web项目的response对接
     */
@Test
public void toJsonOS() throws Exception {
    File file = new File("E:/test.json");
    FileOutputStream fileOutputStream = new FileOutputStream(file);
    JSON.writeJSONString(fileOutputStream,user);
}
  • BeanToArray
@Test
public void bean2Array() throws Exception {
    System.out.println(JSON.toJSONString(user,SerializerFeature.BeanToArray));
}
[1,[[100.0101,661676334969,"2020001","1"],[200.056,788943534969,"2020002","0"]],"小y"]

1.2 反序列化

  • 字符串转对象/Map/集合
@Test
public void parseObjectOrArray(){
    User user = JSON.parseObject(userStr, User.class);
    System.out.println(user);
    List<User> users = JSON.parseArray(usersStr, User.class);
    System.out.println(users);
    List<Map> maps = JSON.parseArray(usersStr, Map.class);
    System.out.println(maps);
}
  • InputStream转对象/Map/集合
/**
     * 和inputstream对接
     * 应用场景:web项目中,请求过来的payload数据可以通过该API解析数据
     */
@Test
public void testIsToObject() throws IOException {
    InputStream is = new ByteArrayInputStream(userStr.getBytes());
    User user = JSON.parseObject(is, User.class);
    System.out.println(user);
}
  • 传入的是对象(数组同理),但是对象中的属性值是一个复杂对象

    {"user":{
        "id":1,
        "username":"小A",
        idCards:[
            {cardNo:2000134102030010, "sex":1, "birthday":"1995-01-01"}, 
            {cardNo:2000134102030020, "sex":0, "birthday":"1992-02-02"}
            ]
      }
    }
    
@Test
public void testComObject() throws IOException {
    String a = "{\"user\":{\n" +
        "    \"id\":1,\n" +
        "    \"username\":\"小A\",\n" +
        "    idCards:[\n" +
        "        {cardNo:2000134102030010, \"sex\":1, \"birthday\":\"1995-01-01\"}, \n" +
        "        {cardNo:2000134102030020, \"sex\":0, \"birthday\":\"1992-02-02\"}\n" +
        "        ]\n" +
        "\t}\n" +
        "}";
    Map<String,User> user = JSON.parseObject(a, new TypeReference<Map<String, User>>(){});
    System.out.println(user);

    User user1 = user.get("user");
    IdCard idCard = user1.getIdCards().get(0);
    System.out.println(idCard.getBirthday());
}

1.3 定制序列化

——以最常用的Date类型举例说明。

方式一:@JSONField

@JSONField(format = "yyyy-MM-dd")
private Date birthday;
@Test
public void dateFormat1(){
    String string = JSON.toJSONString(user);
    System.out.println(string);
}

方式二:使用SerializerFeature的WriteDateUseDateFormat

@Test
public void dateFormat(){
    JSON.DEFFAULT_DATE_FORMAT = "yyyy/MM/dd HH:mm:ss";
    String string = JSON.toJSONString(user,SerializerFeature.WriteDateUseDateFormat);
    System.out.println(string);
}
{"id":1,"idCards":[{"balance":100.015,"birthday":"1990-11-10","cardNo":"1001"},{"balance":300.0123,"birthday":"2000-10-09","cardNo":"1002"}],"username":"查克拉"}

方式三:配置SerializeConfig

@Test
public void dateFormat3(){
    SerializeConfig config = new SerializeConfig();
    config.put(Date.class,new SimpleDateFormatSerializer("yyyy/MM/dd HH:mm:ss"));
    String str = JSON.toJSONString(user,config);
    System.out.println(JSON.toJSONString(str);
}

方式四:使用SerializeFilter

@Test
public void dateFormat4(){
    // 类似全局配置,@JSONField会失效
    ValueFilter valueFilter = new ValueFilter() {
        public Object process(Object object, String name, Object value) {
            if(value instanceof Date){
                value = new SimpleDateFormat("yyyy/MM/dd").format(value);
            }
            return value;
        }
    };
    System.out.println(JSON.toJSONString(user,valueFilter));
}

SerializeFilter下有多个子接口或抽象类

简单说明:
PropertyPreFilter根据PropertyName判断是否序列化
PropertyFilter 在序列化,设定那些字段是否被序列化
NameFilter 序列化时修改Key的名称。比如属性名为name,可以修改为Name。
ValueFilter 序列化时修改Value
BeforeFilter 在序列化对象的所有属性之前执行某些操作

AfterFilter 在序列化对象的所有属性之后执行某些操作

他们有执行顺序:

​ PropertyPreFilter --> PropertyFilter --> NameFilter --> ValueFilter --> BeforeFilter --> AfterFilter

方式五:使用JSONField注解的serializeUsing属性

public class DateSer implements ObjectSerializer {
    public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
        if (object == null) {
            serializer.out.writeNull();
            return;
        }
        Date date = (Date)object;
        String dateStr = new SimpleDateFormat("yyyy-MM/dd HH:mm:ss").format(date);
        serializer.write(dateStr);
    }
}
@JSONField(serializeUsing = DateSer.class)
private Date birthday;

注解属性说明:

public @interface JSONField {
  // 序列化、反序列化的顺序
    int ordinal() default 0;
  // 指定字段的名称
    String name() default "";
  // 指定字段的格式,对日期格式有用 -常用
    String format() default "";
   // 是否序列化 -常用
    boolean serialize() default true;
  // 是否反序列化
    boolean deserialize() default true;
  // 指定该字段使用的SerializerFeature
    SerializerFeature[] serialzeFeatures() default {};

    Feature[] parseFeatures() default {};
   // 给属性打上标签, 相当于给属性进行了分组
    String label() default "";
    
    boolean jsonDirect() default false;
    
  // 设置属性的序列化类
    Class<?> serializeUsing() default Void.class;
   // 设置属性的反序列化类
    Class<?> deserializeUsing() default Void.class;

    String[] alternateNames() default {};

    boolean unwrapped() default false;
}

2、springboot+fastjson

2.1 配置fastjson

1)依赖 (我们此处暂不使用fastjson的起步依赖方式)

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.68</version>
</dependency>

2)配置类

springmvc 4.2+ 、fastjson使用最新的

@Configuration
public class HttpMessageConfig {

    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverter(){
        FastJsonHttpMessageConverter fastJsonHttpMessageConverter = 
            new FastJsonHttpMessageConverter();
    // fastJsonHttpMessageConverter通过封装FastjsonConfig配置全局

        return new HttpMessageConverters(fastJsonHttpMessageConverter);
    }
}

2.2 FastJsonConfig

public void setCharset(Charset charset);
public void setSerializerFeatures(SerializerFeature... serializerFeatures); 序列化特性
public void setSerializeConfig(SerializeConfig serializeConfig); 序列化配置-个性化
public void setParserConfig(ParserConfig parserConfig); 反序列化配置
public void setSerializeFilters(SerializeFilter... serializeFilters); 序列化过滤器

2.3 SerializerFeature

名称 描述
QuoteFieldNames 输出key时是否使用双引号,默认为true
UseSingleQuotes 使用单引号而不是双引号,默认为false
WriteMapNullValue 是否输出值为null的字段,默认为false
WriteEnumUsingToString Enum输出name()或者original,默认为false
WriteEnumUsingName 用枚举name()输出
UseISO8601DateFormat Date使用ISO8601格式输出,默认为false
WriteNullListAsEmpty List字段如果为null,输出为[],而非null
WriteNullStringAsEmpty 字符类型字段如果为null,输出为”“,而非null
WriteNullNumberAsZero 数值字段如果为null,输出为0,而非null
WriteNullBooleanAsFalse Boolean字段如果为null,输出为false,而非null
SkipTransientField 如果是true,类中的Get方法对应的Field是transient,序列化时将会被忽略。默认为true
SortField 按字段名称排序后输出。默认为false
(过期)WriteTabAsSpecial 把\t做转义输出,默认为false
PrettyFormat 结果是否格式化,默认为false
WriteClassName 序列化时写入类型信息,默认为false。反序列化时需用到
DisableCircularReferenceDetect 消除对同一对象循环引用的问题,默认为false
WriteSlashAsSpecial 对斜杠’/’进行转义
BrowserCompatible 将中文都会序列化为\uXXXX格式,字节数会多一些,但是能兼容IE 6,默认为false
WriteDateUseDateFormat 全局修改日期格式,默认为false。JSON.DEFFAULT_DATE_FORMAT = “yyyy-MM-dd”;JSON.toJSONString(obj, SerializerFeature.WriteDateUseDateFormat);
(过期)DisableCheckSpecialChar 一个对象的字符串属性中如果有特殊字符如双引号,将会在转成json时带有反斜杠转移符。如果不需要转义,可以使用这个属性。默认为false

2.4 SerializeConfig

// API
public boolean put(Type type, ObjectSerializer value)
serializeConfig.propertyNamingStrategy = 
    PropertyNamingStrategy.CamelCase/PascalCase/...;

CamelCase策略,Java对象属性:personId,序列化后属性:persionId
PascalCase策略,Java对象属性:personId,序列化后属性:PersonId
SnakeCase策略,Java对象属性:personId,序列化后属性:person_id
KebabCase策略,Java对象属性:personId,序列化后属性:person-id

3.解决方案

3.1 重复引用

使用 SerializerFeature.DisableCircularReferenceDetect 来去除重复引用
@PostMapping("/users")
public @ResponseBody List<User> users(@RequestBody User user){
    //封装用户信息
    ...
    //封装信用卡信息
    List<IdCard> idCards = new ArrayList<>();
    idCards.add(idCard1);
    idCards.add(idCard2);
    user.setIdCards(idCards);
    user2.setIdCards(idCards);//两个user对象共用一个idCards集合

    List<User> userList = new ArrayList<>();
    userList.add(user);
    userList.add(user2);
    return userList;
}

[
    {
        "createTime": "2020/05/24 17:12:15",
        "id": 4,
        "idCards": [
            {  "balance": 20000.011,
                "birthday": "2020-05-24 17:12:15",
                "cardNo": "2002110" },
            {   "balance": 200.1271,
                "birthday": "2020-05-24 17:12:15",
                "cardNo": "2002120" }
        ],
        "username": ""
    },
    {
        "createTime": "2020/02/02 00:00:00",
        "id": 10,
        "idCards": [
            {  "$ref": "$[0].idCards[0]" },
            {   "$ref": "$[0].idCards[1]" }
        ],
        "username": "lisi"
    }
]
语法 描述
引用根对象
引用自己
引用父对象
引用父对象的父对象
基于路径的引用
fastJsonConfig.setSerializerFeatures(
    //去除重复引用
  SerializerFeature.DisableCircularReferenceDetect
)

如果能直接控制到序列化方法的话,可以

JSON.toJSONString(user,SerializerFeature.DisableCircularReferenceDetect);

3.2 BigDecimal类型设置

方案一:

public class BigDecimalSerializer implements ObjectSerializer {

    private final String pattern;

    public BigDecimalSerializer(String pattern){
        this.pattern = pattern;
    }

    @Override
    public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
        DecimalFormat decimalFormat = new DecimalFormat(pattern);
        SerializeWriter out = serializer.out;
        if (object == null) {
            out.write("0.00");
            return;
        }

        BigDecimal decimal = (BigDecimal) object;
        //
        String formatDecimal = decimalFormat.format(decimal);
        out.write(formatDecimal);
    }
}
serializeConfig.put(BigDecimal.class,new BigDecimalSerializer("#0.00"));

发现并不可行。
但是如果在IdCard中任意加一个@JSONField注解,并且有format属性,就可用了!!!!!!可用了!!!

方案二:

使用SerializeFilter处理

FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
SerializeConfig serializeConfig = new SerializeConfig();


fastJsonConfig.setSerializeConfig(serializeConfig);

PropertyFilter propertyFilter = new PropertyFilter() {
    @Override
    public boolean apply(Object object, String name, Object value) {
        if(value instanceof BigDecimal){
            return false;
        }
        return true;
    }
};

AfterFilter afterFilter = new AfterFilter() {
    @Override
    public void writeAfter(Object object) {
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.getType() == BigDecimal.class) {
                field.setAccessible(true);
                Object value= null;
                try {
                    value = (BigDecimal)field.get(object);
                    value = ((BigDecimal) value).setScale(2,BigDecimal.ROUND_DOWN);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                writeKeyValue(field.getName(), value );
            }
        }
    }
};

fastJsonConfig.setSerializeFilters(propertyFilter,afterFilter);
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);

方案三:

ValueFilter valueFilter = new ValueFilter() {
    @Override
    public Object process(Object object, String name, Object value) {
        if(value instanceof BigDecimal){
            value = ((BigDecimal)value).setScale(3,BigDecimal.ROUND_DOWN);
        }
        return value;
    }
};
fastJsonConfig.setSerializeFilters(valueFilter);

3.3 日期类型格式化

要想做到该效果,需要我们配置

SerializeConfig serializeConfig = new SerializeConfig();
serializeConfig.put(Date.class,new SimpleDateFormatSerializer("yyyy-MM-dd HH:mm:ss"));

然后就可以在属性上添加@JSONFeild注解。

——看着好像是就近原则,其实,只是代码中优先处理了带有format格式化的字段

如下情况下,是全局说了算!

如果采用下面的全局配置方式,则会导致@JSONField失效

//配置全局日期处理,配置后@JSONField不再生效
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm"); 

//设置全局ValueFilter,配置后@JSONField不再生效
ValueFilter valueFilter = (Object object, String name, Object value) -> {
    if(value == null){
        value = "";
    }
    if(value instanceof LocalDateTime){
        value = ((LocalDateTime)value).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }
    if(value instanceof Date){
        value = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date)value);
    }

    return value;
};
fastJsonConfig.setSerializeFilters(valueFilter,...);

4.(低版本)漏洞观光

4.1 OOM

@Test
public void oom(){
    String str = "{\"name\":\"\\x\"";
    Map userMap = JSON.parseObject(str, Map.class);
    System.out.println(userMap);
}

4.2 Illegal target of jump or branch(2779)

JSON.parseObject("{}", AbcDTO.class);

总结

方法 参数 功能
parseOobject (string) 将该字符串解析为JSONObject类型
(String,Class) 将该字符串解析为Class指定的类型
(String,TypeReference) 将该字符串解析为TypeReference对象指定的自定义类型