JSON 工具
JSONUtils 是基于 Jackson 封装的 JSON 序列化和反序列化工具类,提供了简化的 API 和预配置的时间格式化。
类信息
- 包名:
com.molandev.core.web.json - 类名:
JSONUtils - 类型: 静态工具类
- 依赖: Jackson (com.fasterxml.jackson.*)
核心特性
✅ 预配置的时间格式
- 时区: GMT+8(中国标准时间)
- LocalDateTime:
yyyy-MM-dd HH:mm:ss - LocalDate:
yyyy-MM-dd - LocalTime:
HH:mm:ss - Date:
yyyy-MM-dd HH:mm:ss
✅ 宽松的反序列化
- 忽略未知属性(
FAIL_ON_UNKNOWN_PROPERTIES = false) - 自动处理多态类型(支持继承关系)
✅ 泛型支持
- 支持
List<T>、Map<K,V>等复杂泛型 - 类型安全的反序列化
✅ 异常封装
- 将
JsonProcessingException封装为RuntimeException - 无需手动处理检查型异常
核心方法
1. toJsonString
将对象序列化为 JSON 字符串。
public static String toJsonString(Object object)| 参数 | 类型 | 说明 |
|---|---|---|
| object | Object | 要序列化的对象 |
| 返回值 | String | JSON 字符串 |
示例:
User user = new User();
user.setId(1L);
user.setName("张三");
user.setCreateTime(LocalDateTime.now());
String json = JSONUtils.toJsonString(user);
// {"id":1,"name":"张三","createTime":"2024-01-18 14:30:00"}2. toObject(String, Class)
将 JSON 字符串反序列化为指定类型的对象。
public static <T> T toObject(String str, Class<T> clazz)| 参数 | 类型 | 说明 |
|---|---|---|
| str | String | JSON 字符串 |
| clazz | Class<T> | 目标类型 |
| 返回值 | T | 反序列化后的对象 |
示例:
String json = "{\"id\":1,\"name\":\"张三\",\"createTime\":\"2024-01-18 14:30:00\"}";
User user = JSONUtils.toObject(json, User.class);3. toObject(String, Type)
将 JSON 字符串反序列化为指定泛型类型的对象。
public static <T> T toObject(String str, Type type)| 参数 | 类型 | 说明 |
|---|---|---|
| str | String | JSON 字符串 |
| type | Type | 目标泛型类型 |
| 返回值 | T | 反序列化后的对象 |
示例:
String json = "{\"code\":200,\"data\":{\"name\":\"张三\"}}";
Type type = new TypeToken<Response<User>>(){}.getType();
Response<User> response = JSONUtils.toObject(json, type);4. toList
将 JSON 数组字符串反序列化为 List。
public static <T> List<T> toList(String str, Type type)| 参数 | 类型 | 说明 |
|---|---|---|
| str | String | JSON 数组字符串 |
| type | Type | List 中元素的类型 |
| 返回值 | List<T> | 反序列化后的 List |
示例:
String json = "[{\"id\":1,\"name\":\"张三\"},{\"id\":2,\"name\":\"李四\"}]";
List<User> users = JSONUtils.toList(json, User.class);5. toMap
将 JSON 字符串转换为 Map。
public static Map<String, Object> toMap(String str)| 参数 | 类型 | 说明 |
|---|---|---|
| str | String | JSON 字符串 |
| 返回值 | Map<String, Object> | 转换后的 Map |
示例:
String json = "{\"name\":\"张三\",\"age\":25,\"active\":true}";
Map<String, Object> map = JSONUtils.toMap(json);
// {name=张三, age=25, active=true}完整示例
示例 1: REST API 响应处理
import com.molandev.core.web.json.JSONUtils;
import lombok.Data;
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
private UserService userService;
// 返回 JSON 格式的用户信息
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id) {
User user = userService.getById(id);
return JSONUtils.toJsonString(user);
}
// 接收 JSON 格式的用户信息
@PostMapping("/user")
public User createUser(@RequestBody String json) {
User user = JSONUtils.toObject(json, User.class);
userService.save(user);
return user;
}
// 批量处理
@PostMapping("/users/batch")
public List<User> batchCreate(@RequestBody String json) {
List<User> users = JSONUtils.toList(json, User.class);
userService.saveBatch(users);
return users;
}
}
@Data
class User {
private Long id;
private String name;
private String email;
private LocalDateTime createTime;
}示例 2: Redis 缓存序列化
import com.molandev.core.web.json.JSONUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class CacheService {
@Autowired
private StringRedisTemplate redisTemplate;
// 缓存对象
public void cacheUser(User user) {
String key = "user:" + user.getId();
String json = JSONUtils.toJsonString(user);
redisTemplate.opsForValue().set(key, json, 30, TimeUnit.MINUTES);
}
// 读取缓存
public User getUser(Long id) {
String key = "user:" + id;
String json = redisTemplate.opsForValue().get(key);
if (json != null) {
return JSONUtils.toObject(json, User.class);
}
return null;
}
// 缓存列表
public void cacheUserList(List<User> users) {
String json = JSONUtils.toJsonString(users);
redisTemplate.opsForValue().set("users:all", json);
}
// 读取列表缓存
public List<User> getUserList() {
String json = redisTemplate.opsForValue().get("users:all");
if (json != null) {
return JSONUtils.toList(json, User.class);
}
return Collections.emptyList();
}
}示例 3: 配置文件解析
import com.molandev.core.web.json.JSONUtils;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
@Component
public class ConfigLoader {
// 加载 JSON 配置文件
public AppConfig loadConfig(String configPath) throws IOException {
String json = Files.readString(Paths.get(configPath));
return JSONUtils.toObject(json, AppConfig.class);
}
// 加载动态配置(Map 形式)
public Map<String, Object> loadDynamicConfig(String configPath) throws IOException {
String json = Files.readString(Paths.get(configPath));
return JSONUtils.toMap(json);
}
// 保存配置
public void saveConfig(AppConfig config, String configPath) throws IOException {
String json = JSONUtils.toJsonString(config);
Files.writeString(Paths.get(configPath), json);
}
}
@Data
class AppConfig {
private String appName;
private int port;
private DatabaseConfig database;
private List<String> allowedOrigins;
}示例 4: MQ 消息序列化
import com.molandev.core.web.json.JSONUtils;
@Service
public class MessageProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
// 发送对象消息
public void sendOrderMessage(Order order) {
String json = JSONUtils.toJsonString(order);
rabbitTemplate.convertAndSend("order.exchange", "order.created", json);
}
}
@Service
public class MessageConsumer {
// 接收并解析消息
@RabbitListener(queues = "order.queue")
public void handleOrderMessage(String json) {
Order order = JSONUtils.toObject(json, Order.class);
System.out.println("收到订单: " + order.getOrderNo());
// 处理订单逻辑
}
}高级用法
自定义 JsonMapper
如果需要自定义 Jackson 配置,可以设置自己的 JsonMapper:
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.molandev.core.web.json.JSONUtils;
@Configuration
public class JacksonConfig {
@PostConstruct
public void customizeJsonMapper() {
JsonMapper customMapper = JsonMapper.builder()
.defaultDateFormat(new SimpleDateFormat("yyyy/MM/dd"))
.configure(SerializationFeature.INDENT_OUTPUT, true) // 美化输出
.build();
JSONUtils.setJsonMapper(customMapper);
}
}获取底层 JsonMapper
JsonMapper mapper = JSONUtils.getJsonMapper();
// 使用 mapper 进行更复杂的操作技术细节
默认配置
JsonMapper.builder()
.addModule(new JsonJavaTimeModule()) // Java 8 时间支持
.defaultTimeZone(TimeZone.getTimeZone("GMT+8")) // 东八区
.defaultLocale(Locale.CHINA) // 中国地区
.defaultDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.build();时间格式
| 类型 | 格式 | 示例 |
|---|---|---|
| LocalDateTime | yyyy-MM-dd HH:mm:ss | 2024-01-18 14:30:00 |
| LocalDate | yyyy-MM-dd | 2024-01-18 |
| LocalTime | HH:mm:ss | 14:30:00 |
| Date | yyyy-MM-dd HH:mm:ss | 2024-01-18 14:30:00 |
注意事项
⚠️ 异常处理
JSONUtils 将 JsonProcessingException 封装为 RuntimeException,如果需要捕获异常:
try {
User user = JSONUtils.toObject(json, User.class);
} catch (RuntimeException e) {
// JSON 解析失败
log.error("JSON 解析失败", e);
}⚠️ 循环引用
避免对象之间存在循环引用,否则会导致序列化失败或栈溢出。
// 错误示例
class Parent {
private Child child;
}
class Child {
private Parent parent; // 循环引用
}⚠️ 性能考虑
- JSONUtils 内部使用单例 JsonMapper,线程安全且高性能
- 大对象序列化/反序列化会消耗较多内存和 CPU
- 建议对频繁使用的 JSON 进行缓存
⚠️ null 值处理
默认情况下,null 值会被序列化:
User user = new User();
user.setName("张三");
// {"name":"张三","age":null,"email":null}常见问题
Q1: 如何处理未知字段?
A: JSONUtils 默认忽略未知字段,不会抛出异常。
// JSON 中有 extra 字段,但 User 类没有
String json = "{\"name\":\"张三\",\"extra\":\"value\"}";
User user = JSONUtils.toObject(json, User.class); // 正常解析,忽略 extraQ2: 如何处理枚举?
A: Jackson 默认使用枚举的 name() 进行序列化:
enum Status { ACTIVE, INACTIVE }
User user = new User();
user.setStatus(Status.ACTIVE);
String json = JSONUtils.toJsonString(user);
// {"status":"ACTIVE"}Q3: 如何序列化泛型?
A: 使用 toObject(String, Type) 方法:
Type type = new TypeToken<Response<List<User>>>(){}.getType();
Response<List<User>> response = JSONUtils.toObject(json, type);Q4: 时间格式不匹配怎么办?
A: 可以自定义 JsonMapper 或在实体类字段上使用 @JsonFormat 注解:
@Data
class Event {
@JsonFormat(pattern = "yyyy/MM/dd HH:mm:ss")
private LocalDateTime eventTime;
}相关工具
- Spring 工具 - Spring 容器访问
- 任务调度 - 异步任务执行
- Util 模块 - StringUtils - 字符串处理
参考资料
JSON 自动配置(JsonAutoConfiguration)
JsonAutoConfiguration(包 com.molandev.core.web.json)在 Servlet Web 下始终启用,统一 Spring MVC / Feign 的 JSON 与日期格式,与上方 JSONUtils 共用同一套 JsonMapper。
功能说明
JSON 自动配置会自动配置 Spring MVC 的 JSON 转换器,统一使用 JSONUtils 的配置,确保整个应用的 JSON 序列化行为一致。
核心功能
✅ 统一 JSON 转换器
- 替换 Spring 默认的
MappingJackson2HttpMessageConverter - 使用 JSONUtils 的 JsonMapper 配置
- 确保 Controller 返回值和 @RequestBody 参数的 JSON 格式一致
✅ 时间格式统一
- 请求参数时间: 支持 GET 请求参数中的时间类型自动转换
- 响应时间格式: 统一使用
yyyy-MM-dd HH:mm:ss格式 - 时区: 统一使用 GMT+8(东八区)
✅ 字符编码
- 自动配置 UTF-8 字符编码过滤器
- 解决 Form 表单提交中文乱码问题
- 最高优先级(
HIGHEST_PRECEDENCE)
配置说明
Servlet Web 应用下 始终启用,无需额外开关。自动统一 MVC / Feign 的 JSON 与日期格式。
使用示例
示例 1: 统一响应格式
所有 Controller 的返回值都会使用统一的 JSON 格式。
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
User user = new User();
user.setId(id);
user.setName("张三");
user.setCreateTime(LocalDateTime.now());
// 返回的 JSON 格式:
// {
// "id": 1,
// "name": "张三",
// "createTime": "2024-01-18 14:30:00"
// }
return user;
}
}示例 2: 请求参数时间转换
GET 请求参数中的时间字符串会自动转换为对应的时间类型。
@RestController
@RequestMapping("/api")
public class ReportController {
// 支持 GET 请求参数中的时间类型
@GetMapping("/report")
public List<Order> getReport(
@RequestParam LocalDate startDate, // yyyy-MM-dd
@RequestParam LocalDate endDate, // yyyy-MM-dd
@RequestParam LocalDateTime createTime // yyyy-MM-dd HH:mm:ss
) {
// 直接使用转换后的时间类型
return orderService.queryByDateRange(startDate, endDate, createTime);
}
// 请求示例:
// GET /api/report?startDate=2024-01-01&endDate=2024-01-31&createTime=2024-01-18 14:30:00
}示例 3: 请求体 JSON 解析
POST 请求的 @RequestBody 也会使用统一的 JSON 配置。
@RestController
@RequestMapping("/api")
public class OrderController {
@PostMapping("/order")
public Result createOrder(@RequestBody Order order) {
// order.getCreateTime() 会自动解析为 LocalDateTime
// 支持格式: "2024-01-18 14:30:00"
orderService.save(order);
return Result.success();
}
}
@Data
class Order {
private Long id;
private String orderNo;
private LocalDateTime createTime; // 自动解析时间字符串
private LocalDate deliveryDate; // 自动解析日期字符串
}示例 4: 列表数据响应
@RestController
@RequestMapping("/api")
public class ProductController {
@GetMapping("/products")
public List<Product> getProducts() {
// 返回的列表中所有时间字段都会格式化
return productService.listAll();
}
// 响应 JSON 格式:
// [
// {
// "id": 1,
// "name": "商品A",
// "createTime": "2024-01-18 14:30:00",
// "updateTime": "2024-01-18 15:00:00"
// },
// ...
// ]
}技术细节
HTTP 消息转换器配置
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 移除默认的 Jackson 转换器
converters.removeIf(c -> c instanceof MappingJackson2HttpMessageConverter);
// 添加使用 JSONUtils 配置的转换器
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter(JSONUtils.getJsonMapper());
converter.setDefaultCharset(StandardCharsets.UTF_8);
converters.add(converter);
}时间参数格式化
@Override
public void addFormatters(FormatterRegistry registry) {
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
// 配置时间格式
registrar.setTimeFormatter(DateTimeFormatter.ofPattern("HH:mm:ss"));
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
registrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
registrar.registerFormatters(registry);
}字符编码过滤器
@Bean
public OrderedCharacterEncodingFilter characterEncodingFilter() {
OrderedCharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
filter.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filter;
}支持的时间格式
| 时间类型 | 格式 | 示例 | 说明 |
|---|---|---|---|
| LocalDateTime | yyyy-MM-dd HH:mm:ss | 2024-01-18 14:30:00 | 日期时间 |
| LocalDate | yyyy-MM-dd | 2024-01-18 | 日期 |
| LocalTime | HH:mm:ss | 14:30:00 | 时间 |
| Date | yyyy-MM-dd HH:mm:ss | 2024-01-18 14:30:00 | 旧版日期 |
注意事项
⚠️ 配置优先级
- JSON 自动配置在
JacksonAutoConfiguration之前执行(@AutoConfigureBefore) - 会替换 Spring Boot 默认的 Jackson 配置
- 如果你有自定义的 Jackson 配置,可能会被覆盖
⚠️ 始终启用
Servlet Web 下 JSON 自动配置始终生效,无需 enabled 开关。
⚠️ 字符编码
- 自动配置的字符编码过滤器优先级最高
- 确保请求和响应都使用 UTF-8 编码
- 可以解决 Form 表单提交中文乱码问题
⚠️ 依赖
- 需要
spring-boot-starter-web(Servlet Web) - 与项目内其他 Jackson 定制可能叠加,注意
@JsonFormat/ 自定义ObjectMapper
常见问题
Q1: 启用 JSON 配置后,时间格式还是不对?
A: 检查以下几点:
- 确认应用为 Servlet Web(非纯 WebFlux)且
JsonAutoConfiguration已加载 - 查看是否有其他 Jackson 配置覆盖了设置
- 检查实体类字段上是否有
@JsonFormat注解(会覆盖全局配置)
Q2: 如何在启用配置的同时使用自定义格式?
A: 可以在特定字段上使用 @JsonFormat 注解:
@Data
class Event {
@JsonFormat(pattern = "yyyy/MM/dd HH:mm:ss")
private LocalDateTime eventTime; // 使用自定义格式
private LocalDateTime createTime; // 使用全局格式 yyyy-MM-dd HH:mm:ss
}Q3: GET 请求的时间参数格式不对怎么办?
A: 确保请求参数使用正确的格式:
- LocalDateTime:
2024-01-18 14:30:00(注意空格) - LocalDate:
2024-01-18 - LocalTime:
14:30:00
如果需要支持其他格式,可以使用 @DateTimeFormat 注解:
@GetMapping("/report")
public Result getReport(
@RequestParam
@DateTimeFormat(pattern = "yyyy/MM/dd")
LocalDate date
) {
// 支持 2024/01/18 格式
}Q4: 是否会影响现有项目?
A: 会。Servlet Web 下 JSON 配置始终加载,与项目其它 Jackson 定制可能叠加,请注意 @JsonFormat 与自定义 ObjectMapper Bean。
Q5: 与 JSONUtils 的关系?
A:
- JSONUtils 是静态工具类,可以独立使用
- JSON 自动配置会将 JSONUtils 的配置应用到 Spring MVC
- 两者使用相同的 JsonMapper 配置,确保行为一致
最佳实践
1. 统一时间格式
在整个项目中统一使用 yyyy-MM-dd HH:mm:ss 格式,避免前后端时间格式不一致的问题。
2. 禁用其他 Jackson 配置
建议避免再自定义全局 Jackson / ObjectMapper,以免与统一配置冲突。
3. 前端适配
前端在解析时间字符串时,需要按照 yyyy-MM-dd HH:mm:ss 格式处理:
// JavaScript 示例
const dateString = "2024-01-18 14:30:00";
const date = new Date(dateString.replace(' ', 'T')); // 转换为 ISO 格式4. 测试验证
建议编写测试用例验证 JSON 序列化行为:
@SpringBootTest
@AutoConfigureMockMvc
class JsonConfigTest {
@Autowired
private MockMvc mockMvc;
@Test
void testDateTimeFormat() throws Exception {
mockMvc.perform(get("/api/user/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.createTime")
.value(matchesPattern("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")));
}
}