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对象指定的自定义类型 | |