fastjson&fastjson2(版本:1.2.83_noneautotype)
扩展点
-
Fastjson通过其丰富的扩展点显著增强了用户定制序列化和反序列化行为的灵活性,完美契合了实际开发中的多样化需求。在SpringBoot与SpringCloud的集成环境中,开发者能够利用SerializeFilter和ParserProcess精细地构建复杂的序列化与反序列化逻辑,以应对复杂的数据处理场景。同时,通过调整SerializerFeature和ParserFeature,用户可以轻松控制输出格式与特性,如日期格式、空字段处理等,确保数据交换的准确性和效率。
-
此外,SerializeConfig与ParserConfig作为全局配置工具,进一步简化了配置过程,允许开发者在全局范围内统一设置序列化和反序列化的规则,这不仅减少了代码冗余,还极大地提升了系统的可维护性和可扩展性。这些设计使得Fastjson成为处理JSON数据时的强大工具,有效促进了开发效率与系统性能的提升。
序列化扩展点
SerializeFilter
- SerializeFilter接口及其子接口(如SimplePropertyPreFilter、PropertyPreFilter等)为开发者提供了在序列化过程中精细控制输出内容的能力。通过实现这些接口,用户可以灵活地过滤掉不需要的字段、修改字段值、调整字段的序列化行为等,从而满足特定场景下的数据输出需求。
使用方法:
-
实现SerializeFilter接口或其子接口: 根据需求,选择实现SerializeFilter接口或其提供的子接口(如SimplePropertyPreFilter用于简单的属性预过滤,PropertyPreFilter提供基于属性名称和值的过滤逻辑等)。在实现的类中,通过覆盖相关方法来自定义序列化逻辑。
-
创建SerializeFilter实例: 实例化步骤1中创建的自定义SerializeFilter类,准备将其应用于序列化过程。
-
在序列化时传入自定义的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;
}
@Testpublic void testFastjson2() {Student student = new Student("小明", "男", 18, new Date());//序列化成JSON格式的字符串String jsonStr = JSON.toJSONString(student, new SimplePropertyPreFilter() {@Overridepublic 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);}
我们可以发现name属性已过滤。
SerializerFeature(日期格式化请看此)
- SerializerFeature 枚举提供了丰富的选项,用于在序列化过程中精细控制输出的格式和特性。通过这些特性,开发者可以定制JSON字符串的外观,比如格式化输出、忽略空值字段、输出日期格式等,以满足不同的数据交换和展示需求。
使用方法:
- 在调用序列化方法(如
JSON.toJSONString
)时,可以通过其重载版本传入一个或多个SerializerFeature
枚举值作为参数,以此来指定所需的序列化特性。这些枚举值可以通过逻辑运算符(如|
)组合使用,以同时启用多个特性。
解析SerializerFeature
的属性:
-
QuoteFieldNames:
- 字段名使用双引号括起来,符合 JSON 标准。
-
UseSingleQuotes:
- 使用单引号而不是双引号来括起字段名和字符串值。这不是 JSON 标准,但在某些情况下可能更节省空间或符合特定需求。
-
WriteMapNullValue:
- 写入值为
null
的字段。默认情况下,值为null
的字段不会被序列化。
- 写入值为
-
WriteEnumUsingToString:
- 枚举类型使用
toString()
方法的返回值进行序列化,而不是使用枚举的名字。
- 枚举类型使用
-
WriteEnumUsingName:
- 枚举类型使用其名字(即
name()
方法的返回值)进行序列化。
- 枚举类型使用其名字(即
-
UseISO8601DateFormat:
- 使用 ISO8601 标准的日期格式来序列化日期对象。
-
WriteNullListAsEmpty:
- 将
null
值的List
字段序列化为空数组[]
。
- 将
-
WriteNullStringAsEmpty:
- 将
null
值的字符串字段序列化为空字符串""
。
- 将
-
WriteNullNumberAsZero:
- 将
null
值的数字字段序列化为0
。
- 将
-
WriteNullBooleanAsFalse:
- 将
null
值的布尔字段序列化为false
。
- 将
-
SkipTransientField:
- 跳过
transient
修饰的字段,不进行序列化。
- 跳过
-
SortField:
- 对字段进行排序后再序列化。
-
WriteTabAsSpecial(已弃用):
- 特殊处理 Tab 字符,通常不建议使用。
-
PrettyFormat:
- 格式化输出,使 JSON 字符串更易于阅读,包含缩进和换行。
-
WriteClassName:
- 写入类名信息,便于反序列化时恢复对象的实际类型。
-
DisableCircularReferenceDetect:
- 禁用循环引用检测,避免因为循环引用导致的无限递归。
-
WriteSlashAsSpecial:
- 对斜杠
/
进行特殊处理,通常用于确保生成的 JSON 可以作为 URL 的一部分而不被破坏。
- 对斜杠
-
BrowserCompatible:
- 浏览器兼容模式,处理一些与浏览器相关的特殊字符。
-
WriteDateUseDateFormat:
- 使用自定义的日期格式来序列化日期对象。
-
NotWriteRootClassName:
- 不写入根对象的类名,即使启用了
WriteClassName
。
- 不写入根对象的类名,即使启用了
-
DisableCheckSpecialChar(已弃用):
- 禁用特殊字符检查,通常不建议使用以避免潜在的安全问题。
-
BeanToArray:
- 将 Java Bean 序列化为数组形式,而不是默认的键值对形式。
-
WriteNonStringKeyAsString:
- 将非字符串类型的 Key 也序列化为字符串。
-
NotWriteDefaultValue:
- 不写入默认值,即如果字段的值等于其类型的默认值(如数字为 0,布尔为 false),则不进行序列化。
-
BrowserSecure:
- 浏览器安全模式,防止 XSS 攻击等安全问题。
-
IgnoreNonFieldGetter:
- 忽略非字段的 getter 方法,即只序列化 Java Bean 的字段,不序列化通过 getter 方法暴露的属性(如果该 getter 不是对应某个字段的)。
-
WriteNonStringValueAsString:
- 将非字符串类型的值也强制序列化为字符串。
-
IgnoreErrorGetter:
- 在序列化过程中,如果 getter 方法抛出异常,则忽略该异常并继续序列化其他字段。
-
WriteBigDecimalAsPlain:
- 将
BigDecimal
类型的值序列化为无科学计数法的普通数字字符串。
- 将
-
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);
}
SerializeConfig
用途
-
全局配置序列化:提供统一的配置入口,管理序列化过程中的各种行为。
-
自定义序列化器:为特定类型定义自己的序列化逻辑。
-
日期格式设置:指定日期对象在序列化时应该遵循的格式。
使用方法
-
创建
SerializeConfig
实例:首先,需要创建一个SerializeConfig
对象。 -
配置选项:通过
SerializeConfig
提供的各种方法,配置所需的序列化选项,如添加自定义序列化器、设置日期格式等。 -
应用配置:在调用
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 类,包含姓名、地址、年龄和日期字段(略)
}
自定义序列化器(Serializer)
允许用户自定义序列化器,用于控制特定类型的序列化过程。用户需要实现ObjectSerializer
接口,并重写write
方法。
@Datapublic static class CustomObject {private String name;private int value;// 构造方法、getter和setter省略}public static class CustomSerializer implements ObjectSerializer {@Overridepublic 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); // 输出自定义序列化后的结果}
反序列化扩展点
ParseProcess
用途: 在反序列化过程中,ParseProcess
接口允许用户执行额外的逻辑操作,比如对反序列化后的对象进行修改、验证或添加额外的处理步骤。
使用方法
-
实现
ParseProcess
接口: 创建一个类,实现ParseProcess
接口,并重写其中的方法。在这个方法里,你可以定义反序列化完成后需要执行的额外逻辑。 -
应用
ParseProcess
实例: 在调用JSON.parseObject
或其他反序列化方法时,通过其重载版本传入你实现的ParseProcess
实例。这样,在反序列化过程完成后,会自动调用你的自定义逻辑。 -
ParseProcess
有两个实现类,分别为:ExtraProcessor
,ExtraTypeProvider
,两者作用不一样,下面是两个实现类的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;}@Overridepublic 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"));}}
使用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;}@Overridepublic 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;}}
ParserFeature
用途
Feature
枚举在 Fastjson 中用于控制反序列化的行为和特性。通过指定不同的Feature
枚举值,用户可以定制反序列化过程中的多种行为,如是否忽略未知的字段、是否允许单引号包围的字符串等。
使用方法:
- 在进行反序列化操作时,用户可以通过
JSON.parseObject
等方法的重载版本,传入Feature
枚举值来控制反序列化的特性。这些枚举值作为额外的参数,指导 Fastjson 如何解析和转换 JSON 字符串为 Java 对象。
解析Feature枚举的属性:
-
AutoCloseSource:
- 自动关闭JSON源输入流。在完成反序列化后,会尝试关闭输入流,如
InputStream
。
- 自动关闭JSON源输入流。在完成反序列化后,会尝试关闭输入流,如
-
AllowComment:
- 允许JSON字符串中包含注释。标准的JSON是不支持注释的,开启此特性后,可以解析包含
//
或/* */
注释的JSON。
- 允许JSON字符串中包含注释。标准的JSON是不支持注释的,开启此特性后,可以解析包含
-
AllowUnQuotedFieldNames:
- 允许字段名不使用双引号包围。标准的JSON要求字段名必须使用双引号,但开启此特性后,可以解析不使用双引号的字段名。
-
AllowSingleQuotes:
- 允许使用单引号包围字符串值。标准的JSON要求字符串值使用双引号,但开启此特性后,也支持单引号。
-
InternFieldNames:
- 对字段名进行字符串驻留(intern)。这有助于减少内存占用,当有很多相同字段名的JSON对象时。
-
AllowISO8601DateFormat:
- 允许使用ISO8601格式的日期字符串。例如,
2023-04-01T12:00:00Z
。
- 允许使用ISO8601格式的日期字符串。例如,
-
AllowArbitraryCommas:
- 允许JSON对象中存在多余的逗号。例如,
{"a":1,,"b":2}
中的逗号。
- 允许JSON对象中存在多余的逗号。例如,
-
UseBigDecimal:
- 使用
BigDecimal
来解析浮点数,而不是double
。这可以提供更精确的数值表示。
- 使用
-
IgnoreNotMatch:
- 忽略不匹配的字段。当JSON中的字段与Java对象的字段不匹配时,不会抛出异常。
-
SortFeidFastMatch (可能是
SortFieldFastMatch
的拼写错误):- 对字段进行排序以快速匹配。这可能有助于优化某些情况下的反序列化性能。
-
DisableASM:
- 禁用ASM(Java字节码操作和分析框架)优化。在某些情况下,为了避免与ASM相关的问题,可以禁用它。
-
DisableCircularReferenceDetect:
- 禁用循环引用检测。当JSON中存在循环引用时,不会抛出异常。
-
InitStringFieldAsEmpty:
- 将字符串字段初始化为空字符串,而不是
null
。
- 将字符串字段初始化为空字符串,而不是
-
SupportArrayToBean:
- 支持将JSON数组反序列化为Java Bean。通常,JSON数组会转换为Java的
List
或array
,但开启此特性后,可以将其转换为具有特定字段的Java对象。
- 支持将JSON数组反序列化为Java Bean。通常,JSON数组会转换为Java的
-
OrderedField:
- 保持字段的顺序。在反序列化时,Java对象的字段将按照JSON中出现的顺序进行设置。
-
DisableSpecialKeyDetect:
- 禁用特殊键检测。例如,不处理
$ref
等用于处理循环引用的特殊键。
- 禁用特殊键检测。例如,不处理
-
UseObjectArray:
- 使用
Object[]
来接收反序列化的数组,而不是具体类型的数组。
- 使用
-
SupportNonPublicField:
- 支持反序列化到非公共字段(例如,私有字段)。
-
IgnoreAutoType:
- 忽略自动类型识别。这可能与安全性相关,以防止利用类型识别进行攻击。
-
DisableFieldSmartMatch:
- 禁用字段智能匹配。这可能影响字段名与Java对象属性名的匹配逻辑。
-
SupportAutoType:
- 支持自动类型识别。允许在反序列化时自动确定对象的类型。
-
NonStringKeyAsString:
- 将非字符串类型的键也作为字符串处理。
-
CustomMapDeserializer:
- 使用自定义的
Map
反序列化器。允许用户定义如何反序列化JSON对象为Map
类型。
- 使用自定义的
-
ErrorOnEnumNotMatch:
- 当枚举值不匹配时抛出错误。如果JSON中的枚举值与Java枚举类中的值不匹配,将抛出异常。
-
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
通常涉及以下几个步骤:
-
定义类:创建一个类,实现
ObjectDeserializer
接口。 -
实现方法:根据需求实现接口中的方法,特别是
deserialze
方法。 -
注册反序列化器:将自定义的反序列化器注册到 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
@Testpublic 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();}}
结果
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);
}