Gson注解大全:@Expose、@SerializedName实战
Gson(Google JSON)是一个强大的Java序列化/反序列化库,能够将Java对象与JSON格式相互转换。在Gson的众多特性中,注解(Annotation)机制为开发者提供了灵活的对象序列化控制手段。本文将深入剖析Gson中最常用的两个核心注解——@Expose和@SerializedName,通过源码解析、实战案例和最佳实践,帮助开发者掌握注解的高级应用技巧,解决JSON字段映射与序列化控制的常见痛点。
注解基础:Gson注解体系概述
Gson注解是Java对象与JSON转换过程中的"元数据指令",通过在类字段或方法上添加特定注解,开发者可以精确控制序列化/反序列化行为。Gson注解体系主要包含字段控制(如@Expose)、命名映射(如@SerializedName)、版本控制(@Since/@Until)和自定义适配器(@JsonAdapter)四大类。其中@Expose和@SerializedName作为最基础也最常用的注解,构成了Gson对象映射的核心控制手段。
Gson注解的实现位于gson/src/main/java/com/google/gson/annotations/目录下,所有注解均使用@Retention(RetentionPolicy.RUNTIME)保留策略,确保在运行时可通过反射机制被Gson框架识别处理。
@Expose:序列化/反序列化权限控制
@Expose注解用于标记类字段是否参与JSON序列化与反序列化过程,提供了细粒度的序列化权限控制。与Java的transient关键字不同,@Expose通过注解参数实现更灵活的序列化策略配置。
注解定义与参数解析
@Expose注解的源码定义如下(gson/src/main/java/com/google/gson/annotations/Expose.java):
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Expose {
/**
* 是否参与序列化,默认true
*/
boolean serialize() default true;
/**
* 是否参与反序列化,默认true
*/
boolean deserialize() default true;
}
该注解包含两个布尔型参数:
serialize:控制字段是否被序列化(Java对象→JSON)deserialize:控制字段是否被反序列化(JSON→Java对象)
默认情况下,两个参数均为true,表示字段既参与序列化也参与反序列化。
启用与生效条件
重要注意:@Expose注解默认不生效,必须通过GsonBuilder显式启用排除未标注@Expose的字段:
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation() // 启用@Expose过滤
.create();
未启用此配置时,Gson将忽略@Expose注解,所有字段(除transient修饰的)均参与序列化/反序列化。
使用场景与示例
基础使用示例
public class User {
@Expose private String username; // 参与序列化和反序列化
@Expose(serialize = false) private String password; // 仅反序列化
@Expose(deserialize = false) private String token; // 仅序列化
private String internalId; // 未标注,启用过滤后不参与
}
测试用例分析
Gson官方测试类ExposeFieldsTest(gson/src/test/java/com/google/gson/functional/ExposeFieldsTest.java)定义了如下测试模型:
private static class ExposeTestTarget {
@Expose private final Integer a;
@Expose(serialize = false) private final Integer b;
@Expose(deserialize = false) private final Integer c;
@Expose(serialize = false, deserialize = false) private final Integer d;
private final Integer e; // 未标注@Expose
}
当启用excludeFieldsWithoutExposeAnnotation()后,不同操作的字段包含情况如下:
| 字段 | 序列化(serialize) | 反序列化(deserialize) | 说明 |
|---|---|---|---|
| a | ✅ 包含 | ✅ 包含 | 默认配置,双向参与 |
| b | ❌ 排除 | ✅ 包含 | 仅反序列化 |
| c | ✅ 包含 | ❌ 排除 | 仅序列化 |
| d | ❌ 排除 | ❌ 排除 | 双向排除 |
| e | ❌ 排除 | ❌ 排除 | 未标注@Expose |
与transient的区别
| 特性 | @Expose注解 | transient关键字 |
|---|---|---|
| 控制粒度 | 可分别控制序列化/反序列化 | 同时控制序列化和反序列化 |
| 启用方式 | 需要GsonBuilder配置 | JVM原生支持,无需额外配置 |
| 灵活性 | 高(四个组合模式) | 低(仅开/关) |
| 适用场景 | 细粒度权限控制 | 简单排除需求 |
高级用法:Java 17 Records支持
Gson对Java 17 Records的@Expose支持可参考测试类Java17RecordTest(gson/src/test/java/com/google/gson/functional/Java17RecordTest.java):
record RecordWithExpose(@Expose int a, int b) {}
// 序列化结果仅包含标注@Expose的字段a
@SerializedName:JSON字段命名映射
@SerializedName注解用于指定Java字段与JSON属性之间的映射关系,解决Java命名规范(驼峰式)与JSON命名规范(下划线、短横线等)不一致的问题,同时支持多别名反序列化。
注解定义与参数解析
@SerializedName注解的源码定义如下(gson/src/main/java/com/google/gson/annotations/SerializedName.java):
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface SerializedName {
/**
* JSON序列化的字段名
*/
String value();
/**
* 反序列化时的备选字段名
*/
String[] alternate() default {};
}
包含两个参数:
value:必填,指定序列化时使用的JSON字段名alternate:可选,指定反序列化时可接受的备选字段名数组
基础命名映射
最常见的用途是修改JSON输出的字段名:
public class User {
@SerializedName("user_name")
private String userName; // Java驼峰式,JSON下划线式
}
// 序列化结果: {"user_name":"张三"}
多别名反序列化
alternate参数允许JSON使用多个字段名映射到同一个Java字段:
public class Metric {
@SerializedName(value = "temp", alternate = {"temperature", "t"})
private double temperature;
}
反序列化时,以下三种JSON格式均可正确映射:
{"temp": 25.5}
{"temperature": 25.5}
{"t": 25.5}
注意:alternate仅影响反序列化,序列化时始终使用value指定的名称。
枚举类型的序列化控制
@SerializedName可用于修改枚举常量的JSON表示,如测试类EnumTest(gson/src/test/java/com/google/gson/functional/EnumTest.java)所示:
private enum Gender {
@SerializedName("boy") MALE,
@SerializedName("girl") FEMALE
}
// 序列化Gender.MALE将得到"boy"
另一个测试类EnumWithObfuscatedTest(gson/src/test/java/com/google/gson/functional/EnumWithObfuscatedTest.java)展示了混淆场景的应用:
enum MailType {
@SerializedName("MAIL") MAIL_TYPE,
@SerializedName("FEMAIL") FEMAIL_TYPE
}
特殊字符与命名策略
@SerializedName可以定义包含特殊字符的JSON字段名,如测试类NamingPolicyTest(gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java)中的示例:
private static class SpecialSerializedName {
@SerializedName("@foo") String atFoo;
@SerializedName("a") String b;
@SerializedName("@value\"_s$\\") String specialChars;
}
序列化结果将保留这些特殊字符,不受全局命名策略影响。
实战技巧与最佳实践
组合使用策略
@Expose和@SerializedName可以组合使用,实现更精确的控制:
public class Product {
@Expose
@SerializedName("product_id")
private Long id;
@Expose(serialize = false)
@SerializedName(value = "price", alternate = {"cost", "value"})
private BigDecimal price;
}
常见问题解决方案
问题1:@Expose注解不生效
排查步骤:
- 确认是否通过
GsonBuilder启用了excludeFieldsWithoutExposeAnnotation() - 检查字段是否为
private(Gson默认只处理private字段) - 确认字段未被
transient修饰(transient优先级高于@Expose)
问题2:alternate别名不生效
排查步骤:
- 确认Gson版本≥2.4(
alternate参数在2.4版本引入) - 检查是否将
alternate用于序列化(仅反序列化有效) - 确认JSON中是否同时出现多个别名(此时取第一个匹配的值)
性能考量
- 注解处理开销:Gson在首次序列化类时缓存注解信息,后续使用无性能损耗
- 字段过滤效率:启用
excludeFieldsWithoutExposeAnnotation()后,字段过滤在反射阶段完成,建议对敏感字段优先使用transient
与其他特性的协同
与TypeAdapter的配合
自定义TypeAdapter时,@SerializedName定义的名称仍会生效,适配器将接收注解指定的JSON字段名。
与版本控制注解的协同
@SerializedName可与@Since/@Until版本注解配合使用:
public class Article {
@Expose
@SerializedName("title")
@Since(1.0)
private String title;
@Expose
@SerializedName("content")
@Since(1.1)
private String content;
}
通过GsonBuilder.setVersion(1.0)可控制不同版本字段的包含。
总结与扩展学习
核心知识点回顾
| 注解 | 核心作用 | 关键参数 | 启用方式 |
|---|---|---|---|
@Expose | 控制序列化/反序列化权限 | serialize、deserialize | GsonBuilder.excludeFieldsWithoutExposeAnnotation() |
@SerializedName | 定义JSON字段名映射 | value、alternate | 无需额外配置,直接生效 |
注解处理流程
扩展学习资源
- 官方文档:UserGuide.md
- 高级注解:
@JsonAdapter自定义适配器注解 - 源码解析:ReflectiveTypeAdapterFactory.java(注解处理实现)
通过掌握@Expose和@SerializedName的使用技巧,开发者可以轻松应对大多数JSON映射场景,编写更安全、更灵活的序列化代码。在实际项目中,建议结合具体业务需求制定注解使用规范,并充分利用Gson提供的测试用例验证序列化行为。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



