首页 前端知识 序列化方式二——JSON之Fastjson

序列化方式二——JSON之Fastjson

2025-03-18 12:03:44 前端知识 前端哥 262 497 我要收藏

fastjson&fastjson2(版本:1.2.83_noneautotype)

扩展点

  • Fastjson通过其丰富的扩展点显著增强了用户定制序列化和反序列化行为的灵活性,完美契合了实际开发中的多样化需求。在SpringBoot与SpringCloud的集成环境中,开发者能够利用SerializeFilter和ParserProcess精细地构建复杂的序列化与反序列化逻辑,以应对复杂的数据处理场景。同时,通过调整SerializerFeature和ParserFeature,用户可以轻松控制输出格式与特性,如日期格式、空字段处理等,确保数据交换的准确性和效率。

  • 此外,SerializeConfig与ParserConfig作为全局配置工具,进一步简化了配置过程,允许开发者在全局范围内统一设置序列化和反序列化的规则,这不仅减少了代码冗余,还极大地提升了系统的可维护性和可扩展性。这些设计使得Fastjson成为处理JSON数据时的强大工具,有效促进了开发效率与系统性能的提升。

序列化扩展点
SerializeFilter
  • SerializeFilter接口及其子接口(如SimplePropertyPreFilter、PropertyPreFilter等)为开发者提供了在序列化过程中精细控制输出内容的能力。通过实现这些接口,用户可以灵活地过滤掉不需要的字段、修改字段值、调整字段的序列化行为等,从而满足特定场景下的数据输出需求。
使用方法:
  1. 实现SerializeFilter接口或其子接口: 根据需求,选择实现SerializeFilter接口或其提供的子接口(如SimplePropertyPreFilter用于简单的属性预过滤,PropertyPreFilter提供基于属性名称和值的过滤逻辑等)。在实现的类中,通过覆盖相关方法来自定义序列化逻辑。

  2. 创建SerializeFilter实例: 实例化步骤1中创建的自定义SerializeFilter类,准备将其应用于序列化过程。

  3. 在序列化时传入自定义的SerializeFilter实例: 当调用JSON.toJSONString()等序列化方法时,利用这些方法的重载版本,将自定义的SerializeFilter实例作为参数传入。这样,在序列化过程中就会应用到你定义的过滤和修改逻辑,从而按需输出JSON字符串。

示例简化:
package com.zhz.test.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Student implements Serializable {

    private String name;
    private String sex;
    private int age;
}

 @Test
    public void testFastjson2() {
        Student student = new Student("小明", "男", 18, new Date());
        //序列化成JSON格式的字符串
        String jsonStr = JSON.toJSONString(student, new SimplePropertyPreFilter() {
            @Override
            public boolean apply(JSONSerializer serializer, Object source, String name) {
                // 定制序列化逻辑
                // 返回true表示保留该字段,false表示过滤
                return !"name".equals(name);
            }
        });
        System.out.println(jsonStr);
        //反序列化成对象
        Student student1 = JSON.parseObject(jsonStr, Student.class);
        System.out.println(student1);
    }

image.png

我们可以发现name属性已过滤。

SerializerFeature(日期格式化请看此)
  • SerializerFeature 枚举提供了丰富的选项,用于在序列化过程中精细控制输出的格式和特性。通过这些特性,开发者可以定制JSON字符串的外观,比如格式化输出、忽略空值字段、输出日期格式等,以满足不同的数据交换和展示需求。
使用方法:
  • 在调用序列化方法(如JSON.toJSONString)时,可以通过其重载版本传入一个或多个SerializerFeature枚举值作为参数,以此来指定所需的序列化特性。这些枚举值可以通过逻辑运算符(如|)组合使用,以同时启用多个特性。
解析SerializerFeature的属性:
  1. QuoteFieldNames:

    • 字段名使用双引号括起来,符合 JSON 标准。
  2. UseSingleQuotes:

    • 使用单引号而不是双引号来括起字段名和字符串值。这不是 JSON 标准,但在某些情况下可能更节省空间或符合特定需求。
  3. WriteMapNullValue:

    • 写入值为 null 的字段。默认情况下,值为 null 的字段不会被序列化。
  4. WriteEnumUsingToString:

    • 枚举类型使用 toString() 方法的返回值进行序列化,而不是使用枚举的名字。
  5. WriteEnumUsingName:

    • 枚举类型使用其名字(即 name() 方法的返回值)进行序列化。
  6. UseISO8601DateFormat:

    • 使用 ISO8601 标准的日期格式来序列化日期对象。
  7. WriteNullListAsEmpty:

    • null 值的 List 字段序列化为空数组 []
  8. WriteNullStringAsEmpty:

    • null 值的字符串字段序列化为空字符串 ""
  9. WriteNullNumberAsZero:

    • null 值的数字字段序列化为 0
  10. WriteNullBooleanAsFalse:

    • null 值的布尔字段序列化为 false
  11. SkipTransientField:

    • 跳过 transient 修饰的字段,不进行序列化。
  12. SortField:

    • 对字段进行排序后再序列化。
  13. WriteTabAsSpecial(已弃用):

    • 特殊处理 Tab 字符,通常不建议使用。
  14. PrettyFormat:

    • 格式化输出,使 JSON 字符串更易于阅读,包含缩进和换行。
  15. WriteClassName:

    • 写入类名信息,便于反序列化时恢复对象的实际类型。
  16. DisableCircularReferenceDetect:

    • 禁用循环引用检测,避免因为循环引用导致的无限递归。
  17. WriteSlashAsSpecial:

    • 对斜杠 / 进行特殊处理,通常用于确保生成的 JSON 可以作为 URL 的一部分而不被破坏。
  18. BrowserCompatible:

    • 浏览器兼容模式,处理一些与浏览器相关的特殊字符。
  19. WriteDateUseDateFormat:

    • 使用自定义的日期格式来序列化日期对象。
  20. NotWriteRootClassName:

    • 不写入根对象的类名,即使启用了 WriteClassName
  21. DisableCheckSpecialChar(已弃用):

    • 禁用特殊字符检查,通常不建议使用以避免潜在的安全问题。
  22. BeanToArray:

    • 将 Java Bean 序列化为数组形式,而不是默认的键值对形式。
  23. WriteNonStringKeyAsString:

    • 将非字符串类型的 Key 也序列化为字符串。
  24. NotWriteDefaultValue:

    • 不写入默认值,即如果字段的值等于其类型的默认值(如数字为 0,布尔为 false),则不进行序列化。
  25. BrowserSecure:

    • 浏览器安全模式,防止 XSS 攻击等安全问题。
  26. IgnoreNonFieldGetter:

    • 忽略非字段的 getter 方法,即只序列化 Java Bean 的字段,不序列化通过 getter 方法暴露的属性(如果该 getter 不是对应某个字段的)。
  27. WriteNonStringValueAsString:

    • 将非字符串类型的值也强制序列化为字符串。
  28. IgnoreErrorGetter:

    • 在序列化过程中,如果 getter 方法抛出异常,则忽略该异常并继续序列化其他字段。
  29. WriteBigDecimalAsPlain:

    • BigDecimal 类型的值序列化为无科学计数法的普通数字字符串。
  30. MapSortField:

    • Map 类型的字段进行排序后再序列化。
