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`下有多个子接口或抽象类  > 简单说明: > > `PropertyPreFilter`根据PropertyName判断是否序列化 > > `PropertyFilter` 在序列化,设定那些字段是否被序列化 > > `NameFilter` 序列化时修改Key的名称。比如属性名为name,可以修改为Name。 > > `ValueFilter` 序列化时修改Value > > `BeforeFilter` 在序列化对象的所有属性之前执行某些操作 > >  > > `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格式化的字段  如下情况下,是全局说了算! > 如果采用下面的全局配置方式,则会导致@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  ```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)   ```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); }
故乡明
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话