0
# Type Adapters
1
2
Type adapters provide fine-grained control over JSON serialization and deserialization for specific types. They are the most flexible way to customize Gson's behavior.
3
4
## TypeAdapter Abstract Class
5
6
The base class for all type adapters.
7
8
```java { .api }
9
public abstract class TypeAdapter<T> {
10
public abstract void write(JsonWriter out, T value) throws IOException;
11
public abstract T read(JsonReader in) throws IOException;
12
13
// Convenience methods
14
public final String toJson(T value);
15
public final void toJson(Writer out, T value) throws IOException;
16
public final T fromJson(String json) throws IOException;
17
public final T fromJson(Reader in) throws IOException;
18
19
// Null handling
20
public final TypeAdapter<T> nullSafe();
21
}
22
```
23
24
## Creating Custom Type Adapters
25
26
**Basic type adapter example:**
27
```java
28
public class PersonAdapter extends TypeAdapter<Person> {
29
@Override
30
public void write(JsonWriter out, Person person) throws IOException {
31
if (person == null) {
32
out.nullValue();
33
return;
34
}
35
36
out.beginObject();
37
out.name("name").value(person.getName());
38
out.name("age").value(person.getAge());
39
out.name("email").value(person.getEmail());
40
out.endObject();
41
}
42
43
@Override
44
public Person read(JsonReader in) throws IOException {
45
if (in.peek() == JsonToken.NULL) {
46
in.nextNull();
47
return null;
48
}
49
50
Person person = new Person();
51
in.beginObject();
52
while (in.hasNext()) {
53
String name = in.nextName();
54
switch (name) {
55
case "name":
56
person.setName(in.nextString());
57
break;
58
case "age":
59
person.setAge(in.nextInt());
60
break;
61
case "email":
62
person.setEmail(in.nextString());
63
break;
64
default:
65
in.skipValue(); // Skip unknown properties
66
break;
67
}
68
}
69
in.endObject();
70
return person;
71
}
72
}
73
```
74
75
**Registering the adapter:**
76
```java
77
Gson gson = new GsonBuilder()
78
.registerTypeAdapter(Person.class, new PersonAdapter())
79
.create();
80
81
Person person = new Person("Alice", 30, "alice@example.com");
82
String json = gson.toJson(person);
83
Person restored = gson.fromJson(json, Person.class);
84
```
85
86
## TypeAdapterFactory Interface
87
88
Factory pattern for creating type adapters dynamically.
89
90
```java { .api }
91
public interface TypeAdapterFactory {
92
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type);
93
}
94
```
95
96
**Example factory for enum handling:**
97
```java
98
public class LowercaseEnumTypeAdapterFactory implements TypeAdapterFactory {
99
@Override
100
@SuppressWarnings("unchecked")
101
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
102
Class<T> rawType = (Class<T>) type.getRawType();
103
if (!rawType.isEnum()) {
104
return null; // This factory only handles enums
105
}
106
107
return new TypeAdapter<T>() {
108
@Override
109
public void write(JsonWriter out, T value) throws IOException {
110
if (value == null) {
111
out.nullValue();
112
} else {
113
out.value(value.toString().toLowerCase());
114
}
115
}
116
117
@Override
118
@SuppressWarnings("unchecked")
119
public T read(JsonReader in) throws IOException {
120
if (in.peek() == JsonToken.NULL) {
121
in.nextNull();
122
return null;
123
}
124
125
String value = in.nextString().toUpperCase();
126
return (T) Enum.valueOf((Class<Enum>) rawType, value);
127
}
128
};
129
}
130
}
131
```
132
133
**Registering the factory:**
134
```java
135
Gson gson = new GsonBuilder()
136
.registerTypeAdapterFactory(new LowercaseEnumTypeAdapterFactory())
137
.create();
138
```
139
140
## Built-in Type Adapters
141
142
Gson provides many built-in type adapters accessible through the TypeAdapters utility class.
143
144
```java { .api }
145
public final class TypeAdapters {
146
public static final TypeAdapter<Class> CLASS;
147
public static final TypeAdapter<Boolean> BOOLEAN;
148
public static final TypeAdapter<Boolean> BOOLEAN_AS_STRING;
149
public static final TypeAdapter<Number> BYTE;
150
public static final TypeAdapter<Number> SHORT;
151
public static final TypeAdapter<Number> INTEGER;
152
public static final TypeAdapter<AtomicInteger> ATOMIC_INTEGER;
153
public static final TypeAdapter<AtomicBoolean> ATOMIC_BOOLEAN;
154
public static final TypeAdapter<Number> LONG;
155
public static final TypeAdapter<Number> FLOAT;
156
public static final TypeAdapter<Number> DOUBLE;
157
public static final TypeAdapter<Character> CHARACTER;
158
public static final TypeAdapter<String> STRING;
159
public static final TypeAdapter<StringBuilder> STRING_BUILDER;
160
public static final TypeAdapter<StringBuffer> STRING_BUFFER;
161
public static final TypeAdapter<URL> URL;
162
public static final TypeAdapter<URI> URI;
163
public static final TypeAdapter<UUID> UUID;
164
public static final TypeAdapter<Currency> CURRENCY;
165
public static final TypeAdapter<Calendar> CALENDAR;
166
public static final TypeAdapter<Locale> LOCALE;
167
public static final TypeAdapter<JsonElement> JSON_ELEMENT;
168
169
// Factory methods
170
public static <TT> TypeAdapterFactory newFactory(Class<TT> type, TypeAdapter<TT> typeAdapter);
171
public static <TT> TypeAdapterFactory newFactory(TypeToken<TT> type, TypeAdapter<TT> typeAdapter);
172
public static TypeAdapterFactory newFactoryForMultipleTypes(Class<?> base, Class<?> sub, TypeAdapter<?> typeAdapter);
173
}
174
```
175
176
## Advanced Type Adapter Patterns
177
178
### Generic Type Handling
179
180
```java
181
public class ListTypeAdapterFactory implements TypeAdapterFactory {
182
@Override
183
@SuppressWarnings("unchecked")
184
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
185
Type rawType = type.getRawType();
186
if (rawType != List.class) {
187
return null;
188
}
189
190
Type elementType = ((ParameterizedType) type.getType()).getActualTypeArguments()[0];
191
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
192
193
return (TypeAdapter<T>) new TypeAdapter<List<?>>() {
194
@Override
195
public void write(JsonWriter out, List<?> list) throws IOException {
196
if (list == null) {
197
out.nullValue();
198
return;
199
}
200
201
out.beginArray();
202
for (Object element : list) {
203
elementAdapter.write(out, element);
204
}
205
out.endArray();
206
}
207
208
@Override
209
public List<?> read(JsonReader in) throws IOException {
210
if (in.peek() == JsonToken.NULL) {
211
in.nextNull();
212
return null;
213
}
214
215
List<Object> list = new ArrayList<>();
216
in.beginArray();
217
while (in.hasNext()) {
218
list.add(elementAdapter.read(in));
219
}
220
in.endArray();
221
return list;
222
}
223
};
224
}
225
}
226
```
227
228
### Delegating Adapters
229
230
Sometimes you need to modify the behavior of an existing adapter:
231
232
```java
233
public class TimestampAdapter extends TypeAdapter<Date> {
234
private final TypeAdapter<Date> delegate;
235
236
public TimestampAdapter(TypeAdapter<Date> delegate) {
237
this.delegate = delegate;
238
}
239
240
@Override
241
public void write(JsonWriter out, Date date) throws IOException {
242
if (date == null) {
243
out.nullValue();
244
} else {
245
out.value(date.getTime()); // Write as timestamp
246
}
247
}
248
249
@Override
250
public Date read(JsonReader in) throws IOException {
251
if (in.peek() == JsonToken.NULL) {
252
in.nextNull();
253
return null;
254
}
255
256
long timestamp = in.nextLong();
257
return new Date(timestamp);
258
}
259
}
260
```
261
262
### Hierarchical Type Adapters
263
264
Handle inheritance hierarchies:
265
266
```java
267
Gson gson = new GsonBuilder()
268
.registerTypeHierarchyAdapter(Animal.class, new AnimalAdapter())
269
.create();
270
271
// This adapter will be used for Animal and all its subclasses
272
public class AnimalAdapter implements JsonSerializer<Animal>, JsonDeserializer<Animal> {
273
@Override
274
public JsonElement serialize(Animal src, Type typeOfSrc, JsonSerializationContext context) {
275
JsonObject result = new JsonObject();
276
result.add("type", new JsonPrimitive(src.getClass().getSimpleName()));
277
result.add("properties", context.serialize(src, src.getClass()));
278
return result;
279
}
280
281
@Override
282
public Animal deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
283
JsonObject jsonObject = json.getAsJsonObject();
284
String type = jsonObject.get("type").getAsString();
285
JsonElement element = jsonObject.get("properties");
286
287
try {
288
Class<?> clazz = Class.forName("com.example." + type);
289
return context.deserialize(element, clazz);
290
} catch (ClassNotFoundException e) {
291
throw new JsonParseException("Unknown element type: " + type, e);
292
}
293
}
294
}
295
```
296
297
## Null Handling
298
299
Type adapters can handle nulls explicitly or use the `nullSafe()` wrapper:
300
301
```java
302
public class NullSafeStringAdapter extends TypeAdapter<String> {
303
@Override
304
public void write(JsonWriter out, String value) throws IOException {
305
out.value(value == null ? "NULL" : value);
306
}
307
308
@Override
309
public String read(JsonReader in) throws IOException {
310
String value = in.nextString();
311
return "NULL".equals(value) ? null : value;
312
}
313
}
314
315
// Or use nullSafe wrapper
316
TypeAdapter<String> adapter = new StringAdapter().nullSafe();
317
```
318
319
## Accessing Delegate Adapters
320
321
Get the next adapter in the chain to avoid infinite recursion:
322
323
```java
324
Gson gson = new GsonBuilder()
325
.registerTypeAdapterFactory(new LoggingAdapterFactory())
326
.create();
327
328
public class LoggingAdapterFactory implements TypeAdapterFactory {
329
@Override
330
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
331
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
332
333
return new TypeAdapter<T>() {
334
@Override
335
public void write(JsonWriter out, T value) throws IOException {
336
System.out.println("Serializing: " + value);
337
delegate.write(out, value);
338
}
339
340
@Override
341
public T read(JsonReader in) throws IOException {
342
T result = delegate.read(in);
343
System.out.println("Deserialized: " + result);
344
return result;
345
}
346
};
347
}
348
}
349
```
350
351
## Legacy Serializer/Deserializer Interfaces
352
353
For simpler cases, you can use the legacy interfaces:
354
355
```java { .api }
356
public interface JsonSerializer<T> {
357
public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context);
358
}
359
360
public interface JsonDeserializer<T> {
361
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException;
362
}
363
```
364
365
**Context interfaces:**
366
```java { .api }
367
public interface JsonSerializationContext {
368
public JsonElement serialize(Object src);
369
public JsonElement serialize(Object src, Type typeOfSrc);
370
}
371
372
public interface JsonDeserializationContext {
373
public <T> T deserialize(JsonElement json, Type typeOfT) throws JsonParseException;
374
}
375
```
376
377
**Usage:**
378
```java
379
public class PersonSerializer implements JsonSerializer<Person> {
380
@Override
381
public JsonElement serialize(Person src, Type typeOfSrc, JsonSerializationContext context) {
382
JsonObject obj = new JsonObject();
383
obj.addProperty("fullName", src.getFirstName() + " " + src.getLastName());
384
obj.addProperty("age", src.getAge());
385
return obj;
386
}
387
}
388
389
Gson gson = new GsonBuilder()
390
.registerTypeAdapter(Person.class, new PersonSerializer())
391
.create();
392
```