# 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":{
" +
" "id":1,
" +
" "username":"小A",
" +
" idCards:[
" +
" {cardNo:2000134102030010, "sex":1, "birthday":"1995-01-01"},
" +
" {cardNo:2000134102030020, "sex":0, "birthday":"1992-02-02"}
" +
" ]
" +
" }
" +
"}";
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 | 把 做转义输出,默认为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>
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);
}