CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-protostuff--protostuff-runtime

Protobuf serialization for pre-existing objects with runtime schema generation and polymorphic type handling

Pending
Overview
Eval results
Files

custom-serialization.mddocs/

Custom Serialization and Delegates

System for implementing custom serialization logic for specific types, providing higher priority than default serializers. The delegate system allows you to override the default serialization behavior for any type, enabling special handling for complex objects, third-party types, or optimization scenarios.

Capabilities

Delegate Interface

Core interface for implementing custom type serialization with full control over the serialization process.

/**
 * Interface for custom type serialization delegates
 * Delegates have higher priority than default serializers
 */
public interface Delegate<V> {
    
    /**
     * Get the protobuf field type for this delegate
     * @return FieldType for wire format
     */
    FieldType getFieldType();
    
    /**
     * Read/deserialize value from input stream
     * @param input Input stream containing serialized data
     * @return Deserialized value instance
     * @throws IOException if reading fails
     */
    V readFrom(Input input) throws IOException;
    
    /**
     * Write/serialize value to output stream
     * @param output Output stream to write to
     * @param number Field number for this value
     * @param value Value to serialize
     * @param repeated Whether this is a repeated field
     * @throws IOException if writing fails
     */
    void writeTo(Output output, int number, V value, boolean repeated) throws IOException;
    
    /**
     * Transfer value through a pipe (streaming serialization)
     * @param pipe Pipe for streaming operations
     * @param input Input stream
     * @param output Output stream  
     * @param number Field number
     * @param repeated Whether this is a repeated field
     * @throws IOException if transfer fails
     */
    void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated) throws IOException;
    
    /**
     * Get the Java class this delegate handles
     * @return Class type handled by this delegate
     */
    Class<?> typeClass();
}

HasDelegate Wrapper

Wrapper class for delegates in polymorphic contexts, providing access to the underlying delegate.

/**
 * Wrapper for delegates in polymorphic serialization contexts
 */
public class HasDelegate<T> {
    
    /**
     * Get the wrapped delegate instance
     * @return Delegate instance
     */
    public Delegate<T> getDelegate();
}

Delegate Implementation Examples

String Delegate Example

import io.protostuff.runtime.Delegate;
import io.protostuff.WireFormat.FieldType;
import io.protostuff.Input;
import io.protostuff.Output;
import io.protostuff.Pipe;
import java.io.IOException;

/**
 * Custom delegate for String serialization with compression
 */
public class CompressedStringDelegate implements Delegate<String> {
    
    @Override
    public FieldType getFieldType() {
        return FieldType.BYTES; // Serialize as bytes for compression
    }
    
    @Override
    public String readFrom(Input input) throws IOException {
        byte[] compressed = input.readByteArray();
        return decompress(compressed);
    }
    
    @Override
    public void writeTo(Output output, int number, String value, boolean repeated) throws IOException {
        if (value != null) {
            byte[] compressed = compress(value);
            output.writeByteArray(number, compressed, repeated);
        }
    }
    
    @Override
    public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated) 
            throws IOException {
        output.writeByteArray(number, input.readByteArray(), repeated);
    }
    
    @Override
    public Class<?> typeClass() {
        return String.class;
    }
    
    private byte[] compress(String value) {
        // Implement compression logic
        return value.getBytes(); // Simplified
    }
    
    private String decompress(byte[] compressed) {
        // Implement decompression logic  
        return new String(compressed); // Simplified
    }
}

Date Delegate Example

import java.util.Date;

/**
 * Custom delegate for Date serialization as long timestamp
 */
public class DateDelegate implements Delegate<Date> {
    
    @Override
    public FieldType getFieldType() {
        return FieldType.INT64;
    }
    
    @Override
    public Date readFrom(Input input) throws IOException {
        long timestamp = input.readInt64();
        return new Date(timestamp);
    }
    
    @Override
    public void writeTo(Output output, int number, Date value, boolean repeated) throws IOException {
        if (value != null) {
            output.writeInt64(number, value.getTime(), repeated);
        }
    }
    