测试样例
package com.zhz.test.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Student implements Serializable {

    private String name;
    private String sex;
    private int age;
}

@Test
public void testFastjson3() {
    Student student = new Student("小明", null, 18);
    //序列化成JSON格式的字符串
    String jsonStr = JSON.toJSONString(student, SerializerFeature.WriteNullStringAsEmpty);
    System.out.println(jsonStr);
    //反序列化成对象
    Student student1 = JSON.parseObject(jsonStr, Student.class);
    System.out.println(student1);
}

image.png

SerializeConfig
用途
  • 全局配置序列化:提供统一的配置入口,管理序列化过程中的各种行为。

  • 自定义序列化器:为特定类型定义自己的序列化逻辑。

  • 日期格式设置:指定日期对象在序列化时应该遵循的格式。

使用方法
  1. 创建 SerializeConfig 实例:首先,需要创建一个 SerializeConfig 对象。

  2. 配置选项:通过 SerializeConfig 提供的各种方法,配置所需的序列化选项,如添加自定义序列化器、设置日期格式等。

  3. 应用配置:在调用 JSON.toJSONString 或其他序列化方法时,通过传入已配置的 SerializeConfig 实例,来应用这些全局配置。

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Student implements Serializable {

    private String name;
    private String sex;
    private int age;
    private Date birthday;
}

@Test
public void testFastjson4() {
    // 创建 SerializeConfig 实例
    SerializeConfig serializeConfig = new SerializeConfig();

    // 配置日期格式(可选)
    serializeConfig.put(Date.class, new SimpleDateFormatSerializer("yyyy-MM-dd HH:mm:ss"));

    // 创建对象实例
    Student student = new Student("小明", null, 18,new Date());

    // 序列化成 JSON 格式的字符串,并应用 SerializeConfig 配置
    String jsonStr = JSON.toJSONString(student, serializeConfig, SerializerFeature.WriteNullStringAsEmpty);
    System.out.println(jsonStr);

    // 反序列化成对象(注意:反序列化时不需要 SerializeConfig)
    Student student1 = JSON.parseObject(jsonStr, Student.class);
    System.out.println(student1);

    // 假设有一个 Student 类,包含姓名、地址、年龄和日期字段(略)
}

image.png

自定义序列化器(Serializer)

允许用户自定义序列化器,用于控制特定类型的序列化过程。用户需要实现ObjectSerializer 接口,并重写write方法。

  @Data
    public static class CustomObject {
        private String name;
        private int value;

        // 构造方法、getter和setter省略
    }

    public static class CustomSerializer implements ObjectSerializer {
        @Override
        public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
            CustomObject customObj = (CustomObject) object;
            serializer.write("custom_" + customObj.getName()); // 自定义序列化逻辑
        }
    }

    public static void main(String[] args) {
        CustomObject customObject = new CustomObject();
        customObject.setName("test");
        customObject.setValue(100);

        SerializeConfig config = new SerializeConfig();
        config.put(CustomObject.class, new CustomSerializer());

        String jsonString = JSON.toJSONString(customObject, config);
        System.out.println(jsonString); // 输出自定义序列化后的结果
    }

image.png

反序列化扩展点
ParseProcess

用途: 在反序列化过程中,ParseProcess 接口允许用户执行额外的逻辑操作,比如对反序列化后的对象进行修改、验证或添加额外的处理步骤。

使用方法
  1. 实现 ParseProcess 接口: 创建一个类,实现 ParseProcess 接口,并重写其中的方法。在这个方法里,你可以定义反序列化完成后需要执行的额外逻辑。

  2. 应用 ParseProcess 实例: 在调用 JSON.parseObject 或其他反序列化方法时,通过其重载版本传入你实现的 ParseProcess 实例。这样,在反序列化过程完成后,会自动调用你的自定义逻辑。

  3. ParseProcess有两个实现类,分别为:ExtraProcessorExtraTypeProvider,两者作用不一样,下面是两个实现类的demo。

