JSON processing library for Selenium WebDriver providing serialization and deserialization capabilities
—
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.
Generic type capture utility for handling parameterized types safely.
/**
* Captures generic type information for type-safe operations with parameterized types.
* Used to preserve generic type information that would otherwise be lost due to type erasure.
*/
public abstract class TypeToken<T> {
/**
* Get the captured Type information.
*
* @return Type object representing the parameterized type
*/
public Type getType();
}Base class for implementing custom type conversion logic.
/**
* Base class for custom type conversion between JSON and Java objects.
* Implements both Predicate<Class<?>> for type testing and Function for coercion logic.
*/
public abstract class TypeCoercer<T> implements Predicate<Class<?>>, Function<Type, BiFunction<JsonInput, PropertySetting, T>> {
/**
* Test if this coercer can handle the specified class.
*
* @param aClass the class to test
* @return true if this coercer can handle the class; false otherwise
*/
@Override
public abstract boolean test(Class<?> aClass);
/**
* Apply coercion logic for the specified type.
*
* @param type the type to coerce to
* @return BiFunction that performs the actual coercion
*/
@Override
public abstract BiFunction<JsonInput, PropertySetting, T> apply(Type type);
}Strategy enumeration for property assignment during deserialization.
/**
* Used to specify the strategy used to assign values during deserialization.
*/
public enum PropertySetting {
/** Values are stored via the corresponding Bean setter methods */
BY_NAME,
/** Values are stored in fields with the indicated names */
BY_FIELD
}The library includes comprehensive built-in support for standard Java types:
// Primitive wrapper types
Boolean.class, Byte.class, Double.class, Float.class, Integer.class, Long.class, Short.class
// Collection types
List.class, Set.class, Array types
// Standard Java types
Map.class, String.class, Enum.class, URI.class, URL.class, UUID.class,
Instant.class, Date.class, File.class, Level.class (java.util.logging)
// Special handling
Optional.class, CharSequence.classimport org.openqa.selenium.json.Json;
import org.openqa.selenium.json.TypeToken;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
Json json = new Json();
// Create TypeToken for List<String>
Type listOfStrings = new TypeToken<List<String>>() {}.getType();
List<String> names = json.toType("[\"John\",\"Jane\",\"Bob\"]", listOfStrings);
// Create TypeToken for Map<String, Integer>
Type mapOfStringToInt = new TypeToken<Map<String, Integer>>() {}.getType();
Map<String, Integer> scores = json.toType("{\"John\":95,\"Jane\":87}", mapOfStringToInt);
// Create TypeToken for complex nested types
Type complexType = new TypeToken<List<Map<String, Object>>>() {}.getType();
List<Map<String, Object>> data = json.toType(jsonString, complexType);
// Use predefined type constants
Map<String, Object> simpleMap = json.toType(jsonString, Json.MAP_TYPE);
List<Map<String, Object>> listOfMaps = json.toType(jsonString, Json.LIST_OF_MAPS_TYPE);// JavaBean-style object
public class Person {
private String name;
private int age;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
// Field-access style object
public class Product {
public String name; // Public fields
public double price;
}
Json json = new Json();
String personJson = "{\"name\":\"John\",\"age\":30}";
// Use setter methods (default)
Person person1 = json.toType(personJson, Person.class, PropertySetting.BY_NAME);
// Use direct field access
Person person2 = json.toType(personJson, Person.class, PropertySetting.BY_FIELD);
// For objects with public fields, BY_FIELD is more appropriate
String productJson = "{\"name\":\"Laptop\",\"price\":999.99}";
Product product = json.toType(productJson, Product.class, PropertySetting.BY_FIELD);import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
// Custom coercer for LocalDate
public class LocalDateCoercer extends TypeCoercer<LocalDate> {
@Override
public boolean test(Class<?> aClass) {
return LocalDate.class.isAssignableFrom(aClass);
}
@Override
public BiFunction<JsonInput, PropertySetting, LocalDate> apply(Type type) {
return (jsonInput, propertySetting) -> {
String dateString = jsonInput.nextString();
return LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE);
};
}
}
// Usage with JsonInput
String jsonWithDate = "{\"eventDate\":\"2023-12-25\"}";
try (JsonInput input = json.newInput(new StringReader(jsonWithDate))) {
input.addCoercers(new LocalDateCoercer());
input.beginObject();
input.nextName(); // "eventDate"
LocalDate date = input.read(LocalDate.class);
input.endObject();
System.out.println("Event date: " + date);
}// Custom coercer for handling different numeric formats
public class FlexibleNumberCoercer extends TypeCoercer<Number> {
@Override
public boolean test(Class<?> aClass) {
return Number.class.isAssignableFrom(aClass) &&
!aClass.equals(Integer.class) &&
!aClass.equals(Double.class); // Don't override built-ins
}
@Override
public BiFunction<JsonInput, PropertySetting, Number> apply(Type type) {
return (jsonInput, propertySetting) -> {
JsonType jsonType = jsonInput.peek();
if (jsonType == JsonType.STRING) {
// Handle numeric strings
String numStr = jsonInput.nextString().trim();
if (numStr.contains(".")) {
return Double.parseDouble(numStr);
} else {
return Long.parseLong(numStr);
}
} else if (jsonType == JsonType.NUMBER) {
// Handle regular numbers
return jsonInput.nextNumber();
} else {
throw new JsonException("Cannot coerce " + jsonType + " to Number");
}
};
}
}// Object with custom serialization method
public class CustomSerializableObject {
private String data;
private int value;
public CustomSerializableObject(String data, int value) {
this.data = data;
this.value = value;
}
// Custom serialization - library will use this method
public Map<String, Object> toJson() {
Map<String, Object> result = new HashMap<>();
result.put("customData", data.toUpperCase());
result.put("customValue", value * 2);
result.put("timestamp", System.currentTimeMillis());
return result;
}
// Custom deserialization - library will use this method
public static CustomSerializableObject fromJson(Map<String, Object> data) {
String customData = (String) data.get("customData");
Integer customValue = (Integer) data.get("customValue");
return new CustomSerializableObject(
customData.toLowerCase(),
customValue / 2
);
}
}
// Usage
Json json = new Json();
CustomSerializableObject original = new CustomSerializableObject("hello", 42);
String jsonString = json.toJson(original);
// Uses toJson() method: {"customData":"HELLO","customValue":84,"timestamp":1703508600000}
CustomSerializableObject restored = json.toType(jsonString, CustomSerializableObject.class);
// Uses fromJson() method to reconstruct object// Advanced custom coercer that handles complex JSON structures
public class PersonCoercer extends TypeCoercer<Person> {
@Override
public boolean test(Class<?> aClass) {
return Person.class.equals(aClass);
}
@Override
public BiFunction<JsonInput, PropertySetting, Person> apply(Type type) {
return (jsonInput, propertySetting) -> {
Person person = new Person();
jsonInput.beginObject();
while (jsonInput.hasNext()) {
String propertyName = jsonInput.nextName();
switch (propertyName) {
case "name":
person.setName(jsonInput.nextString());
break;
case "age":
person.setAge(jsonInput.nextNumber().intValue());
break;
case "address":
// Recursively parse nested object
Address address = jsonInput.read(Address.class);
person.setAddress(address);
break;
case "hobbies":
// Parse array
List<String> hobbies = jsonInput.readArray(String.class);
person.setHobbies(hobbies);
break;
default:
jsonInput.skipValue(); // Skip unknown properties
}
}
jsonInput.endObject();
return person;
};
}
}// Register multiple coercers - order matters for overlapping types
try (JsonInput input = json.newInput(new StringReader(jsonData))) {
input.addCoercers(
new LocalDateCoercer(),
new LocalDateTimeCoercer(),
new FlexibleNumberCoercer(),
new CustomPersonCoercer()
);
// The first matching coercer will be used
MyComplexObject result = input.read(MyComplexObject.class);
}// Working with custom types in collections
public class Event {
private String name;
private LocalDate date;
// ... constructors, getters, setters
}
// Create TypeToken for List<Event>
Type eventListType = new TypeToken<List<Event>>() {}.getType();
String jsonEvents = """
[
{"name":"Conference","date":"2023-12-25"},
{"name":"Meeting","date":"2023-12-26"}
]
""";
try (JsonInput input = json.newInput(new StringReader(jsonEvents))) {
input.addCoercers(new LocalDateCoercer(), new EventCoercer());
List<Event> events = input.read(eventListType);
for (Event event : events) {
System.out.println(event.getName() + " on " + event.getDate());
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-seleniumhq-selenium--selenium-json