fast json详解一

  • fastjson 实例
# fastjson

## 0、遇到的问题:

### 0.1 项目中有需求如下

```
  把所有响应给前端的数据都只保留两位小数(在项目中,数字是BigDecimal类型)。由于是新接手的项目,有很多类中的属性需要改动(可能位置太多,找不全),如何一步到位?
```

```
  所有的日期格式需要转换为'yyyy-MM-dd HH:mm:ss'格式,但是如果有@JSONField(format="")注解的,按照注解的format配置内容来进行格式化
```

```
  出现"$ref"字符串解决...
```

### 0.2 fastjson

```
https://github.com/alibaba/fastjson/wiki
```

fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。

#### fastjson的优点

- #####  速度快

fastjson相对其他JSON库的特点是快,从2011年fastjson发布1.1.x版本之后,其性能从未被其他Java实现的JSON库超越。

- #####  使用广泛

fastjson在阿里巴巴大规模使用,在数万台服务器上部署,fastjson在业界被广泛接受。在2012年被开源中国评选为最受欢迎的国产开源软件之一。

- ##### 测试完备

fastjson有非常多的testcase,在1.2.11版本中,testcase超过3321个。每次发布都会进行回归测试,保证质量稳定。

- #####  使用简单

fastjson的API十分简洁。

```
String text = JSON.toJSONString(obj); //序列化
VO vo = JSON.parseObject("{...}", VO.class); //反序列化
```

- #####  功能完备

支持泛型,支持流处理超大文本,支持枚举,支持**序列化**和反序列化扩展。

## 1、基本API和配置

### 1.0 准备POJO

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

准备测试数据:

```java
@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 -- 字符串

```java
@Test
public void toJsonString(){
    String userString = JSON.toJSONString(user);
    System.out.println(userString);
    String usersString = JSON.toJSONString(users);
    System.out.println(usersString);
}
```

```json
{
    "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

```java
/**
     * 和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

```java
@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/集合

```java
@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/集合

```java
/**
     * 和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);
}
```

- 传入的是对象(数组同理),但是对象中的属性值是一个复杂对象

  ```json
  {"user":{
      "id":1,
      "username":"小A",
      idCards:[
          {cardNo:2000134102030010, "sex":1, "birthday":"1995-01-01"}, 
          {cardNo:2000134102030020, "sex":0, "birthday":"1992-02-02"}
          ]
      }
  }
  ```

```java
@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

```java
@JSONField(format = "yyyy-MM-dd")
private Date birthday;
```

```java
@Test
public void dateFormat1(){
    String string = JSON.toJSONString(user);
    System.out.println(string);
}
```

方式二:使用SerializerFeature的WriteDateUseDateFormat

```java
@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);
}
```

```json
{"id":1,"idCards":[{"balance":100.015,"birthday":"1990-11-10","cardNo":"1001"},{"balance":300.0123,"birthday":"2000-10-09","cardNo":"1002"}],"username":"查克拉"}
```



方式三:配置SerializeConfig

```java
@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**

```java
@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`下有多个子接口或抽象类

![1590304908902](assets/1590304908902.png) 

> 简单说明:
>
> `PropertyPreFilter`根据PropertyName判断是否序列化
>
> `PropertyFilter` 在序列化,设定那些字段是否被序列化
>
> `NameFilter` 序列化时修改Key的名称。比如属性名为name,可以修改为Name。
>
> `ValueFilter` 序列化时修改Value
>
> `BeforeFilter` 在序列化对象的所有属性之前执行某些操作
>
> ![1590305267125](assets/1590305267125.png) 
>
> `AfterFilter` 在序列化对象的所有属性之后执行某些操作
>
> 
>
> 他们有执行顺序:
>
> ​    PropertyPreFilter --> PropertyFilter --> NameFilter --> ValueFilter --> BeforeFilter --> AfterFilter



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

```java
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);
    }
}
```

```java
@JSONField(serializeUsing = DateSer.class)
private Date birthday;
```

> 注解属性说明:

```java
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的起步依赖方式)

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

2)**配置类**

springmvc 4.2+ 、fastjson使用最新的

```java
@Configuration
public class HttpMessageConfig {

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

        return new HttpMessageConverters(fastJsonHttpMessageConverter);
    }
}
```

### 2.2 FastJsonConfig

```java
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

```java
// 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 重复引用

```java
使用 SerializerFeature.DisableCircularReferenceDetect 来去除重复引用
```

```java
@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;
}

```

```json
[
    {
        "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"
    }
]
```

> | 语法                             | 描述               |
> | -------------------------------- | ------------------ |
> | {"$ref":"$"}                     | 引用根对象         |
> | {"$ref":"@"}                     | 引用自己           |
> | {"$ref":".."}                    | 引用父对象         |
> | {"$ref":"../.."}                 | 引用父对象的父对象 |
> | {"$ref":"$.members[0].reportTo"} | 基于路径的引用     |

```java
fastJsonConfig.setSerializerFeatures(
    //去除重复引用
    SerializerFeature.DisableCircularReferenceDetect
)
```

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

```java
JSON.toJSONString(user,SerializerFeature.DisableCircularReferenceDetect);
```



### 3.2 BigDecimal类型设置

方案一:

```java
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);
    }
}
```

```java
serializeConfig.put(BigDecimal.class,new BigDecimalSerializer("#0.00"));
```

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

方案二:

**使用SerializeFilter处理**

```java
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);
```

方案三:

```java
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 日期类型格式化

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

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

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

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

![1590315125591](assets/1590315125591.png) 



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

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

```java
//配置全局日期处理,配置后@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

![1590224128793](assets/1590224128793.png)

```java
@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)

![1590224228611](assets/1590224228611.png)

![1590224619791](assets/1590224619791.png) 

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

 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itheima</groupId>
    <artifactId>fastjson_demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.68</version>
    </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>


</project>
  •  使用fastjson中的TypeReference
  public static void main(String[] args) {
        String str = "{'XX':1}";
        Map<String, BigDecimal> map = JSON.parseObject(str, new TypeReference<Map<String, BigDecimal>>(){});
        System.out.println(map);
    }

 

posted @ 2020-12-03 19:14  Bonnie_ξ  阅读(541)  评论(0编辑  收藏  举报