使用ExtraProcessor 处理多余字段
  public class VO {
        private int id;
        private Map<String, Object> attributes = new HashMap<String, Object>();
        public int getId() { return id; }
        public void setId(int id) { this.id = id;}
        public Map<String, Object> getAttributes() { return attributes;}

        @Override
        public String toString() {
            return "VO{" +
                    "id=" + id +
                    ", attributes=" + attributes +
                    '}';
        }

        public static void main(String[] args) {

            ExtraProcessor processor = new ExtraProcessor() {
                public void processExtra(Object object, String key, Object value) {
                    System.out.println("---------------object = " + object);
                    System.out.println("---------------key = " + key);
                    System.out.println("---------------value = " + value);
                    System.out.println();
                    VO vo = (VO) object;
                    vo.setId(789);// 修改一下id值
                    vo.getAttributes().put(key, value);
                }
            };
            // 这里name和phone是多余的,在VO里没有
            VO vo = JSON.parseObject("{\"id\":123,\"name\":\"abc\",\"phone\":\"2134213\"}", VO.class, processor);

            System.out.println("vo.getId() = " + vo.getId());
            System.out.println("vo.getAttributes().get(\"name\") = " + vo.getAttributes().get("name"));
            System.out.println("vo.getAttributes().get(\"phone\") = " + vo.getAttributes().get("phone"));
        }
    }

image.png

使用ExtraTypeProvider 为多余的字段提供类型
        public static class VO {
        private int id;
        private Map<String, Object> attributes = new HashMap<String, Object>();
        public int getId() { return id; }
        public void setId(int id) { this.id = id;}
        public Map<String, Object> getAttributes() { return attributes;}

        @Override
        public String toString() {
            return "VO{" +
                    "id=" + id +
                    ", attributes=" + attributes +
                    '}';
        }

        public static void main(String[] args) {

            // 这里name和phone是多余的,在VO里没有
            VO vo = JSON.parseObject("{\"id\":123,\"name\":\"abc\",\"phone\":\"2134213\"}", VO.class, new MyExtraProcessor());

            System.out.println("vo.getId() = " + vo.getId());
            System.out.println("vo.getAttributes().get(\"name\") = " + vo.getAttributes().get("name"));
            System.out.println("vo.getAttributes().get(\"phone\") = " + vo.getAttributes().get("phone"));
        }
    }
    public static class MyExtraProcessor implements ExtraProcessor, ExtraTypeProvider {
        public void processExtra(Object object, String key, Object value) {
            VO vo = (VO) object;
            vo.getAttributes().put(key, value);
        }

        public Type getExtraType(Object object, String key) {
            if ("phone".equals(key)) {
                return int.class;
            }
            return null;
        }
    }

image.png

ParserFeature
用途
  • Feature 枚举在 Fastjson 中用于控制反序列化的行为和特性。通过指定不同的 Feature 枚举值,用户可以定制反序列化过程中的多种行为,如是否忽略未知的字段、是否允许单引号包围的字符串等。
使用方法:
  • 在进行反序列化操作时,用户可以通过 JSON.parseObject 等方法的重载版本,传入 Feature 枚举值来控制反序列化的特性。这些枚举值作为额外的参数,指导 Fastjson 如何解析和转换 JSON 字符串为 Java 对象。
解析Feature枚举的属性:
  1. AutoCloseSource:

    • 自动关闭JSON源输入流。在完成反序列化后,会尝试关闭输入流,如InputStream
  2. AllowComment:

    • 允许JSON字符串中包含注释。标准的JSON是不支持注释的,开启此特性后,可以解析包含///* */注释的JSON。
  3. AllowUnQuotedFieldNames:

    • 允许字段名不使用双引号包围。标准的JSON要求字段名必须使用双引号,但开启此特性后,可以解析不使用双引号的字段名。
  4. AllowSingleQuotes:

    • 允许使用单引号包围字符串值。标准的JSON要求字符串值使用双引号,但开启此特性后,也支持单引号。
  5. InternFieldNames:

    • 对字段名进行字符串驻留(intern)。这有助于减少内存占用,当有很多相同字段名的JSON对象时。
  6. AllowISO8601DateFormat:

    • 允许使用ISO8601格式的日期字符串。例如,2023-04-01T12:00:00Z
  7. AllowArbitraryCommas:

    • 允许JSON对象中存在多余的逗号。例如,{"a":1,,"b":2}中的逗号。
  8. UseBigDecimal:

    • 使用BigDecimal来解析浮点数,而不是double。这可以提供更精确的数值表示。
  9. IgnoreNotMatch:

    • 忽略不匹配的字段。当JSON中的字段与Java对象的字段不匹配时,不会抛出异常。
  10. SortFeidFastMatch (可能是SortFieldFastMatch的拼写错误):

    • 对字段进行排序以快速匹配。这可能有助于优化某些情况下的反序列化性能。
  11. DisableASM:

    • 禁用ASM(Java字节码操作和分析框架)优化。在某些情况下,为了避免与ASM相关的问题,可以禁用它。
  12. DisableCircularReferenceDetect:

    • 禁用循环引用检测。当JSON中存在循环引用时,不会抛出异常。
  13. InitStringFieldAsEmpty:

    • 将字符串字段初始化为空字符串,而不是null
  14. SupportArrayToBean:

    • 支持将JSON数组反序列化为Java Bean。通常,JSON数组会转换为Java的Listarray,但开启此特性后,可以将其转换为具有特定字段的Java对象。
  15. OrderedField:

    • 保持字段的顺序。在反序列化时,Java对象的字段将按照JSON中出现的顺序进行设置。
  16. DisableSpecialKeyDetect:

    • 禁用特殊键检测。例如,不处理$ref等用于处理循环引用的特殊键。
  17. UseObjectArray:

    • 使用Object[]来接收反序列化的数组,而不是具体类型的数组。
  18. SupportNonPublicField:

    • 支持反序列化到非公共字段(例如,私有字段)。
  19. IgnoreAutoType:

    • 忽略自动类型识别。这可能与安全性相关,以防止利用类型识别进行攻击。
  20. DisableFieldSmartMatch:

    • 禁用字段智能匹配。这可能影响字段名与Java对象属性名的匹配逻辑。
  21. SupportAutoType:

    • 支持自动类型识别。允许在反序列化时自动确定对象的类型。
  22. NonStringKeyAsString:

    • 将非字符串类型的键也作为字符串处理。
  23. CustomMapDeserializer:

    • 使用自定义的Map反序列化器。允许用户定义如何反序列化JSON对象为Map类型。
  24. ErrorOnEnumNotMatch:

    • 当枚举值不匹配时抛出错误。如果JSON中的枚举值与Java枚举类中的值不匹配,将抛出异常。
  25. SafeMode:

    • 安全模式。在此模式下,Fastjson会采取更严格的安全措施来防止潜在的安全风险,如自动类型识别的限制等。
