Protobuf serialization for pre-existing objects with runtime schema generation and polymorphic type handling
—
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.
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;
}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
}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;
}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;
}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;
}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;
}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;
}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;
}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
}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 listimport 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
}
}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 listCommon issues with annotation usage:
IllegalArgumentException - Invalid field numbers (out of range or duplicate)RuntimeException - Conflicting field configurationsAnnotations work in conjunction with IdStrategy configuration:
Install with Tessl CLI
npx tessl i tessl/maven-io-protostuff--protostuff-runtime