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

annotations.mddocs/

Annotation-Based Configuration

Field-level configuration annotations for controlling serialization behavior, field numbers, and exclusions. These annotations provide a declarative way to customize how protostuff-runtime handles individual fields and classes during schema generation and serialization.

Capabilities

@Tag Annotation

Primary annotation for configuring field serialization behavior, field numbers, and field-level options.

/**
 * Annotation for configuring field serialization behavior
 * Controls field numbers, aliases, and group filtering
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tag {
    
    /**
     * The field number to use in the protobuf wire format
     * Must be unique within the message and in range [1, 2^29-1]
     * @return field number
     */
    int value();
    
    /**
     * Optional alias for the field name
     * If specified, this name will be used instead of the Java field name
     * @return field alias or empty string for default
     */
    String alias() default "";
    
    /**
     * Group filter for conditional field processing
     * Fields with the same group filter can be processed together
     * @return group filter value, 0 for no filtering
     */
    int groupFilter() default 0;
}

@Exclude Annotation

Annotation to mark fields that should be ignored during serialization and schema generation.

/**
 * Annotation to exclude fields from serialization
 * Fields marked with @Exclude will not be included in the schema
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Exclude {
    // Marker annotation - no parameters
}

@Morph Annotation

Annotation for controlling polymorphic behavior of fields and types.

/**
 * Annotation for controlling polymorphic morphing behavior
 * Affects how non-final POJOs and interfaces are handled
 */
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Morph {
    
    /**
     * Whether to enable morphing for this field or type
     * @return true to enable morphing, false to disable
     */
    boolean value() default true;
}

Usage Examples

Basic Field Tagging

import io.protostuff.Tag;
import io.protostuff.Exclude;

/**
 * User class with explicit field numbering
 */
public class User {
    
    @Tag(1)
    public String name;
    
    @Tag(2)  
    public int age;
    
    @Tag(3)
    public String email;
    
    @Tag(4)
    public boolean active;
    
    // This field will not be serialized
    @Exclude
    public String password;
    
    // This field will not be serialized either
    @Exclude
    public transient String sessionToken;
}

Field Aliases and Naming

import io.protostuff.Tag;

/**
 * Class demonstrating field aliases
 */
public class Product {
    
    @Tag(value = 1, alias = "product_id")
    public long id;
    
    @Tag(value = 2, alias = "product_name")
    public String name;
    
    @Tag(value = 3, alias = "unit_price")
    public double price;
    
    @Tag(value = 4, alias = "is_available")
    public boolean available;
    
    // Alias helps with naming conventions different from Java
    @Tag(value = 5, alias = "created_timestamp")
    public long createdAt;
}

Group Filtering

import io.protostuff.Tag;

/**
 * Class using group filters for conditional serialization
 */
public class UserProfile {
    
    // Basic information (group 1)
    @Tag(value = 1, groupFilter = 1)
    public String name;
    
    @Tag(value = 2, groupFilter = 1)
    public int age;
    
    // Contact information (group 2)
    @Tag(value = 3, groupFilter = 2)
    public String email;
    
    @Tag(value = 4, groupFilter = 2)
    public String phone;
    
    // Sensitive information (group 3)
    @Tag(value = 5, groupFilter = 3)
    public String ssn;
    
    @Tag(value = 6, groupFilter = 3)
    public String creditCardNumber;
    
    // Always included (no group filter)
    @Tag(7)
    public String publicId;
}

Complex Field Configuration

import io.protostuff.Tag;
import io.protostuff.Exclude;
import java.util.List;
import java.util.Map;

/**
 * Complex class with various annotation combinations
 */
public class Order {
    
    @Tag(1)
    public String orderId;
    
    @Tag(2)
    public long customerId;
    
    @Tag(value = 3, alias = "order_items")
    public List<OrderItem> items;
    
    @Tag(value = 4, alias = "order_total")
    public double totalAmount;
    
    @Tag(value = 5, groupFilter = 1)  // Audit information
    public long createdAt;
    
    @Tag(value = 6, groupFilter = 1)  // Audit information
    public String createdBy;
    
    @Tag(value = 7, alias = "shipping_address")
    public Address shippingAddress;
    
    @Tag(value = 8, alias = "billing_address")
    public Address billingAddress;
    
    // Additional metadata that might be filtered
    @Tag(value = 9, groupFilter = 2)
    public Map<String, String> metadata;
    
    // Internal processing fields - excluded
    @Exclude
    public boolean processed;
    
    @Exclude
    public String internalNotes;
    
    // Transient fields are also excluded by default
    public transient Object tempData;
}

Polymorphic Configuration with @Morph

import io.protostuff.Tag;
import io.protostuff.Morph;

/**
 * Base class with morphing enabled
 */
@Morph(true)  // Enable morphing at the class level
public abstract class Shape {
    
    @Tag(1)
    public String name;
    
    @Tag(2) 
    public String color;
}

/**
 * Derived classes automatically inherit morphing behavior
 */
public class Circle extends Shape {
    
    @Tag(3)
    public double radius;
}

public class Rectangle extends Shape {
    
    @Tag(3)
    public double width;
    
    @Tag(4)
    public double height;
}

/**
 * Class using polymorphic fields
 */
public class Drawing {
    
    @Tag(1)
    public String title;
    
    // This field will use polymorphic serialization
    @Tag(2)
    @Morph(true)  // Explicitly enable morphing for this field
    public List<Shape> shapes;
    
    // This field disables morphing (will serialize as base type only)
    @Tag(3)
    @Morph(false)
    public Shape backgroundShape;
}

Advanced Annotation Patterns