package com.zhz.test.serialization.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Student implements Serializable {

    private String name;
    private String sex;
    private int age;
    private Date birthday;
}

@Test
public void testFastjson5() {
    // 创建对象实例
    Student student = new Student("小明", null, 18, new Date());

    // 序列化成 JSON 格式的字符串,并应用 SerializeConfig 配置
    String jsonStr = JSON.toJSONString(student, SerializerFeature.WriteNullStringAsEmpty);
    System.out.println(jsonStr);

    // 反序列化成对象(注意:反序列化时不需要 SerializeConfig)
    Student student1 = JSON.parseObject(jsonStr, Student.class, Feature.ErrorOnEnumNotMatch);
    System.out.println(student1);
}
自定义反序列化器(Deserializer)

ObjectDeserializer 专门用于处理将 JSON 数据反序列化为 Java 对象的过程。不过,需要注意的是,对于大多数开发者而言,直接使用 ObjectDeserializer 进行自定义反序列化可能不是最常见的做法,因为 Fastjson 提供了更高级别的抽象(如 @JSONField 注解、Feature 枚举等)来简化这一过程。

然而,在需要高度自定义反序列化行为时,了解并可能实现 ObjectDeserializer 是很有用的。下面是对 ObjectDeserializer 的一些基本解析:

1. 接口定义

ObjectDeserializer 是一个接口,它定义了一系列方法来处理 JSON 数据的反序列化过程。这些方法包括但不限于:

  • <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName):这是最主要的反序列化方法,用于将 JSON 数据转换为 Java 对象。

  • int getFastMatchToken():返回一个整数值,用于快速匹配 JSON 数据的类型,以提高反序列化性能。

2. 使用场景
  • 当你需要处理复杂的反序列化逻辑,而 Fastjson 提供的默认行为或注解方式无法满足需求时。

  • 当你需要优化反序列化性能,通过自定义 ObjectDeserializer 来实现更高效的解析逻辑时。

  • 当你需要处理自定义类型或第三方库中的类型,并且这些类型没有提供标准的 Fastjson 支持时。

3. 实现自定义 **ObjectDeserializer**

实现自定义的 ObjectDeserializer 通常涉及以下几个步骤:

  1. 定义类:创建一个类,实现 ObjectDeserializer 接口。

  2. 实现方法:根据需求实现接口中的方法,特别是 deserialze 方法。

  3. 注册反序列化器:将自定义的反序列化器注册到 Fastjson 的 ParserConfig 中,以便在反序列化过程中使用。

4、实现实例

**下面我们要实现一个需求:**实现自适应的各种日期解析

日期工具类

package com.zhz.test.serialization.json.fastjson;

import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 日期工具类<br>
 *
 * @author zhouhengzhe
 */
