工具类,导入jar即可使用:
(FastJson有Jsonobject,Json,Jsonarray类,Jackson只有Objectmapper类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182private final static ObjectMapper objectMapper = new ObjectMapper(); private JacksonUtils() { } public static ObjectMapper getInstance() { return objectMapper; } /** * javaBean、列表数组转换为json字符串 */ public static String obj2json(Object obj) throws Exception { return objectMapper.writeValueAsString(obj); } /** * javaBean、列表数组转换为json字符串,忽略空值 */ public static String obj2jsonIgnoreNull(Object obj) throws Exception { ObjectMapper mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return mapper.writeValueAsString(obj); } /** * json 转JavaBean */ public static <T> T json2pojo(String jsonString, Class<T> clazz) throws Exception { objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); return objectMapper.readValue(jsonString, clazz); } /** * json字符串转换为map */ public static <T> Map<String, Object> json2map(String jsonString) throws Exception { ObjectMapper mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return mapper.readValue(jsonString, Map.class); } /** * json字符串转换为map */ public static <T> Map<String, T> json2map(String jsonString, Class<T> clazz) throws Exception { Map<String, Map<String, Object>> map = objectMapper.readValue(jsonString, new TypeReference<Map<String, T>>() { }); Map<String, T> result = new HashMap<String, T>(); for (Map.Entry<String, Map<String, Object>> entry : map.entrySet()) { result.put(entry.getKey(), map2pojo(entry.getValue(), clazz)); } return result; } /** * 深度转换json成map * * @param json * @return */ public static Map<String, Object> json2mapDeeply(String json) throws Exception { return json2MapRecursion(json, objectMapper); } /** * 把json解析成list,如果list内部的元素存在jsonString,继续解析 * * @param json * @param mapper 解析工具 * @return * @throws Exception */ private static List<Object> json2ListRecursion(String json, ObjectMapper mapper) throws Exception { if (json == null) { return null; } List<Object> list = mapper.readValue(json, List.class); for (Object obj : list) { if (obj != null && obj instanceof String) { String str = (String) obj; if (str.startsWith("[")) { obj = json2ListRecursion(str, mapper); } else if (obj.toString().startsWith("{")) { obj = json2MapRecursion(str, mapper); } } } return list; } /** * 把json解析成map,如果map内部的value存在jsonString,继续解析 * * @param json * @param mapper * @return * @throws Exception */ private static Map<String, Object> json2MapRecursion(String json, ObjectMapper mapper) throws Exception { if (json == null) { return null; } Map<String, Object> map = mapper.readValue(json, Map.class); for (Map.Entry<String, Object> entry : map.entrySet()) { Object obj = entry.getValue(); if (obj != null && obj instanceof String) { String str = ((String) obj); if (str.startsWith("[")) { List<?> list = json2ListRecursion(str, mapper); map.put(entry.getKey(), list); } else if (str.startsWith("{")) { Map<String, Object> mapRecursion = json2MapRecursion(str, mapper); map.put(entry.getKey(), mapRecursion); } } } return map; } /** * 与javaBean json数组字符串转换为列表 */ public static <T> List<T> json2list(String jsonArrayStr, Class<T> clazz) throws Exception { JavaType javaType = getCollectionType(ArrayList.class, clazz); List<T> lst = (List<T>) objectMapper.readValue(jsonArrayStr, javaType); return lst; } /** * 获取泛型的Collection Type * * @param collectionClass 泛型的Collection * @param elementClasses 元素类 * @return JavaType Java类型 * @since 1.0 */ public static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) { return objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses); } /** * map 转JavaBean */ public static <T> T map2pojo(Map map, Class<T> clazz) { return objectMapper.convertValue(map, clazz); } /** * map 转json * * @param map * @return */ public static String mapToJson(Map map) { try { return objectMapper.writeValueAsString(map); } catch (Exception e) { e.printStackTrace(); } return ""; } /** * map 转JavaBean */ public static <T> T obj2pojo(Object obj, Class<T> clazz) { return objectMapper.convertValue(obj, clazz); }
该博客主要讲3点:
1、统一时间格式可以使用注解
@JsonFormat也可以在spring配置文件中配置全局的格式
2、说了一些
Jackson在Spring Boot框架中的配置项,我们应该如何配置3、讲了一些注解:序列化注解,反序列化注解,属性注解,常规注解,我们最常用到的是@JsonProperty注解和@JsonFormat注解
4、springboot中对加了@RestController或者@Controller+@ResponseBody注解的方法的返回值默认是Json格式,所以,对date类型的数据,在返回浏览器端时,会被springboot默认的Jackson框架转换,而Jackson框架默认的时区GMT(相对于中国是少了8小时)。所以最终返回到前端结果是相差8小时
解决办法:
1:只要在url后面加上&serverTimezone=GMT%2b8就完美解决
2:实体类的属性上添加注解@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
Spring Boot框架中使用Jackson的处理总结
1.前言
通常我们在使用Spring Boot框架时,如果没有特别指定接口的序列化类型,则会使用Spring Boot框架默认集成的Jackson框架进行处理,通过Jackson框架将服务端响应的数据序列化成JSON格式的数据。
本文主要针对在Spring Boot框架中使用Jackson进行处理的经验进行总结,同时也结合在实际开发场景中碰到的问题以及解决方案进行陈述。
本文涉及到的源码地址:codeInAction: 代码实战集合,经验总结输出
PS:目前市面上针对JSON序列化的框架很多,比较出名的就是Jackson、Gson、FastJson。如果开发者对序列化框架没有特别的要求的情况下,个人建议是直接使用Spring Boot框架默认集成的Jackson,没有必要进行更换。
2.统一序列化时间格式
在我们的接口中,针对时间类型的字段序列化是最常见的需求之一,一般前后端开发人员会针对时间字段统一进行约束,这样有助于在编码开发时,统一编码规范。
在Spring Boot框架中,如果使用Jackson处理框架,并且没有任何配置的情况下,Jackson针对不同时间类型字段,序列化的格式也会不尽相同。
先来看一个简单示例,User.java
实体类编码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public class User { private String name; private Integer age; private LocalDateTime birthday; private Date studyDate; private LocalDate workDate; private Calendar firstWorkDate; public static User buildOne(){ User user=new User(); LocalDateTime now=LocalDateTime.now(); user.setWorkDate(now.plusYears(25).toLocalDate()); user.setStudyDate(Date.from(now.plusYears(5).atZone(ZoneId.systemDefault()).toInstant())); user.setName("姓名-"+RandomUtil.randomString(5)); user.setAge(RandomUtil.randomInt(0,100)); user.setBirthday(now); user.setFirstWorkDate(Calendar.getInstance()); return user; } //getter and setter... }
接口代码层也很简单,返回一个User的实体对象即可,代码如下:
1
2
3
4
5
6
7
8
9
10@RestController public class UserApplication { @GetMapping("/queryOne") public ResponseEntity<User> queryOne(){ return ResponseEntity.ok(User.buildOne()); } }
如果我们对框架代码没有任何的配置,此时我们通过调用接口/queryOne
,拿到的返回结果数据如下图:
Jackson序列化框架针对四个不同的时间类型字段,序列化处理的操作是不同的,如果我们对时间字段有格式化的要求时,我们应该如何处理呢?
2.1 通过@JsonFormat
注解
最直接也是最简单的一种方式,是我们通过使用Jackson提供的@JsonFormat
注解,对需要格式化处理的时间字段进行标注,在@JsonFormat
注解中写上我们的时间格式化字符,User.java
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class User { private String name; private Integer age; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime birthday; private Date studyDate; private LocalDate workDate; private Calendar firstWorkDate; //getter and setter... }
此时,我们再通过调用接口,拿到的返回结果如下图:
通过对birthday
字段标注@JsonFormat
注解,最终Jackson框架会将该字段序列化为我们标注的格式类型。
2.2 配置全局application.yml
通过@JsonFormat
注解的方式虽然能解决问题,但是我们在实际的开发当中,涉及到的时间字段会非常多,如果全部都用注解的方式对项目中的时间字段进行标注,那开发的工作量也会很大,并且多团队一起协同编码时,难免会存在遗漏的情况,因此,@JsonFormat
注解只适用于针对特定的接口,特定的场景下,对序列化响应的时间字段进行约束,而在全局的角度来看,开发者应该考虑通过在application.yml
配置文件中进行全局配置
针对Spring Boot框架中Jackson的全局配置,我们在application.yml
进行配置时,IDEA等编辑器会给出相应的提示,包含的属性如下图:
开发者可以通过org.springframework.boot.autoconfigure.jackson.JacksonProperties.java
查看所有配置的源码信息
配置属性 | 说明 |
---|---|
date-format | 日期字段格式化,例如:yyyy-MM-dd HH:mm:ss |
针对日期字段的格式化处理,我们只需要使用date-format
属性进行配置即可,application.yml
配置如下:
1
2
3
4spring: jackson: date-format: yyyy-MM-dd HH:mm:ss
当然,如果有必要的话,还需要配置time-zone
时区属性,不过该属性不配置的情况下,Jackson会使用系统默认时区。
我们从Spring Boot的源码中可以看到对Jackson的时间处理逻辑,JacksonAutoConfiguration.java
中部分代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24private void configureDateFormat(Jackson2ObjectMapperBuilder builder) { // We support a fully qualified class name extending DateFormat or a date // pattern string value String dateFormat = this.jacksonProperties.getDateFormat(); if (dateFormat != null) { try { Class<?> dateFormatClass = ClassUtils.forName(dateFormat, null); builder.dateFormat((DateFormat) BeanUtils.instantiateClass(dateFormatClass)); } catch (ClassNotFoundException ex) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat); // Since Jackson 2.6.3 we always need to set a TimeZone (see // gh-4170). If none in our properties fallback to the Jackson's // default TimeZone timeZone = this.jacksonProperties.getTimeZone(); if (timeZone == null) { timeZone = new ObjectMapper().getSerializationConfig().getTimeZone(); } simpleDateFormat.setTimeZone(timeZone); builder.dateFormat(simpleDateFormat); } } }
从上面的代码中,我们可以看到的处理逻辑:
- 从yml配置文件中拿到
dateFormat
属性字段 - 首先通过
ClassUtils.forName
方法来判断开发者配置的是否是格式化类,如果配置的是格式化类,则直接配置dateFormat
属性 - 类找不到的情况下,捕获
ClassNotFoundException
异常,默认使用JDK自带的SimpleDateFormat
类进行初始化
最终,我们在application.yml
配置文件中配置了全局的Jackson针对日期处理的格式化信息,此时我们再看/queryOne
接口响应的内容是什么情况呢?如下图:
从图中我们可以发现,除了LocalDate
类型的字段,包含时分秒类型的日期类型:LocalDateTime
、Date
、Calendar
全部按照我们的要求将日期序列化成了yyyy-MM-dd HH:mm:ss
格式,达到了我们的要求。
3.Jackson在Spring Boot框架中的配置选项
在上面的时间字段序列化处理,我们已经知道了如何配置,那么在Spring Boot的框架中,针对Jackson的各个配置项主要包含哪些呢?我们通过IDEA的提示可以看到,配置如下图:
在上面的12个属性中,每个属性的配置都会对Jackson产生不同的效果,接下来,我们逐一详解每个属性配置的作用
3.1 date-format日期格式化
date-format
在前面我们已经知道了该属性的作用,主要是针对日期字段的格式化
3.2 time-zone时区
time-zone
字段也是和日期字段类型,使用不同的时区,最终日期类型字段响应的结果会不一样
时区的表示方法有两种:
- 指定时区的名称,例如:
Asia/Shanghai
,America/Los_Angeles
- 通过格林威治平时
GMT
针对时分秒做+
或者-
自定义操作
通过指定时区的名称,假设我们指定当前的项目是America/Los_Angeles
,那么接口响应的数据是什么效果呢?
PS:时区名称如果不是很清楚的话,一般在Linux服务器的
/usr/share/zoneinfo
目录可以进行查看,如下图:
application.yml
:
1
2
3
4
5spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: America/Los_Angeles
效果图如下:
我们在结合代码来分析:
1
2
3
4
5
6
7
8
9
10
11
12
13//User.java public static User buildOne(){ User user=new User(); LocalDateTime now=LocalDateTime.now(); user.setWorkDate(now.plusYears(25).toLocalDate()); user.setStudyDate(Date.from(now.plusYears(5).atZone(ZoneId.systemDefault()).toInstant())); user.setName("姓名-"+RandomUtil.randomString(5)); user.setAge(RandomUtil.randomInt(0,100)); user.setBirthday(now); user.setFirstWorkDate(Calendar.getInstance()); return user; }
由于洛杉矶时区与上海时区相差16个小时,因此,Jackson框架针对日期的序列化时,分别做了不同类型的处理,但我们也能看出差别
LocalDateTime
、LocalDate
类型的字段,Jackson的时区设置不会对该字段产生影响(因为这两个日期类型自带时区属性)Date
、Calendar
类型的字段受Jackson序列化框架的时区设置影响
另外一种方式是通过格林威治平时(GMT)做加减法,主要有两种格式支持:
GMT+HHMM
或者GMT-HHMM
或者GMT+H
:其中HH代表的是小时数,MM代表的是分钟数,取值范围是0-9,例如我们常见的GMT+8代表东八区,也就是北京时间GMT+HH:MM
或者GMT-HH:MM
:其中HH代表的是小时数,MM代表的是分钟数,取值范围是0-9,和上面意思差不多
可以自己写测试代码进行测试,示例如下:
1
2
3
4
5
6
7
8
9
10public class TimeTest { public static void main(String[] args) { LocalDateTime localDateTime=LocalDateTime.now(); DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); System.out.println(localDateTime.format(dateTimeFormatter)); System.out.println(LocalDateTime.now(ZoneId.of("GMT+0901")).format(dateTimeFormatter)); System.out.println(LocalDateTime.now(ZoneId.of("GMT+09:01")).format(dateTimeFormatter)); } }
3.3 locale本地化
JSON序列化时Locale的变量设置
3.4 visibility访问级别
Jackson支持从私有字段中读取值,但是默认情况下不这样做,如果我们的项目中存在不同的序列化反序列化需求,那么我们可以在配置文件中对visibility
进行配置
我们将上面User.java
代码中的name属性的get方法修饰符从public
变更为private
,其他字段保持不变
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class User { private String name; private Integer age; private Date nowDate; private LocalDateTime birthday; private Date studyDate; private LocalDate workDate; private Calendar firstWorkDate; //getter方法修饰符从public修改为private private String getName() { return name; } //other setter and getter }
此时,我们通过调用/queryOne
接口响应结果如下:
从结果中我们可以看到,由于我们将name属性的getter
方法设置为了private
,因此jackson在序列化时,没有拿到该字段
此时,我们再修改application.yml
的配置,如下:
1
2
3
4
5spring: jackson: visibility: getter: any
我们通过将getter
设置为any
级别的类型,再调用/queryOne
接口,响应结果如下:
从图中可以看出,jackson序列化结果中又出现了name属性,这代表即使name字段的属性和getter
方法都是private
,但是jackson还是获取到了该成员变量的值,并且进行了序列化处理。
通过设置visibility
属性即可达到上面的效果。开发者根据自己的需要自行进行选择。
3.5 property-naming-strategy属性命名策略
通常比较常见的我们针对java代码中的实体类属性一般都是驼峰命名法(Camel-Case),但是Jackson序列化框架也提供了更多的序列化策略,而property-naming-strategy
就是配置该属性的。
先来看Spring Boot框架如何配置jackson的命名策略
JacksonAutoConfiguration.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15private void configurePropertyNamingStrategyField(Jackson2ObjectMapperBuilder builder, String fieldName) { // Find the field (this way we automatically support new constants // that may be added by Jackson in the future) Field field = ReflectionUtils.findField(PropertyNamingStrategy.class, fieldName, PropertyNamingStrategy.class); Assert.notNull(field, () -> "Constant named '" + fieldName + "' not found on " + PropertyNamingStrategy.class.getName()); try { builder.propertyNamingStrategy((PropertyNamingStrategy) field.get(null)); } catch (Exception ex) { throw new IllegalStateException(ex); } }
通过反射,直接获取PropertyNamingStrategy
类中的成员变量的值
PropertyNamingStrategy
定义了Jackson(2.11.4
)框架中的命名策略常量成员变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65package com.fasterxml.jackson.databind; //other import public class PropertyNamingStrategy // NOTE: was abstract until 2.7 implements java.io.Serializable { /** * Naming convention used in languages like C, where words are in lower-case * letters, separated by underscores. * See {@link SnakeCaseStrategy} for details. * * @since 2.7 (was formerly called {@link #CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES}) */ public static final PropertyNamingStrategy SNAKE_CASE = new SnakeCaseStrategy(); /** * Naming convention used in languages like Pascal, where words are capitalized * and no separator is used between words. * See {@link PascalCaseStrategy} for details. * * @since 2.7 (was formerly called {@link #PASCAL_CASE_TO_CAMEL_CASE}) */ public static final PropertyNamingStrategy UPPER_CAMEL_CASE = new UpperCamelCaseStrategy(); /** * Naming convention used in Java, where words other than first are capitalized * and no separator is used between words. Since this is the native Java naming convention, * naming strategy will not do any transformation between names in data (JSON) and * POJOS. * * @since 2.7 (was formerly called {@link #PASCAL_CASE_TO_CAMEL_CASE}) */ public static final PropertyNamingStrategy LOWER_CAMEL_CASE = new PropertyNamingStrategy(); /** * Naming convention in which all words of the logical name are in lower case, and * no separator is used between words. * See {@link LowerCaseStrategy} for details. * * @since 2.4 */ public static final PropertyNamingStrategy LOWER_CASE = new LowerCaseStrategy(); /** * Naming convention used in languages like Lisp, where words are in lower-case * letters, separated by hyphens. * See {@link KebabCaseStrategy} for details. * * @since 2.7 */ public static final PropertyNamingStrategy KEBAB_CASE = new KebabCaseStrategy(); /** * Naming convention widely used as configuration properties name, where words are in * lower-case letters, separated by dots. * See {@link LowerDotCaseStrategy} for details. * * @since 2.10 */ public static final PropertyNamingStrategy LOWER_DOT_CASE = new LowerDotCaseStrategy(); //others... }
从源码中我们可以看到,有六种策略供我们进行配置,配置示例如下:
1
2
3
4
5
6
7
8
9spring: jackson: date-format: yyyy-MM-dd HH:mm:ss locale: zh_CN time-zone: GMT+8 visibility: getter: any property-naming-strategy: LOWER_CAMEL_CASE
SNAKE_CASE
SNAKE_CASE
主要包含的规则,详见SnakeCaseStrategy:
- java属性名称中所有大写的字符都会转换为两个字符,下划线和该字符的小写形式,例如
userName
会转换为user_name
,对于连续性的大写字符,近第一个进行下划线转换,后面的大小字符则是小写,例如theWWW
会转换为the_www
- 对于首字母大写的情况,近转成小写,例如:
Results
会转换为results
,并不会转换为_results
- 针对属性中已经包含下划线的情况,仅做小写转换处理
- 下划线出现在首位的情况下,会被去除处理,例如属性名:
_user
会被转换为user
真实效果如下图:
UPPER_CAMEL_CASE
UPPER_CAMEL_CASE
顾名思义,驼峰命名法的规则,只是首字母会转换为大写,详见UpperCamelCaseStrategy
真实效果图如下:
LOWER_CAMEL_CASE
LOWER_CAMEL_CASE
效果和UPPER_CAMEL_CASE
正好相反,其首字母会变成小写,详见LowerCamelCaseStrategy
效果图如下:
LOWER_CASE
LOWER_CASE
从命名来看很明显,将属性名 全部转为小写,详见LowerCaseStrategy
KEBAB_CASE
KEBAB_CASE
策略和SNAKE_CASE
规则类似,只是下划线变成了横线-
,详见KebabCaseStrategy
效果图如下:
LOWER_DOT_CASE
LOWER_DOT_CASE
策略和KEBAB_CASE
规则相似,只是由横线变成了点.
,详见LowerDotCaseStrategy
效果图如下:
总结:看了上面这么多属性名称的策略,其实每一种类型只是不同的场景下才需要,如果上面jackson给定的默认策略名称无法满足,我们从源码中也能看到,通过自定义实现类,也能满足企业的个性化需求,非常方便。
3.6 mapper通用功能开关配置
mapper
属性是一个Map类型,主要是针对MapperFeature
定义开关属性,是否启用这些特性
1
2
3
4
5/** * Jackson general purpose on/off features. */ private final Map<MapperFeature, Boolean> mapper = new EnumMap<>(MapperFeature.class);
在MapperFeature.java
中,我们可以跟踪源码来看:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/** * Enumeration that defines simple on/off features to set * for {@link ObjectMapper}, and accessible (but not changeable) * via {@link ObjectReader} and {@link ObjectWriter} (as well as * through various convenience methods through context objects). *<p> * Note that in addition to being only mutable via {@link ObjectMapper}, * changes only take effect when done <b>before any serialization or * deserialization</b> calls -- that is, caller must follow * "configure-then-use" pattern. */ public enum MapperFeature implements ConfigFeature { //....... }
MapperFeature
是一个枚举类型,对当前jackson的一些特性通过枚举变量的方式来定义开关属性,也是方便使用者来使用的。
主要包含以下枚举变量:
USE_ANNOTATIONS
:USE_GETTERS_AS_SETTERS
PROPAGATE_TRANSIENT_MARKER
AUTO_DETECT_CREATORS
AUTO_DETECT_FIELDS
AUTO_DETECT_GETTERS
AUTO_DETECT_IS_GETTERS
AUTO_DETECT_SETTERS
REQUIRE_SETTERS_FOR_GETTERS
ALLOW_FINAL_FIELDS_AS_MUTATORS
INFER_PROPERTY_MUTATORS
INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES
CAN_OVERRIDE_ACCESS_MODIFIERS
OVERRIDE_PUBLIC_ACCESS_MODIFIERS
USE_STATIC_TYPING
USE_BASE_TYPE_AS_DEFAULT_IMPL
DEFAULT_VIEW_INCLUSION
SORT_PROPERTIES_ALPHABETICALLY
ACCEPT_CASE_INSENSITIVE_PROPERTIES
ACCEPT_CASE_INSENSITIVE_ENUMS
ACCEPT_CASE_INSENSITIVE_VALUES
USE_WRAPPER_NAME_AS_PROPERTY_NAME
USE_STD_BEAN_NAMING
ALLOW_EXPLICIT_PROPERTY_RENAMING
ALLOW_COERCION_OF_SCALARS
IGNORE_DUPLICATE_MODULE_REGISTRATIONS
IGNORE_MERGE_FOR_UNMERGEABLE
BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES
3.7 serialization序列化特性开关配置
serialization
属性同mapper
类似,也是一个Map类型的属性
1
2
3
4
5/** * Jackson on/off features that affect the way Java objects are serialized. */ private final Map<SerializationFeature, Boolean> serialization = new EnumMap<>(SerializationFeature.class);
3.8 deserialization反序列化开关配置
deserialization
反序列化配置
1
2
3
4
5/** * Jackson on/off features that affect the way Java objects are deserialized. */ private final Map<DeserializationFeature, Boolean> deserialization = new EnumMap<>(DeserializationFeature.class);
3.9 parser配置
3.10 generator配置
3.11 defaultPropertyInclusion序列化包含的属性配置
该属性是一个枚举配置,主要包含:
ALWAYS
:顾名思义,始终包含,和属性的值无关NON_NULL
:值非空的属性才会包含属性NON_ABSENT
:值非空的属性,或者Optional
类型的属性非空NON_EMPTY
: 空值的属性不包含NON_DEFAULT
:不使用jackson的默认规则对该字段进行序列化,详见示例CUSTOM
:自定义规则USE_DEFAULTS
:配置使用该规则的属性字段,将会优先使用class上的注解规则,否则会使用全局的序列化规则,详见示例
CUSTOM
自定义规则是需要开发者在属性字段上使用@JsonInclude
注解,并且指定valueFilter
属性,该属性需要传递一个Class
,示例如下:
1
2
3
4
5//User.java //指定value级别是CUSTOM @JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = StringFilter.class) private String name;
StringFilter
则是判断非空的依据,该依据由开发者自己定义,返回true
将会被排除,false
则不会排除,示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14//自定义非空判断规则 public class StringFilter { @Override public boolean equals(Object other) { if (other == null) { // Filter null's. return true; } // Filter "custom_string". return "custom_string".equals(other); } }
4.Spring Boot针对Jackson的约定配置做的事情
在前面的文章中,我们已经详细的了解了Jackson在Spring Boot框架中的各个配置项,那么Spring Boot针对Jackson框架在约定配置时会做哪些事情呢?
在Spring Boot的spring-boot-autoconfigure-x.x.jar
包中,我们可以看到Spring Boot框架针对jackson的处理源码,如下图:
主要包含三个类:
- JacksonProperties:Spring Boot框架提供jackson的配置属性类,即开发者在
application.yml
配置文件中的配置项属性 - JacksonAutoConfiguration:Jackson的默认注入配置类
- Jackson2ObjectMapperBuilderCustomizer:自定义用于注入jackson的配置辅助接口
核心类是JacksonAutoConfiguration.java
,该类是Spring Boot框架将Jackson相关实体Bean注入Spring容器的关键配置类。其主要作用:
- 注入Jackson的
ObjectMapper
实体Bean到Spring容器中 - 注入
ParameterNamesModule
实体Bean到Spring容器中 - 注入
Jackson2ObjectMapperBuilder
实体Bean - 注入
JsonComponentModule
实体Bean - 注入
StandardJackson2ObjectMapperBuilderCustomizer
实体Bean,该类是上面Jackson2ObjectMapperBuilderCustomizer
的实现类,主要用于接收JacksonProperties
属性,将Jackson的外部配置属性接收,然后最终执行customize
方法,构建ObjectMapper
所需要的Jackson2ObjectMapperBuilder
属性,最终为ObjectMapper
属性赋值准备
源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121@Configuration(proxyBeanMethods = false) @ConditionalOnClass(ObjectMapper.class) public class JacksonAutoConfiguration { private static final Map<?, Boolean> FEATURE_DEFAULTS; static { Map<Object, Boolean> featureDefaults = new HashMap<>(); featureDefaults.put(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); featureDefaults.put(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false); FEATURE_DEFAULTS = Collections.unmodifiableMap(featureDefaults); } @Bean public JsonComponentModule jsonComponentModule() { return new JsonComponentModule(); } @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Jackson2ObjectMapperBuilder.class) static class JacksonObjectMapperConfiguration { @Bean @Primary @ConditionalOnMissingBean ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { return builder.createXmlMapper(false).build(); } } @Configuration(proxyBeanMethods = false) @ConditionalOnClass(ParameterNamesModule.class) static class ParameterNamesModuleConfiguration { @Bean @ConditionalOnMissingBean ParameterNamesModule parameterNamesModule() { return new ParameterNamesModule(JsonCreator.Mode.DEFAULT); } } @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Jackson2ObjectMapperBuilder.class) static class JacksonObjectMapperBuilderConfiguration { @Bean @Scope("prototype") @ConditionalOnMissingBean Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext, List<Jackson2ObjectMapperBuilderCustomizer> customizers) { Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); builder.applicationContext(applicationContext); customize(builder, customizers); return builder; } private void customize(Jackson2ObjectMapperBuilder builder, List<Jackson2ObjectMapperBuilderCustomizer> customizers) { for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) { customizer.customize(builder); } } } @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Jackson2ObjectMapperBuilder.class) @EnableConfigurationProperties(JacksonProperties.class) static class Jackson2ObjectMapperBuilderCustomizerConfiguration { @Bean StandardJackson2ObjectMapperBuilderCustomizer standardJacksonObjectMapperBuilderCustomizer( ApplicationContext applicationContext, JacksonProperties jacksonProperties) { return new StandardJackson2ObjectMapperBuilderCustomizer(applicationContext, jacksonProperties); } static final class StandardJackson2ObjectMapperBuilderCustomizer implements Jackson2ObjectMapperBuilderCustomizer, Ordered { private final ApplicationContext applicationContext; private final JacksonProperties jacksonProperties; StandardJackson2ObjectMapperBuilderCustomizer(ApplicationContext applicationContext, JacksonProperties jacksonProperties) { this.applicationContext = applicationContext; this.jacksonProperties = jacksonProperties; } @Override public int getOrder() { return 0; } @Override public void customize(Jackson2ObjectMapperBuilder builder) { if (this.jacksonProperties.getDefaultPropertyInclusion() != null) { builder.serializationInclusion(this.jacksonProperties.getDefaultPropertyInclusion()); } if (this.jacksonProperties.getTimeZone() != null) { builder.timeZone(this.jacksonProperties.getTimeZone()); } configureFeatures(builder, FEATURE_DEFAULTS); configureVisibility(builder, this.jacksonProperties.getVisibility()); configureFeatures(builder, this.jacksonProperties.getDeserialization()); configureFeatures(builder, this.jacksonProperties.getSerialization()); configureFeatures(builder, this.jacksonProperties.getMapper()); configureFeatures(builder, this.jacksonProperties.getParser()); configureFeatures(builder, this.jacksonProperties.getGenerator()); configureDateFormat(builder); configurePropertyNamingStrategy(builder); configureModules(builder); configureLocale(builder); } //more configure methods... } }
总结:通过一系列的方法,最终构造一个生产级别可用的ObjectMapper
对象,供在Spring Boot框架中对Java对象实现序列化与反序列化操作。
5.Jackson常见注解使用示例
备注:本小结内容来源https://www.baeldung.com/jackson-annotations,如果工作中对于jackson的注解使用较少的情况下,可以看看该篇文章,是一个非常好的补充。
5.1 序列化
5.1.1 @JsonAnyGetter
@JsonAnyGetter
注解运行可以灵活的使用Map
类型的作为属性字段
实体类如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class ExtendableBean { public String name; private Map<String, String> properties; @JsonAnyGetter public Map<String, String> getProperties() { return properties; } public ExtendableBean(String name) { this.name = name; this.properties=new HashMap<String, String>(); } public void add(String key,String value){ this.properties.put(key,value); } }
通过序列化该实体Bean,我们将会得到Map
属性中的所有Key
作为属性值,测试序列化代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14@Test public void whenSerializingUsingJsonAnyGetter_thenCorrect() throws JsonProcessingException { ExtendableBean bean = new ExtendableBean("My bean"); bean.add("attr1", "val1"); bean.add("attr2", "val2"); String result = new ObjectMapper().writeValueAsString(bean); assertThat(result, containsString("attr1")); assertThat(result, containsString("val1")); }
最终输出结果如下:
1
2
3
4
5
6{ "name":"My bean", "attr2":"val2", "attr1":"val1" }
如果不使用@JsonAnyGetter
注解,那么最终序列化结果将会在properties
属性下面,结果如下:
1
2
3
4
5
6
7
8{ "name": "My bean", "properties": { "attr2": "val2", "attr1": "val1" } }
5.1.2 @JsonGetter
@JsonGetter
注解是一个替代@JsonProperty
的注解,可以将一个方法标注为getter
方法
例如下面的示例中,我们通过注解@JsonGetter
将方法getTheName()
作为属性name
的getter
方法
1
2
3
4
5
6
7
8
9
10public class MyBean { public int id; private String name; @JsonGetter("name") public String getTheName() { return name; } }
5.1.3 @JsonPropertyOrder
可以通过使用@JsonPropertyOrder
注解来指定属性的序列化顺序
实体bean定义如下:
1
2
3
4
5
6@JsonPropertyOrder({ "name", "id" }) public class MyBean { public int id; public String name; }
最终序列化结果为:
1
2
3
4
5{ "name":"My bean", "id":1 }
也可以通过@JsonPropertyOrder(alphabetic=true)
来指定按照字母排序,那么响应结果将是:
1
2
3
4
5{ "id":1, "name":"My bean" }
5.1.4 @JsonRawValue
@JsonRawValue
注解可以指定字符串属性类为json,如下代码:
1
2
3
4
5
6
7public class RawBean { public String name; @JsonRawValue public String json; }
创建RawBean
的示例,给属性json
赋值,代码如下:
1
2
3
4RawBean bean = new RawBean("My bean", "{"attr":false}"); String result = new ObjectMapper().writeValueAsString(bean);
最终序列化结果如下:
1
2
3
4
5
6
7{ "name":"My bean", "json":{ "attr":false } }
5.1.5 @JsonValue
@JsonValue
注解主要用于序列化整个实例对象的单个方法,例如,在一个枚举类中,@JsonValue
注解进行标注,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public enum TypeEnumWithValue { TYPE1(1, "Type A"), TYPE2(2, "Type 2"); private Integer id; private String name; TypeEnumWithValue(Integer id, String name) { this.id = id; this.name = name; } @JsonValue public String getName() { return name; } }
测试代码如下:
1
2
3
4String enumAsString = new ObjectMapper() .writeValueAsString(TypeEnumWithValue.TYPE1); System.out.println(enumAsString);
最终通过序列化代码得到的结果将是:
1
2"Type A"
5.1.6 @JsonRootName
@JsonRootName
注解旨在给当前序列化的实体对象加一层包裹对象。
举例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14//RootUser.java public class RootUser { private String name; private String title; public RootUser(String name, String title) { this.name = name; this.title = title; } //getter and setters }
在上面的实体类中,正常情况下,如果要序列号RootUser
对象,其结果格式为:
1
2
3
4
5{ "name": "name1", "title": "title1" }
在RootUser
加上@JsonRootName
注解后,该类改动如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//RootUser.java @JsonRootName(value = "root") public class RootUser { private String name; private String title; public RootUser(String name, String title) { this.name = name; this.title = title; } //getter and setters }
启用ObjectMapper
对象的WRAP_ROOT_VALUE
特性,测试代码如下:
1
2
3
4ObjectMapper objectMapper=new ObjectMapper(); objectMapper.enable(SerializationFeature.WRAP_ROOT_VALUE); String result=objectMapper.writeValueAsString(new RootUser("name1","title1"));
最终序列化JSON结果如下:
1
2
3
4
5
6
7{ "root": { "name": "name1", "title": "title1" } }
5.1.7 @JsonSerialize
@JsonSerialize
注解允许开发者自定义序列化实现,来看代码实现
1
2
3
4
5
6
7
8
9
10
11public class EventWithSerializer { public String name; @JsonSerialize(using = CustomDateSerializer.class) public Date eventDate; public Date publishDate; //getter and setter... }
在上面的代码中,针对eventDate
字段,我们通过使用@JsonSerialize
注解,自定义了一个序列化实现类CustomDateSerializer
,该类实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22//CustomDateSerializer.java public class CustomDateSerializer extends StdSerializer<Date> { private static SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); public CustomDateSerializer() { this(null); } public CustomDateSerializer(Class<Date> t) { super(t); } @Override public void serialize( Date value, JsonGenerator gen, SerializerProvider arg2) throws IOException, JsonProcessingException { gen.writeString(formatter.format(value)); } }
最终序列化的结果格式如下:
1
2
3
4
5
6{ "name": "名称", "eventDate": "24-03-2021 06:14:32", "publishDate": 1616580872574 }
从结果我们可以得知,针对某个特定的字段序列化的方式,我们可以完全自定义,非常的方便。
5.2 反序列化
5.2.1 @JsonCreator
@JsonCreator
配合@JsonProperty
注解能到达在反序列化实体对象时,指定不变更属性名称的效果
例如有如下JSON:
1
2
3
4
5{ "id":1, "theName":"My bean" }
在实体类中,我们没有属性名称是theName
,但我们想把theName
属性反序列化时赋值给name
,此时实体类对象结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13public class BeanWithCreator { public int id; public String name; @JsonCreator public BeanWithCreator( @JsonProperty("id") int id, @JsonProperty("theName") String name) { this.id = id; this.name = name; } }
在BeanWithCreator
的构造函数中添加@JsonCreator
注解,并且配合@JsonProperty
注解进行属性指向,最终反序列化代码如下:
1
2
3
4
5
6
7
8
9
10
11
12@Test public void whenDeserializingUsingJsonCreator_thenCorrect() throws IOException { String json = "{"id":1,"theName":"My bean"}"; BeanWithCreator bean = new ObjectMapper() .readerFor(BeanWithCreator.class) .readValue(json); assertEquals("My bean", bean.name); }
5.2.2 @JacksonInject
@JacksonInject
注解可以指定反序列化对象时,属性值不从来源JSON获取,而从injection
中获取
实体类如下:
1
2
3
4
5
6
7public class BeanWithInject { @JacksonInject public int id; public String name; }
反序列化代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@Test public void whenDeserializingUsingJsonInject_thenCorrect() throws IOException { String json = "{"name":"My bean"}"; InjectableValues inject = new InjectableValues.Std() .addValue(int.class, 1); BeanWithInject bean = new ObjectMapper().reader(inject) .forType(BeanWithInject.class) .readValue(json); assertEquals("My bean", bean.name); assertEquals(1, bean.id); }
5.2.3 @JsonAnySetter
@JsonAnySetter
和@JsonAnyGetter
注解意思一致,只不过是针对序列化与反序列化而言,@JsonAnySetter
注解可以将来源JSON最终转化为Map
类型的属性结构
实体代码如下:
1
2
3
4
5
6
7
8
9
10public class ExtendableBean { public String name; private Map<String, String> properties; @JsonAnySetter public void add(String key, String value) { properties.put(key, value); } }
JSON源如下:
1
2
3
4
5
6{ "name":"My bean", "attr2":"val2", "attr1":"val1" }
通过@JsonAnySetter
的注解标注,最终attr1
及attr2
的值将会添加到properties
的Map对象中
示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14@Test public void whenDeserializingUsingJsonAnySetter_thenCorrect() throws IOException { String json = "{"name":"My bean","attr2":"val2","attr1":"val1"}"; ExtendableBean bean = new ObjectMapper() .readerFor(ExtendableBean.class) .readValue(json); assertEquals("My bean", bean.name); assertEquals("val2", bean.getProperties().get("attr2")); }
5.2.4 @JsonSetter
@JsonSetter
注解是@JsonProperty
的替代注解,用于标注该方法为setter
方法
当我们需要读取一些JSON数据时,但是目标实体类与该数据不完全匹配是,该注解是非常有用的。
示例代码如下:
1
2
3
4
5
6
7
8
9
10public class MyBean { public int id; private String name; @JsonSetter("name") public void setTheName(String name) { this.name = name; } }
通过指定setTheName
作为属性name
的setter
方法,反序列化时可以达到最终效果
示例如下:
1
2
3
4
5
6
7
8
9
10
11
12@Test public void whenDeserializingUsingJsonSetter_thenCorrect() throws IOException { String json = "{"id":1,"name":"My bean"}"; MyBean bean = new ObjectMapper() .readerFor(MyBean.class) .readValue(json); assertEquals("My bean", bean.getTheName()); }
5.2.5 @JsonDeserialize
@JsonDeserialize
注解和序列化注解@JsonSerialize
的效果是一致的,作用与反序列化时,针对特定的字段,存在差异化的发序列化效果
1
2
3
4
5
6
7public class EventWithSerializer { public String name; @JsonDeserialize(using = CustomDateDeserializer.class) public Date eventDate; }
CustomDateDeserializer
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28public class CustomDateDeserializer extends StdDeserializer<Date> { private static SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); public CustomDateDeserializer() { this(null); } public CustomDateDeserializer(Class<?> vc) { super(vc); } @Override public Date deserialize( JsonParser jsonparser, DeserializationContext context) throws IOException { String date = jsonparser.getText(); try { return formatter.parse(date); } catch (ParseException e) { throw new RuntimeException(e); } } }
最终,反序列化JSON,时,得到eventDate
字段,测试代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17@Test public void whenDeserializingUsingJsonDeserialize_thenCorrect() throws IOException { String json = "{"name":"party","eventDate":"20-12-2014 02:30:00"}"; SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); EventWithSerializer event = new ObjectMapper() .readerFor(EventWithSerializer.class) .readValue(json); assertEquals( "20-12-2014 02:30:00", df.format(event.eventDate)); }
5.2.6 @JsonAlias
@JsonAlias
注解作用于可以指定一个别名与JSON数据中的字段进行对于,最终反序列化时,能将该值最终反序列化时赋值给对象
实体如下:
1
2
3
4
5
6public class AliasBean { @JsonAlias({ "fName", "f_name" }) private String firstName; private String lastName; }
上面的代码中,firstName
字段通过@JsonAlias
注解指定了两个别名字段,意思是反序列化时可以从JSON中读取fName
或者f_name
的值赋值到firstName
中
测试代码如下:
1
2
3
4
5
6
7@Test public void whenDeserializingUsingJsonAlias_thenCorrect() throws IOException { String json = "{"fName": "John", "lastName": "Green"}"; AliasBean aliasBean = new ObjectMapper().readerFor(AliasBean.class).readValue(json); assertEquals("John", aliasBean.getFirstName()); }
5.3 属性注解
5.3.1 @JsonIgnoreProperties
使用@JsonIgnoreProperties
注解作用于class级别中可以达到在序列化时忽略一个或多个字段的效果
实体代码如下:
1
2
3
4
5
6@JsonIgnoreProperties({ "id" }) public class BeanWithIgnore { public int id; public String name; }
最终在序列化BeanWithIgnore
实体对象时,字段id
将会被忽略
5.3.2 @JsonIgnore
@JsonIgnore
注解作用与属性级别中,在序列化时可以忽略该字段
实体代码如下:
1
2
3
4
5
6
7public class BeanWithIgnore { @JsonIgnore public int id; public String name; }
最终在序列化BeanWithIgnore
实体对象时,字段id
将会被忽略
复制代码1
2
3
4注解名称:@JsonIgnore 作用:在实体类向前台返回数据时用来忽略不想传递给前台的属性或接口。 Eg:Bean实体中会有某些运维字段,在返回信息给前台的时候,当不希望将对应值也一并返回; 此时可以在对应属性上加上注解JsonIgnore或者,可以在User类上加上注解@JsonIgnoreProperties(value = "{password}")
5.3.3 @JsonIgnoreType
@JsonIgnoreType
指定忽略类型属性
1
2
3
4
5
6
7
8
9
10
11public class User { public int id; public Name name; @JsonIgnoreType public static class Name { public String firstName; public String lastName; } }
在上面的示例中,类型Name
将会被忽略
5.3.4 @JsonInclude
使用@JsonInclude
注解可以排除属性值中包含empty/null/default
的属性
1
2
3
4
5
6@JsonInclude(Include.NON_NULL) public class MyBean { public int id; public String name; }
在MyBean
中使用了Include.NON_NULL
则代表该实体对象序列化时不会包含空值
5.3.5 @JsonAutoDetect
@JsonAutoDetect
可以覆盖实体对象属性中的默认可见级别,比如私有属性可见与不可见
实体对象如下:
1
2
3
4
5
6
7
8
9
10public class PrivateBean { private int id; private String name; public PrivateBean(int id, String name) { this.id = id; this.name = name; } }
在PrivateBean
中,没有给属性字段id
、name
设置公共的getter
方法,此时,如果我们如果直接对该实体对象进行序列化时,jackson会提示错误
1
2Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.xiaoymin.boot.action.jackson.model.PrivateBean and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
我们修改PrivateBean
中的代码,增加@JsonAutoDetect
注解,代码如下:
1
2
3
4
5
6
7
8
9
10
11@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) public class PrivateBean { private int id; private String name; public PrivateBean(int id, String name) { this.id = id; this.name = name; } }
此时,在序列化该实体对象,将会得到响应结果
1
2
3
4PrivateBean bean = new PrivateBean(1, "My bean"); String result = new ObjectMapper().writeValueAsString(bean); System.out.println(result);
5.4 常规注解
5.4.1 @JsonProperty
我们可以添加@JsonProperty
批注以在JSON中指示属性名称。
当实体对象中没有标准的getter/setter
方法时,我们可以使用该注解进行指定属性名称已方便jackson框架进行序列化/反序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class MyBean { public int id; private String name; @JsonProperty("name") public void setTheName(String name) { this.name = name; } @JsonProperty("name") public String getTheName() { return name; } }
5.4.2 @JsonFormat
针对日期字段可以通过使用@JsonFormat
注解进行格式化输出
1
2
3
4
5
6
7
8
9public class EventWithFormat { public String name; @JsonFormat( shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss") public Date eventDate; }
5.4.3 @JsonUnwrapped
@JsonUnwrapped
注解可以指定jackson框架在序列化/反序列化时是否需要对该字段进行wrapped
操作
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class UnwrappedUser { public int id; @JsonUnwrapped public Name name; //getter and setter... public static class Name { public String firstName; public String lastName; //getter and setter } }
通过注解@JsonUnwrapped
标注name
属性,最终序列化该对象时,会和正常情况下有所区别
1
2
3
4
5UnwrappedUser.Name name = new UnwrappedUser.Name("John", "Doe"); UnwrappedUser user = new UnwrappedUser(1, name); String result = new ObjectMapper().writeValueAsString(user);
我们得到的结果如下:
1
2
3
4
5
6{ "id": 1, "firstName": "John", "lastName": "Doe" }
5.4.4 @JsonView
通过View
的方式来指定序列化/反序列化时是否包含属性
示例代码如下:
View
定义
1
2
3
4
5public class Views { public static class Public {} public static class Internal extends Public {} }
实体代码:
1
2
3
4
5
6
7
8
9
10
11
12
13public class Item { @JsonView(Views.Public.class) public int id; @JsonView(Views.Public.class) public String itemName; @JsonView(Views.Internal.class) public String ownerName; //getter and setter.. }
最终序列化代码示例:
1
2
3
4
5Item item = new Item(2, "book", "John"); String result = new ObjectMapper().writerWithView(Views.Public.class).writeValueAsString(item); System.out.println(result);
最终序列化结果输出:
1
2{"id":2,"itemName":"book"}
对返回小数位数的处理:
double
一、工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40package com.mdw.platform.orders.config; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.DecimalFormat; /** * 格式化 * * double 精确到小数点后两位 * Date: 2019/10/15 11:58 * title:title * @author ht */ public class CustomerDoubleSerialize extends JsonSerializer<Double> { /** * 原本这里是 ##.00 ,带来的问题是如果数据库数据为0.00返回“ .00 “经评论指正,改为0.00 */ private DecimalFormat df = new DecimalFormat("0.00"); @Override public void serialize(Double arg0, JsonGenerator arg1, SerializerProvider arg2) throws IOException { if(arg0 != null) { BigDecimal bigDecimal = new BigDecimal(String.valueOf(arg0)); df.setRoundingMode(RoundingMode.HALF_UP); //四舍五入。需要将数据转成bigDecimal, 否则会存在经度丢失问题 String format = df.format(bigDecimal); double aDouble = Double.parseDouble(format); arg1.writeNumber(aDouble);//返回数字格式 } } }
二、 实现
对实体类加上@JsonSerialize(using = CustomerDoubleSerialize.class)
1
2
3
4
5
6
7import com.fasterxml.jackson.databind.annotation.JsonSerialize; /** * 提货费用 */ @JsonSerialize(using = CustomerDoubleSerialize.class) private Double pickUpCost;
bigdeimal:
因为springboot默认采用jackson序列化,而jackson序列化又可以自定义序列化器,所以我们在返回double或者bigdeimal类型数据时,可以指定序列化器,而不用在代码里面处理这样可以避免重复的处理代码
1、自定义序列化器:
public class CustomDecimalSerialize extends JsonSerializer<BigDecimal> {
@Override
public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException {
if (value != null) {
value = value.setScale(2, BigDecimal.ROUND_HALF_UP);
gen.writeString(value.toString());
}
}
}
2、在double或者bigdecimal类型字段上指定自定义的序列化器:
//指标值
@JsonSerialize(using = CustomDecimalSerialize.class)
protected BigDecimal value;
最终的效果:
最后
以上就是重要小鸽子最近收集整理的关于Jackson详解的全部内容,更多相关Jackson详解内容请搜索靠谱客的其他文章。
发表评论 取消回复