如何使用Spring Boot实现JSON内嵌JSON结构的API响应
Spring Boot + Java 17 实现多层内嵌JSON接口返回方案
核心逻辑不需要手动拼接JSON字符串,利用Spring Boot默认集成的Jackson序列化能力,对齐结构定义Java对象+组装数据后直接返回即可,具体实现步骤如下:
1. 定义与目标JSON结构完全对齐的响应VO
不要直接把数据库实体类当做返回对象,单独定义纯响应用的VO层对象,字段名、层级和目标JSON一一对应:
- 顶层根节点单独定义,包含所有顶层key对应的字段
- 内嵌JSON对象直接定义对应类型的成员变量,内嵌JSON数组用集合类型声明
示例代码:
// 顶层响应结构,对应JSON最外层的所有key public record DbMockResponse( List<UserVO> users, List<PostVO> posts, List<CommentVO> comments ) {} // 用户对象 public record UserVO( Long id, String name, String email ) {} // 评论对象 public record CommentVO( Long id, String content, Long postId, Long userId ) {} // 文章对象,包含内嵌的作者对象、评论数组 public record PostVO( Long id, String title, String body, UserVO author, // 对应内嵌JSON对象 List<CommentVO> comments // 对应内嵌JSON数组 ) {}
如果目标JSON的key是下划线命名(比如create_time),和Java驼峰命名不匹配,有两种处理方式:要么在字段上加@JsonProperty("create_time")指定序列化后的key名,要么全局配置Jackson的命名策略自动转换。
2. 数据查询与组装
因为数据存储在本地数据库,不要依赖ORM框架的级联查询硬做关联,用内存组装的方式性能更高、逻辑更可控:
- 先查询所有需要的基础表数据,把关联数据构建成ID映射的Map,避免循环查库
- 按照目标JSON的层级关系,把关联对象填充到对应VO的内嵌字段中
示例服务层代码:
@Service public class DbMockService { private final UserRepository userRepository; private final PostRepository postRepository; private final CommentRepository commentRepository; // 构造方法注入,无需写@Autowired public DbMockService(UserRepository userRepository, PostRepository postRepository, CommentRepository commentRepository) { this.userRepository = userRepository; this.postRepository = postRepository; this.commentRepository = commentRepository; } public DbMockResponse getAssembledResponse() { // 1. 全量查询基础表数据 List<User> userDOList = userRepository.findAll(); List<Post> postDOList = postRepository.findAll(); List<Comment> commentDOList = commentRepository.findAll(); // 2. 构建ID索引,方便快速匹配关联数据 Map<Long, UserVO> userVOMap = userDOList.stream() .map(user -> new UserVO(user.getId(), user.getName(), user.getEmail())) .collect(Collectors.toMap(UserVO::id, Function.identity())); Map<Long, List<CommentVO>> postCommentMap = commentDOList.stream() .map(comment -> new CommentVO(comment.getId(), comment.getContent(), comment.getPostId(), comment.getUserId())) .collect(Collectors.groupingBy(CommentVO::postId)); // 3. 组装文章的内嵌字段 List<PostVO> postVOList = postDOList.stream() .map(post -> new PostVO( post.getId(), post.getTitle(), post.getBody(), // 填充内嵌作者对象 userVOMap.get(post.getAuthorId()), // 填充当前文章对应的内嵌评论数组 postCommentMap.getOrDefault(post.getId(), Collections.emptyList()) )).toList(); // 4. 封装成顶层响应对象返回 return new DbMockResponse(new ArrayList<>(userVOMap.values()), postVOList, commentDOList.stream() .map(comment -> new CommentVO(comment.getId(), comment.getContent(), comment.getPostId(), comment.getUserId())) .toList()); } }
3. 接口层直接返回组装好的对象
Spring Boot会自动通过Jackson把返回的Java对象序列化成JSON格式,多层嵌套的对象会自动转成内嵌JSON结构,不需要额外做序列化处理:
@RestController public class DbMockController { private final DbMockService dbMockService; public DbMockController(DbMockService dbMockService) { this.dbMockService = dbMockService; } @GetMapping("/db") public DbMockResponse getDbJson() { return dbMockService.getAssembledResponse(); } }
常见配置优化(对齐示例结构)
在application.yml中添加以下配置,避免字段不匹配、空值多余的问题:
spring: jackson: # 全局自动把驼峰字段转成下划线格式序列化,适配常见JSON命名规范 property-naming-strategy: SNAKE_CASE # 序列化时忽略null字段,避免返回多余的null值 default-property-inclusion: non_null
避坑提醒
- 不要手动拼接JSON字符串返回:手动拼接容易出现转义错误、格式异常,且返回字符串时Spring会把内容当做普通文本处理,不会按JSON格式解析
- 避免循环引用:如果两个VO互相持有对方引用(比如UserVO里存List
,PostVO里存UserVO),需要在其中一方的字段上加 @JsonBackReference注解,避免序列化时出现栈溢出 - 不要直接返回数据库实体:数据库实体通常和接口返回结构存在差异,直接返回容易出现字段泄露、结构不匹配的问题,单独定义VO层是最稳妥的做法
内容的提问来源于stack exchange,提问作者Caio Alexandre
相关产品推荐
相关产品推荐