public class DateSupport {
    /**
     * pattern for received date processing.
     */
    private static final String[] PATTERNS = new String[]
            {
                    "yyyy-MM-dd'T'HH:mm:ss.SSSZ",         /** ISO8601时间格式 */
                    "yyyy-MM-dd HH:mm:ss",                  /** 用于SQL语句的时间戳格式转换格式 */
                    "yyyy-MM-dd",                                  /** 日期转换格式 */
                    "yyyy-MM-d HH:mm:ss",
                    "yyyy-MM-dd HH:mm:ss Z",
                    "yyyy-MM-dd HH:mm:ss z",
                    "HH:mm:ss",
                    "EEE MMM dd HH:mm:ss z yyyy",    /** Tue Feb 27 10:43:27 CST 2024 */
                    "EEE, dd MMM yyyy HH:mm:ss '-'S '('z')'",
                    "EEE, dd MMM yyyy HH:mm:ss '+'S '('z')'",
                    "EEE, dd MMM yyyy HH:mm:ss '-'S",
                    "EEE, dd MMM yyyy HH:mm:ss '+'S",
                    "EEE, dd MMM yyyy HH:mm:ss z",
                    "EEE, dd MMM yyyy HH:mm:ss Z",
                    "EEE, dd MMM yyyy HH:mm:ss",
                    "EEE, d MMM yyyy HH:mm:ss '-'S '('z')'",
                    "EEE, d MMM yyyy HH:mm:ss '+'S '('z')'",
                    "EEE, d MMM yyyy HH:mm:ss '-'S",
                    "EEE, d MMM yyyy HH:mm:ss '+'S",
                    "EEE, d MMM yyyy HH:mm:ss z",
                    "EEE, d MMM yyyy HH:mm:ss Z",
                    "EEE, d MMM yyyy HH:mm:ss",

                    "EEE, dd MMM yy HH:mm:ss '-'S '('z')'",
                    "EEE, dd MMM yy HH:mm:ss '+'S '('z')'",
                    "EEE, dd MMM yy HH:mm:ss '-'S",
                    "EEE, dd MMM yy HH:mm:ss '+'S",
                    "EEE, dd MMM yy HH:mm:ss z",
                    "EEE, dd MMM yy HH:mm:ss Z",
                    "EEE, dd MMM yy HH:mm:ss",
                    "EEE, d MMM yy HH:mm:ss '-'S '('z')'",
                    "EEE, d MMM yy HH:mm:ss '+'S '('z')'",
                    "EEE, d MMM yy HH:mm:ss '-'S",
                    "EEE, d MMM yy HH:mm:ss '+'S",
                    "EEE, d MMM yy HH:mm:ss z",
                    "EEE, d MMM yy HH:mm:ss Z",
                    "EEE, d MMM yy HH:mm:ss",

                    "dd MMM yyyy HH:mm:ss '-'S",
                    "dd MMM yyyy HH:mm:ss '+'S",
                    "dd MMM yyyy HH:mm:ss '-'S '('z')'",
                    "dd MMM yyyy HH:mm:ss '+'S '('z')'",
                    "dd MMM yyyy HH:mm:ss z",
                    "dd MMM yyyy HH:mm:ss Z",
                    "dd MMM yyyy HH:mm:ss",

                    "dd MMM yyy HH:mm:ss '-'S",
                    "dd MMM yyy HH:mm:ss '+'S",
                    "dd MMM yyy HH:mm:ss '-'S '('z')'",
                    "dd MMM yyy HH:mm:ss '+'S '('z')'",
                    "dd MMM yyy HH:mm:ss z",
                    "dd MMM yyy HH:mm:ss Z",
                    "dd MMM yyy HH:mm:ss",

                    "yyyy.MM.dd HH:mm:ss z",
                    "yyyy.MM.dd HH:mm:ss Z",
                    "yyyy.MM.d HH:mm:ss z",
                    "yyyy.MM.d HH:mm:ss Z",
                    "yyyy.MM.dd HH:mm:ss",
                    "yyyy.MM.d HH:mm:ss",

                    "yy.MM.dd HH:mm:ss z",
                    "yy.MM.dd HH:mm:ss Z",
                    "yy.MM.d HH:mm:ss z",
                    "yy.MM.d HH:mm:ss Z",
                    "yy.MM.dd HH:mm:ss",
                    "yy.MM.d HH:mm:ss",

                    "yyyy MM dd HH:mm:ss",
                    "yyyy MM d HH:mm:ss",
                    "yyyy MM dd HH:mm:ss z",
                    "yyyy MM dd HH:mm:ss Z",
                    "yyyy MM d HH:mm:ss z",
                    "yyyy MM d HH:mm:ss Z",

                    "yy MM dd HH:mm:ss",
                    "yy MM d HH:mm:ss",
                    "yy MM dd HH:mm:ss z",
                    "yy MM dd HH:mm:ss Z",
                    "yy MM d HH:mm:ss z",
                    "yy MM d HH:mm:ss Z",

                    "yy-MM-dd HH:mm:ss z",
                    "yy-MM-dd HH:mm:ss Z",
                    "yy-MM-d HH:mm:ss z",
                    "yy-MM-d HH:mm:ss Z",
                    "yy-MM-dd HH:mm:ss",
                    "yy-MM-d HH:mm:ss",

                    "dd MMM yyyy",
                    "d MMM yyyy",

                    "dd.MMM.yyyy",
                    "d.MMM.yyyy",

                    "dd-MMM-yyyy",
                    "d-MMM-yyyy",

                    "dd MM yyyy",
                    "d MM yyyy",

                    "dd.MM.yyyy",
                    "d.MM.yyyy",

                    "dd-MM-yyyy",
                    "d-MM-yyyy",

                    "yyyy MM dd",
                    "yyyy MM d",

                    "yyyy.MM.dd",
                    "yyyy.MM.d",

                    "yyyy-MM-d",

                    "dd MMM yy",
                    "d MMM yy",

                    "dd.MMM.yy",
                    "d.MMM.yy",

                    "dd-MMM-yy",
                    "d-MMM-yy",

                    "dd MM yy",
                    "d MM yy",

                    "dd.MM.yy",
                    "d.MM.yy",

                    "dd-MM-yy",
                    "d-MM-yy",

                    "yy MMM dd",
                    "yy MMM d",

                    "yy.MMM.d",

                    "yy-MMM-dd",
                    "yy-MMM-d",

                    "yy.MMM.dd",

                    // ex: Wed 19, Feb 2003
                    "EEE dd, MMM yyyy",
                    // ex: Wed 19, Feb 03
                    "EEE dd, MMM yy"
            };

    /**
     * ISO8601 date time pattern
     */
    static final String ISO8601_FORMATTER_STR = PATTERNS[0];
    /**
     * 用于SQL语句的时间戳格式转换格式
     */
    public static final String TIMESTAMP_FORMATTER_STR = PATTERNS[1];
    /**
     * 日期转换格式
     */
    static final String DATE_FORMATTER_STR = PATTERNS[2];

    /**
     * get a date from a date string representation in one of the registered formats
     *
     * @param strDate       the date as string.
     * @param pattern       [out] if not null, return pattern string or null if (null or empty) or correct pattern was not found
     * @param excludeIndexs excluded pattern index
     * @return Date object ,otherwise null If (null or empty) or correct pattern was not found
     */
    public static java.util.Date getDateFromString(String strDate, AtomicReference<String> pattern, int... excludeIndexs) {
        java.util.Date dReceivedDate = null;
        if (strDate == null || strDate.trim().equals("null")) {
            return dReceivedDate;
        } else {
            strDate = strDate.trim();
        }

        Set<Integer> exidx =
                Sets.newHashSet(Ints.asList(null == excludeIndexs ? new int[0] : excludeIndexs));
        SimpleDateFormat pSimpleDateFormat = new SimpleDateFormat("", Locale.ENGLISH);
        if (!strDate.isEmpty()) {
            for (int i = 0; i < PATTERNS.length; i++) {
                if (exidx.contains(i)) {
                    continue;
                }
                try {
                    pSimpleDateFormat.applyPattern(PATTERNS[i]);
                    dReceivedDate = pSimpleDateFormat.parse(strDate);
                    if (dReceivedDate == null) {
                        continue;
                    }
                    if (null != pattern) {
                        pattern.set(PATTERNS[i]);
                    }
                    return dReceivedDate;
                } catch (ParseException pe) {
                    ; // ignore this format try the next one
                }
            }
        }
        return dReceivedDate;
    }

