在现代Spring Boot项目中,Jackson作为默认的JSON处理库,承担着Java对象和JSON字符串相互转换的重要职责。本文将结合Spring Boot 2.7.6版本,基于JDK 1.8环境,详细讲解如何优化Jackson的配置,提供一套高效易用的Json工具类,并通过丰富示例辅助理解与应用,帮助您快速上手并提升开发效率。
Spring Boot环境及依赖准备
本示范基于以下环境:
- JDK版本:1.8
- Spring Boot版本:2.7.6
- Jackson版本:2.13.4(Spring Boot 2.7默认依赖)
依赖引入
Spring Boot Web Starter内置了Jackson依赖,您只需在pom.xml
中引入如下依赖即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
|
注意,无需额外明确添加Jackson依赖,Spring Boot Web Starter自动为您集成。
优化Jackson核心配置
为了满足日期格式统一、空值处理、允许单引号等需求,可以在Spring Boot的application.properties
中配置如下属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
spring.jackson.default-property-inclusion=non_null
spring.jackson.serialization.indent_output=true
spring.jackson.serialization.fail_on_empty_beans=false
spring.jackson.deserialization.fail_on_unknown_properties=false
spring.jackson.parser.allow_unquoted_control_chars=true spring.jackson.parser.allow_single_quotes=true
|
以上配置覆盖了大部分业务常用场景,使Jackson的行为更符合实际使用需求。
实用Jackson工具类设计
为了避免重复编写JSON转换代码,建议自定义一个通用的JsonUtil
工具类,封装常用的序列化与反序列化操作,并支持特定的命名策略转换。
设计思路
- 内置两个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
| import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.*; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils;
import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.List;
@Slf4j public class JsonUtil {
private static final ObjectMapper DEFAULT_MAPPER = new ObjectMapper(); private static final ObjectMapper SNAKE_CASE_MAPPER = new ObjectMapper();
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
static { configureMapper(DEFAULT_MAPPER);
configureMapper(SNAKE_CASE_MAPPER); SNAKE_CASE_MAPPER.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); }
private static void configureMapper(ObjectMapper mapper) { mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); mapper.setDateFormat(new SimpleDateFormat(DATE_FORMAT)); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); }
private JsonUtil() { }
public static <T> String obj2String(T obj) { if (obj == null) return null; try { return obj instanceof String ? (String) obj : DEFAULT_MAPPER.writeValueAsString(obj); } catch (JsonProcessingException e) { log.warn("转换对象为JSON字符串失败: {}", e.getMessage()); return null; } }
public static void obj2File(String fileName, Object obj) { if (obj == null || fileName == null || fileName.trim().isEmpty()) return; try { DEFAULT_MAPPER.writeValue(new File(fileName), obj); } catch (IOException e) { log.error("写入对象到文件失败: {}", e.getMessage()); } }
public static <T> String obj2StringFieldSnakeCase(T obj) { if (obj == null) return null; try { return obj instanceof String ? (String) obj : SNAKE_CASE_MAPPER.writeValueAsString(obj); } catch (JsonProcessingException e) { log.warn("转换对象为JSON字符串失败: {}", e.getMessage()); return null; } }
public static <T> T string2ObjFieldLowerCamelCase(String str, Class<T> clazz) { if (!StringUtils.hasText(str) || clazz == null) return null; try { return clazz.equals(String.class) ? (T) str : SNAKE_CASE_MAPPER.readValue(str, clazz); } catch (IOException e) { log.warn("JSON字符串转对象失败: {}", e.getMessage()); return null; } }
public static <T> List<T> string2ListFieldLowerCamelCase(String str, TypeReference<List<T>> typeReference) { if (!StringUtils.hasText(str) || typeReference == null) return null; try { return SNAKE_CASE_MAPPER.readValue(str, typeReference); } catch (IOException e) { log.warn("JSON字符串转集合失败: {}", e.getMessage()); return null; } }
public static <T> String obj2StringPretty(T obj) { if (obj == null) return null; try { return obj instanceof String ? (String) obj : DEFAULT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj); } catch (JsonProcessingException e) { log.warn("转换对象为漂亮格式JSON失败: {}", e.getMessage()); return null; } }
public static <T> T string2Obj(String str, Class<T> clazz) { if (!StringUtils.hasText(str) || clazz == null) return null; try { return clazz.equals(String.class) ? (T) str : DEFAULT_MAPPER.readValue(str, clazz); } catch (IOException e) { log.warn("JSON字符串转对象失败: {}", e.getMessage()); return null; } }
public static <T> T string2Obj(String str, TypeReference<T> typeReference) { if (!StringUtils.hasText(str) || typeReference == null) return null; try { if (typeReference.getType().equals(String.class)) { return (T) str; } return DEFAULT_MAPPER.readValue(str, typeReference); } catch (IOException e) { log.warn("JSON字符串转对象失败: {}", e.getMessage()); return null; } }
public static <T> T string2Obj(String str, Class<?> collectionClazz, Class<?>... elementClazzes) { if (!StringUtils.hasText(str) || collectionClazz == null) return null; JavaType javaType = DEFAULT_MAPPER.getTypeFactory().constructParametricType(collectionClazz, elementClazzes); try { return DEFAULT_MAPPER.readValue(str, javaType); } catch (IOException e) { log.warn("JSON字符串转泛型对象失败: {}", e.getMessage()); return null; } } }
|
实战示例:User实体与转换演示
为了更清晰展现工具类的使用,我们定义一个简单的用户实体类:
1 2 3 4 5 6 7 8 9 10
| import lombok.Data; import java.util.List;
@Data public class User { private String username; private Integer age; private List<String> info; private Long userId; }
|
示例一:Java对象转JSON字符串
1 2 3 4 5 6 7 8 9 10 11
| @Test void testObjToString() { User user = new User(); user.setUsername("clllb"); user.setAge(24); user.setUserId(1L); user.setInfo(Arrays.asList("有一百万", "发大财"));
String json = JsonUtil.obj2String(user); System.out.println(json); }
|
输出结果:
1
| {"username":"clllb","age":24,"info":["有一百万","发大财"],"userId":1}
|
示例二:Java对象转JSON字符串(字段名驼峰转下划线)
1 2 3 4 5 6 7 8 9 10 11
| @Test void testObjToSnakeCaseString() { User user = new User(); user.setUsername("clllb"); user.setAge(24); user.setUserId(11L); user.setInfo(Arrays.asList("有一百万", "发大财"));
String json = JsonUtil.obj2StringFieldSnakeCase(user); System.out.println(json); }
|
输出结果:
1
| {"username":"clllb","age":24,"info":["有一百万","发大财"],"user_id":11}
|
示例三:Java对象转JSON文件
1 2 3 4 5 6 7 8 9 10 11 12
| @Test void testObjToFile() { User user = new User(); user.setUsername("clllb"); user.setAge(24); user.setUserId(1L); user.setInfo(Arrays.asList("有一百万", "发大财"));
String fileName = "user.json"; JsonUtil.obj2File(fileName, user); System.out.println("JSON文件已生成:" + fileName); }
|
执行后会在当前项目根目录生成user.json
文件,内容与obj2String()
输出一致。
示例四:JSON字符串转Java对象(驼峰字段)
1 2 3 4 5 6
| @Test void testStringToObj() { String json = "{\"username\":\"clllb\",\"age\":24,\"info\":[\"有一百万\",\"发大财\"],\"userId\":11}"; User user = JsonUtil.string2Obj(json, User.class); System.out.println(user); }
|
输出结果:
1
| User(username=clllb, age=24, info=[有一百万, 发大财], userId=11)
|
示例五:JSON字符串转Java对象集合(驼峰字段)
1 2 3 4 5 6 7 8 9
| @Test void testStringToListObj() { String json = "[" + "{\"username\":\"clllb\",\"age\":24,\"info\":[\"有一百万\",\"发大财\"],\"userId\":11}," + "{\"username\":\"陈老老老板\",\"age\":25,\"info\":[\"有一千万\",\"发大大财\"],\"userId\":12}" + "]"; List<User> users = JsonUtil.string2Obj(json, new TypeReference<List<User>>() {}); users.forEach(System.out::println); }
|
输出结果:
1 2
| User(username=clllb, age=24, info=[有一百万, 发大财], userId=11) User(username=陈老老老板, age=25, info=[有一千万, 发大大财], userId=12)
|
示例六:JSON字符串转Java对象集合(下划线字段转驼峰)
1 2 3 4 5 6 7 8 9
| @Test void testStringToListObjSnakeCase() { String json = "[" + "{\"username\":\"clllb\",\"age\":24,\"info\":[\"有一百万\",\"发大财\"],\"user_id\":11}," + "{\"username\":\"陈老老老板\",\"age\":25,\"info\":[\"有一千万\",\"发大大财\"],\"user_id\":12}" + "]"; List<User> users = JsonUtil.string2ListFieldLowerCamelCase(json, new TypeReference<List<User>>() {}); users.forEach(System.out::println); }
|
输出结果:
1 2
| User(username=clllb, age=24, info=[有一百万, 发大财], userId=11) User(username=陈老老老板, age=25, info=[有一千万, 发大大财], userId=12)
|
总结与提升建议
本文全面展示了如何在Spring Boot项目中集成并优化Jackson,涵盖依赖引入、核心配置、自定义工具类及丰富实用示例。通过这些实践,您可以:
- 统一JSON的格式和时间处理规则
- 灵活支持驼峰和下划线两种命名转化
- 简化JSON与Java对象的互转代码
- 支持文件读写,方便数据持久化和调试
后续可根据业务进一步扩展,如:
- 支持自定义序列化/反序列化器(实现复杂对象转换)
- 集成Jackson Visibilty控制实现安全过滤敏感字段
- 结合Spring MVC自定义消息转换器配置,以适应特殊请求场景
Jackson是日常后端开发的重要工具,掌握并灵活应用将大幅提升开发效率。希望本文能帮助您打造更健壮、易维护的JSON处理体系!