CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-seleniumhq-selenium--selenium-json

JSON processing library for Selenium WebDriver providing serialization and deserialization capabilities

Pending
Overview
Eval results
Files

type-system.mddocs/

Type System and Coercion

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.

Capabilities

TypeToken Class

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();
}

TypeCoercer Abstract Class

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);
}

PropertySetting Enum

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
}

Built-in Type Support

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.class

Usage Examples

Basic TypeToken Usage

import 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);

Property Setting Strategies

// 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);

Custom TypeCoercer Implementation

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);
}

Complex Custom Coercer with Multiple Types

// 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 Serialization Patterns

// 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

Working with JsonInput in Custom Coercers

// 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;
        };
    }
}

Multiple Coercers and Priority

// 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);
}

Generic Collections with Custom Types

// 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

docs

core-processing.md

index.md

streaming-input.md

streaming-output.md

type-system.md

tile.json