    /**
     * get a date from a date string representation in one of the registered formats
     *
     * @param strDate the date as string.
     * @return Date object ,otherwise null If (null or empty) or correct pattern was not found
     */
    public static java.util.Date getDateFromString(String strDate) {
        return getDateFromString(strDate, null);
    }

    /**
     * get a date from a date string representation in one of the registered formats
     *
     * @param dateStr     the date as string.
     * @param targetClass
     * @return Date object ,otherwise null If (null or empty) or correct pattern was not found
     */
    public static <D extends Date> D parseDateString(String dateStr, Class<D> targetClass) {
        if (null != dateStr && null != targetClass) {
            Date date = null;
            try {
                date = new SimpleDateFormat(ISO8601_FORMATTER_STR).parse(dateStr);
            } catch (ParseException e3) {
                try {
                    date = java.sql.Timestamp.valueOf(dateStr);
                } catch (IllegalArgumentException e) {
                    try {
                        date = java.sql.Date.valueOf(dateStr);
                    } catch (IllegalArgumentException e1) {
                        try {
                            date = java.sql.Time.valueOf(dateStr);
                        } catch (IllegalArgumentException e2) {
                            date = getDateFromString(dateStr);
                        }
                    }
                }
            }
            return castToDate(date, targetClass);
        }
        return null;
    }

    /**
     * get a date from a date string representation in one of the registered formats
     *
     * @param input         the date as string.
     * @param targetClass   Date or Calendar or subclass required
     * @param excludeIndexs excluded pattern index
     * @return Date object ,otherwise null If (null or empty) or correct pattern was not found
     */
    public static <D> D parseAsDate(String input, Class<D> targetClass, int... excludeIndexs) {
        if (null != input && null != targetClass) {
            Date date = getDateFromString(input, null, excludeIndexs);
            return castToDate(date, targetClass);
        }
        return null;
    }

    /**
     * convert {@link Date} to ISO8601 date time format string
     *
     * @param date
     * @return ISO8601 date time format string or null if date is null
     */
    public static String toISO8601String(Date date) {

        return null == date ? null : new SimpleDateFormat(ISO8601_FORMATTER_STR).format(date);
    }

    /**
     * format {@link Datec} to  string
     *
     * @param date
     * @param format date time format string,use ISO8601 format if null
     * @return ISO8601 date time format string or null if date is null
     */
    public static String formatDate(Date date, String format) {
        return null == date ? null : new SimpleDateFormat(null == format ? ISO8601_FORMATTER_STR : format).format(date);
    }

    /**
     * Verify that the string represantes the date with one of the registered formats
     *
     * @param strDate the date as string.
     * @return boolean "true" if the string represantes the date in one of the registed formats.
     */
    public static boolean isDate(String strDate) {
        return null != getDateFromString(strDate);
    }

    /**
     * Verify that the string represantes the date with one of the registered formats
     *
     * @param strDate the date as string.
     * @return boolean "true" if the string represantes the date in one of the registed formats.
     * @since 3.25.0
     */
    public static String patternOf(String strDate) {
        AtomicReference<String> p = new AtomicReference<>();
        getDateFromString(strDate, p);
        return p.get();
    }

    /**
     * 将对象转为指定的日期类型
     *
     * @param <F>         原类型  String,Number,java.util.Date or Calendar or subclass
     * @param <T>         目标类型  java.util.Date or Calendar or subclass
     * @param from
     * @param targetClass
     */
    @SuppressWarnings("unchecked")
    public static <F, T> T castToDate(F from, Class<T> targetClass) {
        if (null != from && null != targetClass) {
            if (targetClass.isInstance(from)) {
                return targetClass.cast(from);
            } else if (from instanceof Date) {
                Date date = (Date) from;
                if (Date.class.isAssignableFrom(targetClass)) {
                    try {
                        return targetClass.getConstructor(long.class).newInstance(date.getTime());
                    } catch (Exception e) {
                        throw new IllegalArgumentException("UNSUPPORTED Date type:" + targetClass.getName(), e);
                    }
                } else if (targetClass == Calendar.class) {
                    Calendar calendar = Calendar.getInstance();
                    calendar.setTime(date);
                    return (T) calendar;
                } else if (Calendar.class.isAssignableFrom(targetClass)) {
                    try {
                        Calendar calendar = (Calendar) targetClass.newInstance();
                        calendar.setTime(date);
                        return (T) calendar;
                    } catch (Exception e) {
                        throw new IllegalArgumentException("UNSUPPORTED Date type:" + targetClass.getName(), e);
                    }
                } else {
                    throw new IllegalArgumentException("UNSUPPORTED Date type:" + targetClass.getName());
                }
            } else if (from instanceof Calendar) {
                return castToDate(((Calendar) from).getTime(), targetClass);
            } else if (from instanceof Number) {
                return castToDate((new Date(((Number) from).longValue())), targetClass);
            } else if (from instanceof String) {
                return castToDate(getDateFromString((String) from, null), targetClass);
            } else {
                throw new IllegalArgumentException("UNSUPPORTED Date type:" + from.getClass().getName());
            }
        }
        return null;
    }
}


反序列化实现

package com.zhz.test.serialization.json.fastjson;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.JSONLexer;
import com.alibaba.fastjson.parser.JSONScanner;
import com.alibaba.fastjson.parser.JSONToken;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;

import java.lang.reflect.Type;
import java.util.Calendar;
import java.util.Date;

/**
 * 日期类型FASTJSON反序列化实现,
 * 相比默认的反序列化器 {@link com.alibaba.fastjson.serializer.DateCodec}  支持更多日期格式
 * @author zhouhengzhe
 */
public class FastjsonDateDeserializer implements ObjectDeserializer {

