0
# Type System and Coercion
1
2
Comprehensive type conversion system supporting custom type coercers, generic type handling with `TypeToken`, and extensible type conversion patterns for complex object hierarchies. This system enables seamless conversion between JSON and Java types with full control over the conversion process.
3
4
## Capabilities
5
6
### TypeToken Class
7
8
Generic type capture utility for handling parameterized types safely.
9
10
```java { .api }
11
/**
12
* Captures generic type information for type-safe operations with parameterized types.
13
* Used to preserve generic type information that would otherwise be lost due to type erasure.
14
*/
15
public abstract class TypeToken<T> {
16
17
/**
18
* Get the captured Type information.
19
*
20
* @return Type object representing the parameterized type
21
*/
22
public Type getType();
23
}
24
```
25
26
### TypeCoercer Abstract Class
27
28
Base class for implementing custom type conversion logic.
29
30
```java { .api }
31
/**
32
* Base class for custom type conversion between JSON and Java objects.
33
* Implements both Predicate<Class<?>> for type testing and Function for coercion logic.
34
*/
35
public abstract class TypeCoercer<T> implements Predicate<Class<?>>, Function<Type, BiFunction<JsonInput, PropertySetting, T>> {
36
37
/**
38
* Test if this coercer can handle the specified class.
39
*
40
* @param aClass the class to test
41
* @return true if this coercer can handle the class; false otherwise
42
*/
43
@Override
44
public abstract boolean test(Class<?> aClass);
45
46
/**
47
* Apply coercion logic for the specified type.
48
*
49
* @param type the type to coerce to
50
* @return BiFunction that performs the actual coercion
51
*/
52
@Override
53
public abstract BiFunction<JsonInput, PropertySetting, T> apply(Type type);
54
}
55
```
56
57
### PropertySetting Enum
58
59
Strategy enumeration for property assignment during deserialization.
60
61
```java { .api }
62
/**
63
* Used to specify the strategy used to assign values during deserialization.
64
*/
65
public enum PropertySetting {
66
/** Values are stored via the corresponding Bean setter methods */
67
BY_NAME,
68
/** Values are stored in fields with the indicated names */
69
BY_FIELD
70
}
71
```
72
73
### Built-in Type Support
74
75
The library includes comprehensive built-in support for standard Java types:
76
77
```java { .api }
78
// Primitive wrapper types
79
Boolean.class, Byte.class, Double.class, Float.class, Integer.class, Long.class, Short.class
80
81
// Collection types
82
List.class, Set.class, Array types
83
84
// Standard Java types
85
Map.class, String.class, Enum.class, URI.class, URL.class, UUID.class,
86
Instant.class, Date.class, File.class, Level.class (java.util.logging)
87
88
// Special handling
89
Optional.class, CharSequence.class
90
```
91
92
## Usage Examples
93
94
### Basic TypeToken Usage
95
96
```java
97
import org.openqa.selenium.json.Json;
98
import org.openqa.selenium.json.TypeToken;
99
import java.lang.reflect.Type;
100
import java.util.List;
101
import java.util.Map;
102
103
Json json = new Json();
104
105
// Create TypeToken for List<String>
106
Type listOfStrings = new TypeToken<List<String>>() {}.getType();
107
List<String> names = json.toType("[\"John\",\"Jane\",\"Bob\"]", listOfStrings);
108
109
// Create TypeToken for Map<String, Integer>
110
Type mapOfStringToInt = new TypeToken<Map<String, Integer>>() {}.getType();
111
Map<String, Integer> scores = json.toType("{\"John\":95,\"Jane\":87}", mapOfStringToInt);
112
113
// Create TypeToken for complex nested types
114
Type complexType = new TypeToken<List<Map<String, Object>>>() {}.getType();
115
List<Map<String, Object>> data = json.toType(jsonString, complexType);
116
117
// Use predefined type constants
118
Map<String, Object> simpleMap = json.toType(jsonString, Json.MAP_TYPE);
119
List<Map<String, Object>> listOfMaps = json.toType(jsonString, Json.LIST_OF_MAPS_TYPE);
120
```
121
122
### Property Setting Strategies
123
124
```java
125
// JavaBean-style object
126
public class Person {
127
private String name;
128
private int age;
129
130
// Getters and setters
131
public String getName() { return name; }
132
public void setName(String name) { this.name = name; }
133
public int getAge() { return age; }
134
public void setAge(int age) { this.age = age; }
135
}
136
137
// Field-access style object
138
public class Product {
139
public String name; // Public fields
140
public double price;
141
}
142
143
Json json = new Json();
144
String personJson = "{\"name\":\"John\",\"age\":30}";
145
146
// Use setter methods (default)
147
Person person1 = json.toType(personJson, Person.class, PropertySetting.BY_NAME);
148
149
// Use direct field access
150
Person person2 = json.toType(personJson, Person.class, PropertySetting.BY_FIELD);
151
152
// For objects with public fields, BY_FIELD is more appropriate
153
String productJson = "{\"name\":\"Laptop\",\"price\":999.99}";
154
Product product = json.toType(productJson, Product.class, PropertySetting.BY_FIELD);
155
```
156
157
### Custom TypeCoercer Implementation
158
159
```java
160
import java.time.LocalDate;
161
import java.time.format.DateTimeFormatter;
162
163
// Custom coercer for LocalDate
164
public class LocalDateCoercer extends TypeCoercer<LocalDate> {
165
166
@Override
167
public boolean test(Class<?> aClass) {
168
return LocalDate.class.isAssignableFrom(aClass);
169
}
170
171
@Override
172
public BiFunction<JsonInput, PropertySetting, LocalDate> apply(Type type) {
173
return (jsonInput, propertySetting) -> {
174
String dateString = jsonInput.nextString();
175
return LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE);
176
};
177
}
178
}
179
180
// Usage with JsonInput
181
String jsonWithDate = "{\"eventDate\":\"2023-12-25\"}";
182
183
try (JsonInput input = json.newInput(new StringReader(jsonWithDate))) {
184
input.addCoercers(new LocalDateCoercer());
185
186
input.beginObject();
187
input.nextName(); // "eventDate"
188
LocalDate date = input.read(LocalDate.class);
189
input.endObject();
190
191
System.out.println("Event date: " + date);
192
}
193
```
194
195
### Complex Custom Coercer with Multiple Types
196
197
```java
198
// Custom coercer for handling different numeric formats
199
public class FlexibleNumberCoercer extends TypeCoercer<Number> {
200
201
@Override
202
public boolean test(Class<?> aClass) {
203
return Number.class.isAssignableFrom(aClass) &&
204
!aClass.equals(Integer.class) &&
205
!aClass.equals(Double.class); // Don't override built-ins
206
}
207
208
@Override
209
public BiFunction<JsonInput, PropertySetting, Number> apply(Type type) {
210
return (jsonInput, propertySetting) -> {
211
JsonType jsonType = jsonInput.peek();
212
213
if (jsonType == JsonType.STRING) {
214
// Handle numeric strings
215
String numStr = jsonInput.nextString().trim();
216
if (numStr.contains(".")) {
217
return Double.parseDouble(numStr);
218
} else {
219
return Long.parseLong(numStr);
220
}
221
} else if (jsonType == JsonType.NUMBER) {
222
// Handle regular numbers
223
return jsonInput.nextNumber();
224
} else {
225
throw new JsonException("Cannot coerce " + jsonType + " to Number");
226
}
227
};
228
}
229
}
230
```
231
232
### Object Serialization Patterns
233
234
```java
235
// Object with custom serialization method
236
public class CustomSerializableObject {
237
private String data;
238
private int value;
239
240
public CustomSerializableObject(String data, int value) {
241
this.data = data;
242
this.value = value;
243
}
244
245
// Custom serialization - library will use this method
246
public Map<String, Object> toJson() {
247
Map<String, Object> result = new HashMap<>();
248
result.put("customData", data.toUpperCase());
249
result.put("customValue", value * 2);
250
result.put("timestamp", System.currentTimeMillis());
251
return result;
252
}
253
254
// Custom deserialization - library will use this method
255
public static CustomSerializableObject fromJson(Map<String, Object> data) {
256
String customData = (String) data.get("customData");
257
Integer customValue = (Integer) data.get("customValue");
258
259
return new CustomSerializableObject(
260
customData.toLowerCase(),
261
customValue / 2
262
);
263
}
264
}
265
266
// Usage
267
Json json = new Json();
268
CustomSerializableObject original = new CustomSerializableObject("hello", 42);
269
270
String jsonString = json.toJson(original);
271
// Uses toJson() method: {"customData":"HELLO","customValue":84,"timestamp":1703508600000}
272
273
CustomSerializableObject restored = json.toType(jsonString, CustomSerializableObject.class);
274
// Uses fromJson() method to reconstruct object
275
```
276
277
### Working with JsonInput in Custom Coercers
278
279
```java
280
// Advanced custom coercer that handles complex JSON structures
281
public class PersonCoercer extends TypeCoercer<Person> {
282
283
@Override
284
public boolean test(Class<?> aClass) {
285
return Person.class.equals(aClass);
286
}
287
288
@Override
289
public BiFunction<JsonInput, PropertySetting, Person> apply(Type type) {
290
return (jsonInput, propertySetting) -> {
291
Person person = new Person();
292
293
jsonInput.beginObject();
294
while (jsonInput.hasNext()) {
295
String propertyName = jsonInput.nextName();
296
297
switch (propertyName) {
298
case "name":
299
person.setName(jsonInput.nextString());
300
break;
301
case "age":
302
person.setAge(jsonInput.nextNumber().intValue());
303
break;
304
case "address":
305
// Recursively parse nested object
306
Address address = jsonInput.read(Address.class);
307
person.setAddress(address);
308
break;
309
case "hobbies":
310
// Parse array
311
List<String> hobbies = jsonInput.readArray(String.class);
312
person.setHobbies(hobbies);
313
break;
314
default:
315
jsonInput.skipValue(); // Skip unknown properties
316
}
317
}
318
jsonInput.endObject();
319
320
return person;
321
};
322
}
323
}
324
```
325
326
### Multiple Coercers and Priority
327
328
```java
329
// Register multiple coercers - order matters for overlapping types
330
try (JsonInput input = json.newInput(new StringReader(jsonData))) {
331
input.addCoercers(
332
new LocalDateCoercer(),
333
new LocalDateTimeCoercer(),
334
new FlexibleNumberCoercer(),
335
new CustomPersonCoercer()
336
);
337
338
// The first matching coercer will be used
339
MyComplexObject result = input.read(MyComplexObject.class);
340
}
341
```
342
343
### Generic Collections with Custom Types
344
345
```java
346
// Working with custom types in collections
347
public class Event {
348
private String name;
349
private LocalDate date;
350
// ... constructors, getters, setters
351
}
352
353
// Create TypeToken for List<Event>
354
Type eventListType = new TypeToken<List<Event>>() {}.getType();
355
356
String jsonEvents = """
357
[
358
{"name":"Conference","date":"2023-12-25"},
359
{"name":"Meeting","date":"2023-12-26"}
360
]
361
""";
362
363
try (JsonInput input = json.newInput(new StringReader(jsonEvents))) {
364
input.addCoercers(new LocalDateCoercer(), new EventCoercer());
365
366
List<Event> events = input.read(eventListType);
367
368
for (Event event : events) {
369
System.out.println(event.getName() + " on " + event.getDate());
370
}
371
}
372
```