import io.protostuff.Tag;
import io.protostuff.Exclude;

/**
 * Advanced usage patterns with annotations
 */
public class AdvancedEntity {
    
    // Required fields with low numbers for efficiency
    @Tag(1)
    public String id;
    
    @Tag(2)
    public String name;
    
    // Optional fields with higher numbers
    @Tag(10)
    public String description;
    
    @Tag(11)
    public Map<String, Object> properties;
    
    // Versioning fields
    @Tag(20)
    public int version;
    
    @Tag(21)
    public long lastModified;
    
    // Audit fields with group filtering
    @Tag(value = 30, groupFilter = 100, alias = "created_timestamp")
    public long createdAt;
    
    @Tag(value = 31, groupFilter = 100, alias = "created_by_user")
    public String createdBy;
    
    @Tag(value = 32, groupFilter = 100, alias = "updated_timestamp")
    public long updatedAt;
    
    @Tag(value = 33, groupFilter = 100, alias = "updated_by_user")
    public String updatedBy;
    
    // Internal state - excluded from serialization
    @Exclude
    public boolean dirty;
    
    @Exclude
    public Object cache;
    
    // Computed fields - excluded
    @Exclude
    public String displayName;  // Computed from other fields
    
    @Exclude
    public boolean valid;       // Computed validation state
}

Schema Generation with Annotations

Basic Schema Creation

import io.protostuff.runtime.RuntimeSchema;
import io.protostuff.Schema;

// Schema generation respects annotations automatically
Schema<User> userSchema = RuntimeSchema.getSchema(User.class);

// Field numbers and aliases from @Tag annotations are used
System.out.println("Field 1 name: " + userSchema.getFieldName(1)); // "name"
System.out.println("Field number for 'email': " + userSchema.getFieldNumber("email")); // 3

// @Exclude fields are not included in the schema
List<Field<User>> fields = ((RuntimeSchema<User>) userSchema).getFields();
// password field will not be in the list

Group-Based Field Filtering

import java.util.Set;

// Create schema excluding sensitive fields (group 3)
Set<String> exclusions = Set.of("ssn", "creditCardNumber");
RuntimeSchema<UserProfile> filteredSchema = RuntimeSchema.createFrom(
    UserProfile.class, exclusions, RuntimeEnv.ID_STRATEGY
);

// Or use group filters programmatically when processing
RuntimeSchema<UserProfile> fullSchema = RuntimeSchema.createFrom(UserProfile.class);
for (Field<UserProfile> field : fullSchema.getFields()) {
    if (field.groupFilter == 3) {
        System.out.println("Sensitive field: " + field.name);
        // Handle sensitive fields specially
    }
}

Polymorphic Schema with Morphing

import io.protostuff.runtime.DefaultIdStrategy;
import io.protostuff.runtime.IdStrategy;

// Configure strategy for morphing
int flags = IdStrategy.MORPH_NON_FINAL_POJOS | IdStrategy.MORPH_COLLECTION_INTERFACES;
DefaultIdStrategy strategy = new DefaultIdStrategy(flags);

// Register polymorphic types
strategy.registerPojo(Shape.class);
strategy.registerPojo(Circle.class);
strategy.registerPojo(Rectangle.class);
strategy.map(Shape.class, Circle.class);
strategy.map(Shape.class, Rectangle.class);

// Create schema with morphing support
Schema<Drawing> drawingSchema = RuntimeSchema.getSchema(Drawing.class, strategy);

// The schema will handle polymorphic Shape objects in the shapes list

Annotation Processing Rules

Field Number Assignment

  1. Explicit @Tag: Use the specified field number
  2. No @Tag: Assign field numbers automatically in declaration order
  3. Mixed Usage: Explicit tags take precedence, automatic assignment fills gaps
  4. Validation: Field numbers must be unique and in valid range [1, 2^29-1]

Field Exclusion Priority

  1. @Exclude annotation: Highest priority - field is always excluded
  2. transient modifier: Excluded by default (can be overridden with @Tag)
  3. static modifier: Always excluded (cannot be overridden)
  4. Accessibility: Private fields may need reflection access

Alias Handling

  1. Wire Format: Aliases are used in the protobuf wire format
  2. Field Lookup: Both original name and alias can be used for field lookup
  3. Precedence: Alias takes precedence over original field name in wire format
  4. Uniqueness: Aliases must be unique within the message

Best Practices

  1. Explicit Field Numbers: Use @Tag with explicit numbers for stable schemas
  2. Reserved Ranges: Reserve number ranges for different types of fields
  3. Group Filtering: Use group filters for conditional serialization scenarios
  4. Meaningful Aliases: Use aliases that match your wire format naming conventions
  5. Exclude Sensitive Data: Always exclude passwords, tokens, and sensitive information
  6. Version Compatibility: Plan field number assignments for schema evolution
  7. Documentation: Document the purpose of group filters and special annotations

Error Handling

Common issues with annotation usage:

  • IllegalArgumentException - Invalid field numbers (out of range or duplicate)
  • RuntimeException - Conflicting field configurations
  • Field number conflicts when mixing @Tag and automatic assignment
  • Invalid group filter values in certain contexts
  • Alias conflicts with existing field names

Interaction with IdStrategy

Annotations work in conjunction with IdStrategy configuration:

  • @Morph + MORPH_NON_FINAL_POJOS: Enables polymorphic serialization
  • Group filters + custom strategy: Custom field filtering logic
  • @Exclude + field exclusions: Programmatic exclusions complement annotation exclusions
  • @Tag aliases + custom naming: Strategy can provide additional naming transformations

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