    @Override
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
        JSONLexer lexer = parser.lexer;
        if(!(type instanceof Class) || !(Date.class.isAssignableFrom((Class)type) || Calendar.class.isAssignableFrom((Class)type))) {
        	throw new IllegalArgumentException("type required Date or Calendar");
        }
        switch(lexer.token()) {
        case JSONToken.LITERAL_INT:{
            long millis = lexer.longValue();
            lexer.nextToken(JSONToken.COMMA);
            try {
            	return (T) DateSupport.castToDate(millis,(Class)type);
			} catch (Exception e) {
				throw new JSONException(e.getMessage(),e);
			}
        }
        case JSONToken.LITERAL_STRING:{
            String strVal = lexer.stringVal();
            lexer.nextToken(JSONToken.COMMA);

            try(JSONScanner iso8601Lexer = new JSONScanner(strVal)){
            	if (iso8601Lexer.scanISO8601DateIfMatch(false)) {
            		Calendar calendar = iso8601Lexer.getCalendar();
            		return (T) DateSupport.castToDate(calendar,(Class)type);
            	}else {
					Object parsed = DateSupport.parseAsDate(strVal, (Class)type,0,1,2,3,4);
					if(parsed != null) {
						return (T) parsed;
					}
					throw new JSONException("FAIL parse date: "+strVal);
				}
            }
        }
        case JSONToken.NULL:
        	lexer.nextToken();
            return null;
        default:
        	throw new JSONException("parse error");
        }
    }
    public int getFastMatchToken() {
        return JSONToken.LITERAL_INT;
    }
}


