• 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":{
    " +
            "    "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>
    •  使用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);
        }
    故乡明
  • 相关阅读:
    二、Cocos2dx概念介绍(游戏开发中不同的坐标系,cocos2dx锚点)
    (2)入门指南——(7)添加jquery代码(Adding our jQuery code)
    Citrix 服务器虚拟化之三十一 XenApp 6.5负载均衡
    CSS——inline-block属性
    VMware Workstation(虚拟机软件) V10.0 简体中文版可以安装了
    [.NET MVC4 入门系列01]Helloworld MVC 4 第一个MVC4程序
    ElasticSearch NEST笔记
    什么是REST API?
    ArrayList与List<T>笔记
    C# Socket SSL通讯笔记
  • 原文地址:https://www.cnblogs.com/luweiweicode/p/14082169.html
Copyright © 2020-2023  润新知