    @Override
    public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated) 
            throws IOException {
        output.writeInt64(number, input.readInt64(), repeated);
    }
    
    @Override
    public Class<?> typeClass() {
        return Date.class;
    }
}

Enum Delegate Example

/**
 * Custom delegate for enum serialization by name with fallback
 */
public class SafeEnumDelegate<E extends Enum<E>> implements Delegate<E> {
    
    private final Class<E> enumClass;
    private final E defaultValue;
    
    public SafeEnumDelegate(Class<E> enumClass, E defaultValue) {
        this.enumClass = enumClass;
        this.defaultValue = defaultValue;
    }
    
    @Override
    public FieldType getFieldType() {
        return FieldType.STRING;
    }
    
    @Override
    public E readFrom(Input input) throws IOException {
        String name = input.readString();
        try {
            return Enum.valueOf(enumClass, name);
        } catch (IllegalArgumentException e) {
            return defaultValue; // Fallback for unknown values
        }
    }
    
    @Override
    public void writeTo(Output output, int number, E value, boolean repeated) throws IOException {
        if (value != null) {
            output.writeString(number, value.name(), repeated);
        }
    }
    
    @Override
    public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated) 
            throws IOException {
        output.writeString(number, input.readString(), repeated);
    }
    
    @Override
    public Class<?> typeClass() {
        return enumClass;
    }
}

Collection Delegate Example

import java.util.*;

/**
 * Custom delegate for Set serialization with duplicate detection
 */
public class SetDelegate<T> implements Delegate<Set<T>> {
    
    private final Schema<T> elementSchema;
    
    public SetDelegate(Schema<T> elementSchema) {
        this.elementSchema = elementSchema;
    }
    
    @Override
    public FieldType getFieldType() {
        return FieldType.MESSAGE;
    }
    
    @Override
    public Set<T> readFrom(Input input) throws IOException {
        Set<T> set = new LinkedHashSet<>();
        
        // Read count
        int count = input.readInt32();
        
        // Read elements
        for (int i = 0; i < count; i++) {
            T element = elementSchema.newMessage();
            input.mergeObject(element, elementSchema);
            set.add(element);
        }
        
        return set;
    }
    
    @Override
    public void writeTo(Output output, int number, Set<T> value, boolean repeated) throws IOException {
        if (value != null && !value.isEmpty()) {
            output.writeObject(number, value, this, repeated);
        }
    }
    
    @Override
    public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated) 
            throws IOException {
        // Implementation for pipe transfer
        output.writeObject(number, pipe, this, repeated);
    }
    
    @Override
    public Class<?> typeClass() {
        return Set.class;
    }
}

Registration and Usage

Registering Delegates

import io.protostuff.runtime.DefaultIdStrategy;

// Create ID strategy
DefaultIdStrategy strategy = new DefaultIdStrategy();

// Register various delegates
strategy.registerDelegate(new DateDelegate());
strategy.registerDelegate(new CompressedStringDelegate());
strategy.registerDelegate(new SafeEnumDelegate<>(Status.class, Status.UNKNOWN));

// Register by class name (useful for optional dependencies)
strategy.registerDelegate("java.time.LocalDateTime", new LocalDateTimeDelegate());
strategy.registerDelegate("java.math.BigDecimal", new BigDecimalDelegate());

// Use strategy with schema
Schema<MyClass> schema = RuntimeSchema.getSchema(MyClass.class, strategy);

Conditional Delegates

/**
 * Delegate that chooses serialization strategy based on value characteristics
 */
public class OptimizedStringDelegate implements Delegate<String> {
    
    private static final int COMPRESSION_THRESHOLD = 100;
    
    @Override
    public FieldType getFieldType() {
        return FieldType.BYTES;
    }
    
    @Override
    public String readFrom(Input input) throws IOException {
        byte[] data = input.readByteArray();
        
        // First byte indicates compression
        if (data[0] == 1) {
            return decompress(Arrays.copyOfRange(data, 1, data.length));
        } else {
            return new String(Arrays.copyOfRange(data, 1, data.length));
        }
    }
    
