or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-processing.mdindex.mdstreaming-input.mdstreaming-output.mdtype-system.md

type-system.mddocs/

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

```