zoukankan      html  css  js  c++  java
  • 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);
        }
    故乡明
  • 相关阅读:
    hdu2328 Corporate Identity
    hdu1238 Substrings
    hdu4300 Clairewd’s message
    hdu3336 Count the string
    hdu2597 Simpsons’ Hidden Talents
    poj3080 Blue Jeans
    poj2752 Seek the Name, Seek the Fame
    poj2406 Power Strings
    hust1010 The Minimum Length
    hdu1358 Period
  • 原文地址:https://www.cnblogs.com/luweiweicode/p/14082169.html
Copyright © 2011-2022 走看看