测试DEMO

    @Test
    public void test6FastjsonDateDeserializer() {
        try {
            String input="\"Tuesday February 27 10:43:27 CST 2024\"";
//        String input="\"2024-02-27 15:01:31+0800\"";
//        String input="\"2024-02-27\"";
//        ParserConfig.global.putDeserializer(Date.class, new FastjsonDateDeserializer());
            ParserConfig.global.putDeserializer(java.sql.Date.class, new FastjsonDateDeserializer());
            Date parsed = JSON.parseObject(input,java.sql.Date.class);
            System.out.println(parsed);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

结果

image.png

5. 注意事项
  • 在实现自定义 ObjectDeserializer 时,要特别注意处理各种可能的 JSON 数据格式和异常情况。

  • 确保你的反序列化器是线程安全的,特别是在多线程环境下使用时。

  • 考虑性能优化,避免在反序列化过程中进行不必要的计算或资源消耗。

package com.zhz.test.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Student implements Serializable {

    private String name;
    private String sex;
    private int age;
}

常用API

//把JSON文本解析为JSONObject或者JSONArray
public static final Object parse(String text); 
 
//把JSON文本解析成JSONObject
public static final JSONObject parseObject(String text)//把JSON文本解析为JavaBean
public static final <T> T parseObject(String text, Class<T> clazz);  
 
//把JSON文本解析成JSONArray。只有字符串是JSON数组的时候才能转,不然会报错。
public static final JSONArray parseArray(String text);  
 
//把JSON文本解析成JavaBean集合
public static final <T> List<T> parseArray(String text, Class<T> clazz);  
 
//将JavaBean序列化为JSON文本 
public static final String toJSONString(Object object); 
 
//将JavaBean序列化为带格式的JSON文本
public static final String toJSONString(Object object, boolean prettyFormat);  
 
//将JavaBean转换为JSONObject或者JSONArray。
public static final Object toJSON(Object javaObject); 

普通版DEMO

引入依赖
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.83_noneautotype</version>
</dependency>
测试demo
/**
 * fastjson测试
 */
@Test
public void testFastjson(){
    Student student = new Student("小明", '男', 18);
    //序列化成JSON格式的字符串
    String jsonStr = JSON.toJSONString(student);
    System.out.println(jsonStr);
    //反序列化成对象
    Student student1 = JSON.parseObject(jsonStr, Student.class);
    System.out.println(student1);
}

结果

image.png

工具类

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83_noneautotype</version>
</dependency>
<dependency>
    <groupId>dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>1.6.1</version>
</dependency>
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
 
/**
 * fastjson工具类
 */
public class FastJsonUtil {
    /**
     * xml字符串转为fastjson中的JSONObject对象
     * @param xml 待转换数据xml字符串
     * @return JSONObject对象
     */
    public static JSONObject xmlToFastJson(String xml){
        JSONObject jsonObject = null;
        try {
            jsonObject = new JSONObject();
            Document document = DocumentHelper.parseText(xml);
            //获取根节点元素对象
            Element root = document.getRootElement();
            iterateNodes(root, jsonObject);
        }catch (Exception e){
            return jsonObject;
        }
        return jsonObject;
    }
 
    /**
     * fastjson中JSONObject对象的转为xml字符串
     * @param jsonStr 待转换json字符串
     * @param escape  是否忽略特殊字符(即特殊字符转xml后追加CDATA)
     * @return xml字符串
     */
    public static String fastJsonToXml(String jsonStr,Boolean escape) {
        try {
            StringBuffer buffer = new StringBuffer();
            //带顺序
            JSONObject json = JSONObject.parseObject(jsonStr, Feature.OrderedField);
            jsonToXmlStr(json,buffer, escape != null && escape);
            return buffer.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }
 
    /**
     * object转JSONObject
     * @param t 待转换的Java对象
     * @return JSONObject对象
     */
    public static <T> JSONObject objectToJSONObject(T  t){
        return  JSONObject.parseObject(JSON.toJSONString(t));
    }
 
    /**
     * json字符串转实体对象
     * @param jsonString json字符串
     * @param clazz 实体对象的类型
     * @return 解析出的实体对象
     */
    public static <T> T jsonStrToEntity(String jsonString,Class<T> clazz){
        return JSONObject.parseObject(jsonString,clazz);
    }
 
    public static void iterateNodes(Element node, JSONObject json) {
        //获取当前元素的名称
        String nodeName = node.getName();
        //判断已遍历的JSON中是否已经有了该元素的名称
        if (json.containsKey(nodeName)) {
            //该元素在同级下有多个
            Object Object = json.get(nodeName);
            JSONArray array;
            if (Object instanceof JSONArray) {
                array = (JSONArray) Object;
            }
            else {
                array = new JSONArray();
                array.add(Object);
            }
            //获取该元素下所有子元素
            List<Element> listElement = node.elements();
            if (listElement.isEmpty()) {
                // 该元素无子元素,获取元素的值
                String nodeValue = node.getTextTrim();
                array.add(nodeValue);
                json.put(nodeName, array);
                return;
            }
            //有子元素
            JSONObject newJson = new JSONObject();
            //遍历所有子元素
            for (Element e : listElement) {
                //递归
                iterateNodes(e, newJson);
            }
            array.add(newJson);
            json.put(nodeName, array);
            return;
        }
        //该元素同级下第一次遍历
        //获取该元素下所有子元素
        List<Element> listElement = node.elements();
        if (listElement.isEmpty()) {
            // 该元素无子元素,获取元素的值
            String nodeValue = node.getTextTrim();
            json.put(nodeName, nodeValue);
            return;
        }
        //有子节点,新建一个JSONObject来存储该节点下子节点的值
        JSONObject object = new JSONObject();
        //遍历所有一级子节点
        for (Element e : listElement) {
            //递归
            iterateNodes(e, object);
        }
        json.put(nodeName, object);
        return;
    }
 
    private static void jsonToXmlStr(JSONObject json,StringBuffer buffer,Boolean isEscape){
        Iterator<Map.Entry<String,Object>> it = json.entrySet().iterator();
        Map.Entry<String,Object> en;
        while(it.hasNext()){
            en = it.next();
            if(en.getKey().startsWith("-")){
                continue;
            }
            if(en.getKey().equals("#text")){
                //直接输出文本
                buffer.append(en.getValue());
                continue;
            }
            if(en.getValue() instanceof JSONObject){
                buffer.append("<").append(en.getKey()).append(getAttr((JSONObject) en.getValue())).append(">");
                JSONObject jo = json.getJSONObject(en.getKey());
                jsonToXmlStr(jo,buffer,isEscape);
                buffer.append("</").append(en.getKey()).append(">");
            }else if(en.getValue() instanceof JSONArray){
                JSONArray jsonArray = json.getJSONArray(en.getKey());
                for (int i = 0; i < jsonArray.size(); i++) {
                    JSONObject jsonobject =  jsonArray.getJSONObject(i);
                    buffer.append("<").append(en.getKey()).append(getAttr(jsonobject)).append(">");
                    jsonToXmlStr(jsonobject,buffer,isEscape);
                    buffer.append("</").append(en.getKey()).append(">");
                }
            }else{
                buffer.append("<").append(en.getKey()).append(">").append(isEscape ? escape(String.valueOf(en.getValue())) : String.valueOf(en.getValue())).append("</").append(en.getKey()).append(">");
            }
        }
    }
 
    /**
     * 拼当前节点属性
     * @param json
     * @return
     */
    private static String getAttr(JSONObject json){
        StringBuilder sb = new StringBuilder();
        for(Map.Entry<String,Object> entity:json.entrySet()){
            if(entity.getKey().startsWith("-")){
                sb.append(" ").append(entity.getKey().substring(1)).append("=\"").append(entity.getValue().toString()).append("\"");
            }
        }
        return sb.toString();
    }
 
    /**
     * 特殊字符pattern
     */
    private static Pattern pattern = Pattern.compile("[<>&\"',]");
 
    /**
     * json转xml中遇到特殊字符对内容追加![CDATA[]]
     * @param string
     * @return
     */
    private static String escape(String string) {
        return pattern.matcher(string).find() ? "<![CDATA[" + string + "]]>" : string;
    }
 
    /**
     * 判断是否是JSON数组
     * @param str JSON字符串
     * @return true或false
     */
    public static boolean isJsonArray(String str) {
        if (str.isEmpty()) {
            return false;
        }
        return isWrap(str.trim(), '[', ']');
    }
 
    public static boolean isWrap(CharSequence str, char prefixChar, char suffixChar) {
        if (null == str) {
            return false;
        }
        return str.charAt(0) == prefixChar && str.charAt(str.length() - 1) == suffixChar;
    }
}

打个广告

本人新搞的个人项目,有意者可到 DDD用户中台 这里购买

可以学习到的体系

  • 项目完全从0到1开始架构,包含前端,后端,架构,服务器,技术管理相关运维知识!

    • 最佳包名设计,项目分层
  • 破冰CRUD,手撕中间件!

    • 基于MybatisPlus封装属于自己的DDD ORM框架

    • 基于Easyexcel封装属于自己的导入导出组件

    • oss对象存储脚手架(阿里云,minio,腾讯云,七牛云等)

    • 邮件脚手架

    • completefuture脚手架

    • redis脚手架

    • xxl-job脚手架

    • 短信脚手架

    • 常用工具类等

  • 传统MVC代码架构弊端的解决方案

    • DDD+CQRS+ES最难架构
  • 结合实际代码的业务场景

    • 多租户单点登录中心

    • 用户中台

    • 消息中心

    • 配置中心

    • 监控设计

  • 程序员的职业规划,人生规划

    • 打工永远没有出路!

    • 打破程序员的35岁魔咒

    • 技术带给你的优势和竞争力【启发】

    • 万物互联网的淘金之路!

技术以外的赚钱路子

可以一起沟通

具体的文章目录

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

购买链接

DDD用户中台

转载请注明出处或者链接地址:https://www.qianduange.cn//article/23972.html
标签
评论
发布的文章
大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!