or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotations.mdconfiguration.mdindex.mdjson-tree-model.mdobject-serialization.mdstreaming-api.mdtype-adapters.md

type-adapters.mddocs/

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

```