Spring Boot中单值对象JSON序列化扁平化处理教程


Spring Boot中单值对象JSON序列化扁平化处理教程

本文旨在解决spring boot应用中,将包含单值j*a对象的实体序列化为json时,出现嵌套结构而非扁平化字符串的问题。通过引入数据传输对象(dto)模式,我们将详细演示如何重构数据模型和api响应,以实现更简洁、符合预期的json输出格式,同时提升api设计的灵活性与安全性。

在构建基于Spring Boot和Hibernate的RESTful服务时,我们经常会遇到将领域模型对象转换为JSON响应的需求。然而,当领域模型中包含一些本质上是“单值对象”(Value Object),例如包装了字符串的EmailAddress类时,默认的JSON序列化行为可能会导致不必要的嵌套结构,这与我们期望的扁平化表示不符。

问题场景描述

假设我们有一个EmailAddress类,它封装了一个电子邮件地址的字符串值以及一些相关业务逻辑(如获取域名、邮箱等)。

// EmailAddress.j*a
public class EmailAddress {
    public String value; // 实际的邮箱地址字符串

    public EmailAddress(String value) {
        this.value = value;
    }

    // 假设还有其他业务方法,如tld(), host(), mailbox()等
    public String tld() { /* ... */ return "com"; }
    public String host() { /* ... */ return "example.com"; }
    public String mailbox() { /* ... */ return "user"; }

    // 为了方便演示,添加getter
    public String getValue() {
        return value;
    }
}

接着,这个EmailAddress类被用作Customer实体的一个字段:

// Customer.j*a
import j*ax.persistence.Entity;
import j*ax.persistence.GeneratedValue;
import j*ax.persistence.GenerationType;
import j*ax.persistence.Id;

@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private EmailAddress mail; // 使用EmailAddress对象

    // 构造函数、getter/setter略
    public Customer() {}

    public Customer(String name, EmailAddress mail) {
        this.name = name;
        this.mail = mail;
    }

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public EmailAddress getMail() { return mail; }
    public void setMail(EmailAddress mail) { this.mail = mail; }
}

当通过Spring Boot的REST服务暴露Customer实体时,默认的Jackson序列化器会将其中的EmailAddress对象视为一个普通的POJO,并将其内部的value字段也序列化出来,导致生成如下的JSON结构:

{
    "id": 1,
    "name": "Test",
    "mail": {
        "value": "test@example.com"
    }
}

然而,我们期望的输出是更简洁的扁平化结构,直接将mail字段表示为字符串:

{
    "id": 1,
    "name": "Test",
    "mail": "test@example.com"
}

解决方案:引入数据传输对象(DTO)

为了解决这个问题,最推荐且最灵活的方法是引入数据传输对象(DTO)。DTO是一种设计模式,用于在应用程序的不同层之间传输数据。它将API响应与内部领域模型解耦,允许我们精确控制API的输出格式。

1. 定义DTO类

首先,为我们的API响应定义一个CustomerDTO类。在这个DTO中,我们将EmailAddress字段扁平化为一个简单的String类型。

// CustomerDTO.j*a
public class CustomerDTO {
    private Long id;
    private String name;
    private String email; // 将EmailAddress扁平化为String

    // 构造函数、getter/setter
    public CustomerDTO() {}