    @Override
    public void writeTo(Output output, int number, String value, boolean repeated) throws IOException {
        if (value != null) {
            byte[] data;
            
            // Compress long strings
            if (value.length() > COMPRESSION_THRESHOLD) {
                byte[] compressed = compress(value);
                data = new byte[compressed.length + 1];
                data[0] = 1; // Compression flag
                System.arraycopy(compressed, 0, data, 1, compressed.length);
            } else {
                byte[] raw = value.getBytes();
                data = new byte[raw.length + 1];
                data[0] = 0; // No compression flag
                System.arraycopy(raw, 0, data, 1, raw.length);
            }
            
            output.writeByteArray(number, data, repeated);
        }
    }
    
    // ... other methods
}

Advanced Delegate Patterns

Versioned Delegate

/**
 * Delegate supporting multiple versions of serialization format
 */
public class VersionedUserDelegate implements Delegate<User> {
    
    private static final int CURRENT_VERSION = 2;
    
    @Override
    public FieldType getFieldType() {
        return FieldType.MESSAGE;
    }
    
    @Override
    public User readFrom(Input input) throws IOException {
        int version = input.readInt32();
        
        switch (version) {
            case 1:
                return readV1(input);
            case 2:
                return readV2(input);
            default:
                throw new IOException("Unsupported version: " + version);
        }
    }
    
    @Override
    public void writeTo(Output output, int number, User value, boolean repeated) throws IOException {
        if (value != null) {
            // Always write current version
            output.writeInt32(1, CURRENT_VERSION, false);
            writeV2(output, value);
        }
    }
    
    private User readV1(Input input) throws IOException {
        // Legacy format handling
        User user = new User();
        user.setName(input.readString());
        user.setAge(input.readInt32());
        return user;
    }
    
    private User readV2(Input input) throws IOException {
        // Current format handling
        User user = new User();
        user.setName(input.readString());
        user.setAge(input.readInt32());
        user.setEmail(input.readString());
        return user;
    }
    
    private void writeV2(Output output, User user) throws IOException {
        output.writeString(2, user.getName(), false);
        output.writeInt32(3, user.getAge(), false);
        output.writeString(4, user.getEmail(), false);
    }
    
    // ... other methods
}

Nullable Delegate Wrapper

/**
 * Wrapper delegate that handles null values explicitly
 */
public class NullableDelegate<T> implements Delegate<T> {
    
    private final Delegate<T> innerDelegate;
    
    public NullableDelegate(Delegate<T> innerDelegate) {
        this.innerDelegate = innerDelegate;
    }
    
    @Override
    public FieldType getFieldType() {
        return FieldType.MESSAGE;
    }
    
    @Override
    public T readFrom(Input input) throws IOException {
        boolean isNull = input.readBool();
        if (isNull) {
            return null;
        }
        return innerDelegate.readFrom(input);
    }
    
    @Override
    public void writeTo(Output output, int number, T value, boolean repeated) throws IOException {
        output.writeBool(1, value == null, false);
        if (value != null) {
            innerDelegate.writeTo(output, 2, value, false);
        }
    }
    
    @Override
    public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated) 
            throws IOException {
        boolean isNull = input.readBool();
        output.writeBool(1, isNull, false);
        if (!isNull) {
            innerDelegate.transfer(pipe, input, output, 2, false);
        }
    }
    
    @Override
    public Class<?> typeClass() {
        return innerDelegate.typeClass();
    }
}

Best Practices

  1. Field Type Selection: Choose appropriate FieldType for efficient wire format
  2. Null Handling: Always check for null values in writeTo method
  3. Error Handling: Provide meaningful error messages in delegates
  4. Performance: Consider performance implications of custom serialization logic
  5. Versioning: Plan for backward compatibility in delegate implementations
  6. Transfer Method: Implement transfer method for efficient pipe operations
  7. Registration Order: Register delegates before creating schemas that use them
  8. Testing: Thoroughly test delegates with various input scenarios including edge cases

Install with Tessl CLI

npx tessl i tessl/maven-io-protostuff--protostuff-runtime

docs

annotations.md

custom-serialization.md

field-access.md

index.md

polymorphic-schemas.md

runtime-environment.md

schema-generation.md

type-strategy.md

tile.json