0
# Annotations
1
2
Gson provides several annotations to control serialization and deserialization behavior directly on fields and classes without requiring configuration changes.
3
4
## @SerializedName
5
6
Specifies alternative names for fields during serialization and deserialization.
7
8
```java { .api }
9
@Target({ElementType.FIELD, ElementType.METHOD})
10
@Retention(RetentionPolicy.RUNTIME)
11
public @interface SerializedName {
12
String value();
13
String[] alternate() default {};
14
}
15
```
16
17
**Basic usage:**
18
```java
19
public class Person {
20
@SerializedName("full_name")
21
private String name;
22
23
@SerializedName("years_old")
24
private int age;
25
}
26
27
// JSON: {"full_name":"Alice","years_old":30}
28
```
29
30
**Multiple alternative names for deserialization:**
31
```java
32
public class Person {
33
@SerializedName(value = "name", alternate = {"full_name", "firstName", "first_name"})
34
private String name;
35
}
36
37
// Can deserialize from any of these JSON formats:
38
// {"name":"Alice"}
39
// {"full_name":"Alice"}
40
// {"firstName":"Alice"}
41
// {"first_name":"Alice"}
42
```
43
44
## @Expose
45
46
Controls which fields are included in serialization and deserialization when using `excludeFieldsWithoutExposeAnnotation()`.
47
48
```java { .api }
49
@Target(ElementType.FIELD)
50
@Retention(RetentionPolicy.RUNTIME)
51
public @interface Expose {
52
boolean serialize() default true;
53
boolean deserialize() default true;
54
}
55
```
56
57
**Usage:**
58
```java
59
public class User {
60
@Expose
61
private String name;
62
63
@Expose(serialize = false) // Only for deserialization
64
private String password;
65
66
@Expose(deserialize = false) // Only for serialization
67
private String token;
68
69
private String internalId; // Not exposed
70
}
71
72
Gson gson = new GsonBuilder()
73
.excludeFieldsWithoutExposeAnnotation()
74
.create();
75
76
// Only fields marked with @Expose will be processed
77
```
78
79
## @Since
80
81
Marks fields as available since a specific API version.
82
83
```java { .api }
84
@Target({ElementType.FIELD, ElementType.TYPE})
85
@Retention(RetentionPolicy.RUNTIME)
86
public @interface Since {
87
double value();
88
}
89
```
90
91
**Usage:**
92
```java
93
public class ApiResponse {
94
private String status;
95
96
@Since(1.1)
97
private String message;
98
99
@Since(2.0)
100
private List<String> errors;
101
}
102
103
// Only include fields from version 1.1 and later
104
Gson gson = new GsonBuilder()
105
.setVersion(1.1)
106
.create();
107
108
// 'status' and 'message' will be included, but 'errors' will be excluded
109
```
110
111
## @Until
112
113
Marks fields as available until a specific API version.
114
115
```java { .api }
116
@Target({ElementType.FIELD, ElementType.TYPE})
117
@Retention(RetentionPolicy.RUNTIME)
118
public @interface Until {
119
double value();
120
}
121
```
122
123
**Usage:**
124
```java
125
public class LegacyResponse {
126
private String data;
127
128
@Until(2.0)
129
private String oldFormat; // Deprecated in version 2.0
130
131
@Since(2.0)
132
private String newFormat; // Added in version 2.0
133
}
134
135
// Version 1.5: includes 'data' and 'oldFormat'
136
Gson gson15 = new GsonBuilder().setVersion(1.5).create();
137
138
// Version 2.1: includes 'data' and 'newFormat'
139
Gson gson21 = new GsonBuilder().setVersion(2.1).create();
140
```
141
142
## @JsonAdapter
143
144
Specifies a custom TypeAdapter, JsonSerializer, JsonDeserializer, or TypeAdapterFactory for a field or class.
145
146
```java { .api }
147
@Target({ElementType.TYPE, ElementType.FIELD})
148
@Retention(RetentionPolicy.RUNTIME)
149
public @interface JsonAdapter {
150
Class<?> value();
151
boolean nullSafe() default true;
152
}
153
```
154
155
**Field-level adapter:**
156
```java
157
public class Event {
158
private String name;
159
160
@JsonAdapter(DateAdapter.class)
161
private Date timestamp;
162
}
163
164
public class DateAdapter extends TypeAdapter<Date> {
165
private final DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
166
167
@Override
168
public void write(JsonWriter out, Date date) throws IOException {
169
if (date == null) {
170
out.nullValue();
171
} else {
172
out.value(format.format(date));
173
}
174
}
175
176
@Override
177
public Date read(JsonReader in) throws IOException {
178
if (in.peek() == JsonToken.NULL) {
179
in.nextNull();
180
return null;
181
}
182
try {
183
return format.parse(in.nextString());
184
} catch (ParseException e) {
185
throw new IOException(e);
186
}
187
}
188
}
189
```
190
191
**Class-level adapter:**
192
```java
193
@JsonAdapter(ColorAdapter.class)
194
public class Color {
195
private int red, green, blue;
196
197
public Color(int red, int green, int blue) {
198
this.red = red;
199
this.green = green;
200
this.blue = blue;
201
}
202
}
203
204
public class ColorAdapter extends TypeAdapter<Color> {
205
@Override
206
public void write(JsonWriter out, Color color) throws IOException {
207
if (color == null) {
208
out.nullValue();
209
} else {
210
String hex = String.format("#%02x%02x%02x", color.red, color.green, color.blue);
211
out.value(hex);
212
}
213
}
214
215
@Override
216
public Color read(JsonReader in) throws IOException {
217
if (in.peek() == JsonToken.NULL) {
218
in.nextNull();
219
return null;
220
}
221
222
String hex = in.nextString();
223
if (hex.startsWith("#")) {
224
hex = hex.substring(1);
225
}
226
227
int rgb = Integer.parseInt(hex, 16);
228
return new Color((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
229
}
230
}
231
232
// Usage: Color serializes as "#ff0000" instead of {"red":255,"green":0,"blue":0}
233
```
234
235
**Using with JsonSerializer/JsonDeserializer:**
236
```java
237
public class User {
238
private String name;
239
240
@JsonAdapter(PasswordSerializer.class)
241
private String password;
242
}
243
244
public class PasswordSerializer implements JsonSerializer<String>, JsonDeserializer<String> {
245
@Override
246
public JsonElement serialize(String src, Type typeOfSrc, JsonSerializationContext context) {
247
return new JsonPrimitive("***"); // Always serialize as stars
248
}
249
250
@Override
251
public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
252
return json.getAsString(); // Deserialize normally
253
}
254
}
255
```
256
257
**Using with TypeAdapterFactory:**
258
```java
259
@JsonAdapter(CaseInsensitiveEnumAdapterFactory.class)
260
public enum Status {
261
ACTIVE, INACTIVE, PENDING
262
}
263
264
public class CaseInsensitiveEnumAdapterFactory implements TypeAdapterFactory {
265
@Override
266
@SuppressWarnings("unchecked")
267
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
268
Class<T> rawType = (Class<T>) type.getRawType();
269
if (!rawType.isEnum()) {
270
return null;
271
}
272
273
return new TypeAdapter<T>() {
274
@Override
275
public void write(JsonWriter out, T value) throws IOException {
276
if (value == null) {
277
out.nullValue();
278
} else {
279
out.value(value.toString());
280
}
281
}
282
283
@Override
284
@SuppressWarnings("unchecked")
285
public T read(JsonReader in) throws IOException {
286
if (in.peek() == JsonToken.NULL) {
287
in.nextNull();
288
return null;
289
}
290
291
String value = in.nextString().toUpperCase();
292
return (T) Enum.valueOf((Class<Enum>) rawType, value);
293
}
294
};
295
}
296
}
297
```
298
299
## Combining Annotations
300
301
Annotations can be combined for comprehensive control:
302
303
```java
304
public class Product {
305
@SerializedName("product_id")
306
@Since(1.0)
307
private String id;
308
309
@SerializedName("product_name")
310
@Expose
311
private String name;
312
313
@SerializedName("price_cents")
314
@JsonAdapter(MoneyAdapter.class)
315
private Money price;
316
317
@Until(2.0)
318
private String oldCategory;
319
320
@Since(2.0)
321
@SerializedName("category_info")
322
private Category category;
323
324
@Expose(serialize = false)
325
private String internalNotes;
326
}
327
```
328
329
## Annotation Processing Order
330
331
Gson processes annotations in the following priority:
332
333
1. **@JsonAdapter** - Takes highest precedence
334
2. **@SerializedName** - Controls field naming
335
3. **@Expose** - Controls field inclusion (when enabled)
336
4. **@Since/@Until** - Controls version-based inclusion
337
5. **Field modifiers** - Processed based on GsonBuilder configuration
338
339
**Example:**
340
```java
341
public class Example {
342
@JsonAdapter(CustomAdapter.class) // 1. Custom adapter used
343
@SerializedName("custom_field") // 2. Field name in JSON
344
@Expose(serialize = false) // 3. Only deserialize (if expose filtering enabled)
345
@Since(1.1) // 4. Only if version >= 1.1
346
private String field;
347
}
348
```
349
350
## Best Practices
351
352
**Use @SerializedName for API compatibility:**
353
```java
354
public class ApiModel {
355
@SerializedName("user_id")
356
private String userId; // Java convention: camelCase
357
358
@SerializedName("created_at")
359
private Date createdAt; // API uses snake_case
360
}
361
```
362
363
**Use @Expose for security:**
364
```java
365
public class User {
366
@Expose
367
private String username;
368
369
@Expose
370
private String email;
371
372
private String passwordHash; // Never expose sensitive data
373
374
@Expose(serialize = false)
375
private String password; // Accept for input, never output
376
}
377
```
378
379
**Use versioning for API evolution:**
380
```java
381
public class ApiResponse {
382
private String status;
383
384
@Until(1.9)
385
private String oldErrorMessage;
386
387
@Since(2.0)
388
private ErrorDetails errorDetails;
389
}
390
```
391
392
**Use @JsonAdapter for domain-specific serialization:**
393
```java
394
public class Order {
395
@JsonAdapter(CurrencyAdapter.class)
396
private BigDecimal totalAmount;
397
398
@JsonAdapter(TimestampAdapter.class)
399
private Instant orderTime;
400
401
@JsonAdapter(Base64Adapter.class)
402
private byte[] signature;
403
}
404
```