    public CustomerDTO(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

2. 实现实体到DTO的映射

接下来,我们需要在服务层或控制器层实现从Customer实体到CustomerDTO的映射逻辑。这可以通过手动创建DTO实例、使用构造函数、或者利用专门的映射库(如ModelMapper、MapStruct)来完成。

手动映射示例:

// CustomerService.j*a (示例服务层)
import org.springframework.stereotype.Service;
import j*a.util.List;
import j*a.util.stream.Collectors;

@Service
public class CustomerService {

    // 假设这里有CustomerRepository或其他数据访问层
    // private final CustomerRepository customerRepository;

    // 构造函数注入略

    public CustomerDTO getCustomerById(Long id) {
        // 假设从数据库获取Customer实体
        Customer customer = new Customer(
            "Test", new EmailAddress("test@example.com")
        ); // 模拟从数据库获取
        customer.setId(id);

        return mapToDTO(customer);
    }

    public List<CustomerDTO> getAllCustomers() {
        // 假设从数据库获取所有Customer实体
        List<Customer> customers = List.of(
            new Customer("Alice", new EmailAddress("alice@example.com")),
            new Customer("Bob", new EmailAddress("bob@example.com"))
        );
        customers.get(0).setId(1L);
        customers.get(1).setId(2L);

        return customers.stream()
                        .map(this::mapToDTO)
                        .collect(Collectors.toList());
    }

    private CustomerDTO mapToDTO(Customer customer) {
        if (customer == null) {
            return null;
        }
        return new CustomerDTO(
            customer.getId(),
            customer.getName(),
            customer.getMail() != null ? customer.getMail().getValue() : null
        );
    }

    // 如果需要处理传入的DTO,也需要实现DTO到实体的映射
    public Customer s*eCustomer(CustomerDTO customerDTO) {
        Customer customer = mapToEntity(customerDTO);
        // 保存customer到数据库
        return customer;
    }

    private Customer mapToEntity(CustomerDTO customerDTO) {
        if (customerDTO == null) {
            return null;
        }
        return new Customer(
            customerDTO.getName(),
            customerDTO.getEmail() != null ? new EmailAddress(customerDTO.getEmail()) : null
        );
    }
}

3. 更新REST控制器

最后,修改您的REST控制器,使其返回CustomerDTO对象而不是Customer实体。

Magician Magician

Figma插件,AI生成图标、图片和UX文案

Magician 412 查看详情 Magician
// CustomerController.j*a
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import j*a.util.List;

@RestController
@RequestMapping("/api/customers")
public class CustomerController {

    private final CustomerService customerService;

    public CustomerController(CustomerService customerService) {
        this.customerService = customerService;
    }

    @GetMapping("/{id}")
    public CustomerDTO getCustomer(@PathVariable Long id) {
        return customerService.getCustomerById(id);
    }

    @GetMapping
    public List<CustomerDTO> getAllCustomers() {
        return customerService.getAllCustomers();
    }
}

现在,当您访问/api/customers/1时,将会得到期望的扁平化JSON响应:

{
    "id": 1,
    "name": "Test",
    "email": "test@example.com"
}

注意事项与最佳实践

  1. 映射库的选择: 对于复杂的实体和DTO映射,手动编写映射代码会变得繁琐且容易出错。推荐使用成熟的映射库:

    • ModelMapper: 运行时反射映射,配置简单,但性能略低于编译时映射。
    • MapStruct: 编译时代码生成,性能优异,但需要更多配置。
  2. DTO的职责: DTO应专注于数据传输,不包含业务逻辑。它通常只包含字段、构造函数、getter和setter。

  3. 双向映射: 在处理POST/PUT请求时,您可能还需要实现从DTO到实体的映射。确保在映射过程中处理好可能的空值和数据转换。

  4. 数据验证: 可以在DTO上使用JSR 303/349 (Bean Validation) 注解(如@NotBlank, @Email等)来对传入的数据进行验证,从而将验证逻辑与领域模型分离。

  5. 安全性: 使用DTO可以避免在API响应中暴露敏感的内部实体字段,从而提高安全性。

  6. @JsonValue注解(替代方案): 如果EmailAddress对象在任何JSON序列化场景下都应该被表示为一个简单的字符串,那么可以直接在EmailAddress类上使用@JsonValue注解。

    // EmailAddress.j*a
    import com.fasterxml.jackson.annotation.JsonValue;
    
    public class EmailAddress {
        public String value;
    
        public EmailAddress(String value) {
            this.value = value;
        }
    
        @JsonValue // 标记此方法返回的值作为JSON序列化的内容
        public String getValue() {
            return value;
        }
        // 其他方法...
    }

    这种方式更简洁,但不如DTO灵活,因为它强制了EmailAddress的单一JSON表示。如果EmailAddress在不同上下文需要不同的JSON表示,DTO是更好的选择。

总结

通过采用数据传输对象(DTO)模式,我们能够有效地解决Spring Boot中单值对象默认JSON序列化为嵌套结构的问题。DTO不仅能帮助我们实现扁平化的JSON输出,还带来了API与领域模型解耦、增强数据验证、提升安全性和灵活性的诸多优势。在实际开发中,结合映射库的使用,可以进一步提高开发效率和代码质量。

以上就是Spring Boot中单值对象JSON序列化扁平化处理教程的详细内容,更多请关注其它相关文章!


# 您的  # 滕州英文网站推广  # 太原抖音营销推广招聘  # seo销售注意什么  # 安阳如何优化网站推广  # 企业seo好吗  # 广州网站建设及推广方案  # 怎样推广网站设计师呢  # 金沙网站关键词优化  # seo搜索价格营销  # seo运营培训  # 将会  # 在这个  # 是一种  # 来了  # java  # 配置文件  # 装了  # 重构  # 序列化  # 扁平化  # string类  # 数据访问  # 邮箱  # stream  # ai  # app  # json  # js 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 优化推广96088 】 【 技术知识133117 】 【 IDC资讯59369 】 【 网络运营7196 】 【 IT资讯61894


相关推荐: 《豆瓣》私信用户方法  优化Asyncio嵌套函数调度:使用生产者-消费者模式实现并发流处理  WooCommerce 购物车:始终显示所有交叉销售商品  雨课堂官网在线登录 网页版雨课堂登录链接  mysql镜像配置如何设置用户权限组_mysql镜像配置用户组与权限分级管理方法  《一起考教师》账号注销方法  Django模型动态关联检查:高效管理复杂关系  圆通快递官网入口查询单号 手机版官方查询入口  《偃武》甘宁技能详解  Sublime怎么配置YAML文件格式化_Sublime YAML Formatter插件教程  《伊瑟》凶影追缉库卢鲁boss攻略  汽水音乐在线听歌网页版 汽水音乐在线听歌网页版入口  PHP与SQL实践:高效实现数据复制与特定列值修改  Highcharts雷达图径向轴数值标签实现教程  多闪电脑版下载_多闪PC端模拟器使用  智慧团建活动报名入口 智慧团建活动报名入口手机端官网​  CSS过渡与滚动滚动事件结合应用_scroll与transition动画  微信朋友圈怎么设置三天可见 微信朋友圈设置指定天数可见步骤【教程】  解决VS Code中Python版本冲突与输出异常的指南  虫虫漫画绿色安全入口_虫虫漫画绿色安全入口安全看漫画  C++ virtual析构函数作用_C++基类虚析构函数防止内存泄漏  抖音官网入口快速访问 抖音网页版账号注册解析  《波斯王子:失落的王冠》剑术大师打法攻略  win11如何运行chkdsk命令 Win11检查和修复磁盘逻辑错误教程【修复】  Symfony路由参数转换器:实体存在性验证与错误处理策略  鸣潮历史学家灯塔位置一览  哔哩哔哩黑名单怎么查看  小红书如何引流到私信?引流到私信有用吗?  PPT智能排版生成入口 免费PPT内容自动生成平台  mysql镜像配置如何恢复数据_mysql镜像配置数据恢复详细流程  如何在解析前预检查XML文件的完整性? 比如检查文件大小或特定结束标签  基于 Flink 和 Kafka 实现高效流处理:连续查询与时间窗口  抖音赚钱快速入门_新手必看的抖音赚钱步骤  《梦想世界:长风问剑录》药师一图流分享  Highcharts雷达图轴线交点数值标注指南  抖音商城官网是什么_抖音商城官方网址与访问方法  《小宇宙》标记不友善评论方法  iPhone 13 Pro Max如何设置桌面小组件_iPhone 13 Pro Max小组件添加指南  SQL聚合查询、联接与筛选:GROUP BY 子句的正确使用与常见陷阱  Golang如何初始化module项目_Golang module init使用说明  《百度畅听版》关闭兴趣推荐方法  招商淘客入门指南  微星主板BIOS怎么调整内存时序_内存参数手动优化BIOS设置教程  yandex网页版直接登录 yandex官方入口平台访问方法  解决PHP MySQL数据库更新无响应:SQL查询语法错误解析  Lar*el Socialite单设备登录策略:实现用户唯一会话管理  Sublime怎么自动添加CSS前缀_Sublime安装Autoprefixer插件  《360浏览器》自动保存账号密码设置方法  《荔枝fm》导出文件教程  QQ网页版官方账号登录入口 QQ网页版网页版入口快速导航 

 2025-12-04

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

运城市盐湖区信雨科技有限公司


运城市盐湖区信雨科技有限公司

运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。

 8156699

 13765294890

 8156699@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.