0
# Custom Serialization and Delegates
1
2
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.
3
4
## Capabilities
5
6
### Delegate Interface
7
8
Core interface for implementing custom type serialization with full control over the serialization process.
9
10
```java { .api }
11
/**
12
* Interface for custom type serialization delegates
13
* Delegates have higher priority than default serializers
14
*/
15
public interface Delegate<V> {
16
17
/**
18
* Get the protobuf field type for this delegate
19
* @return FieldType for wire format
20
*/
21
FieldType getFieldType();
22
23
/**
24
* Read/deserialize value from input stream
25
* @param input Input stream containing serialized data
26
* @return Deserialized value instance
27
* @throws IOException if reading fails
28
*/
29
V readFrom(Input input) throws IOException;
30
31
/**
32
* Write/serialize value to output stream
33
* @param output Output stream to write to
34
* @param number Field number for this value
35
* @param value Value to serialize
36
* @param repeated Whether this is a repeated field
37
* @throws IOException if writing fails
38
*/
39
void writeTo(Output output, int number, V value, boolean repeated) throws IOException;
40
41
/**
42
* Transfer value through a pipe (streaming serialization)
43
* @param pipe Pipe for streaming operations
44
* @param input Input stream
45
* @param output Output stream
46
* @param number Field number
47
* @param repeated Whether this is a repeated field
48
* @throws IOException if transfer fails
49
*/
50
void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated) throws IOException;
51
52
/**
53
* Get the Java class this delegate handles
54
* @return Class type handled by this delegate
55
*/
56
Class<?> typeClass();
57
}
58
```
59
60
### HasDelegate Wrapper
61
62
Wrapper class for delegates in polymorphic contexts, providing access to the underlying delegate.
63
64
```java { .api }
65
/**
66
* Wrapper for delegates in polymorphic serialization contexts
67
*/
68
public class HasDelegate<T> {
69
70
/**
71
* Get the wrapped delegate instance
72
* @return Delegate instance
73
*/
74
public Delegate<T> getDelegate();
75
}
76
```
77
78
## Delegate Implementation Examples
79
80
### String Delegate Example
81
82
```java
83
import io.protostuff.runtime.Delegate;
84
import io.protostuff.WireFormat.FieldType;
85
import io.protostuff.Input;
86
import io.protostuff.Output;
87
import io.protostuff.Pipe;
88
import java.io.IOException;
89
90
/**
91
* Custom delegate for String serialization with compression
92
*/
93
public class CompressedStringDelegate implements Delegate<String> {
94
95
@Override
96
public FieldType getFieldType() {
97
return FieldType.BYTES; // Serialize as bytes for compression
98
}
99
100
@Override
101
public String readFrom(Input input) throws IOException {
102
byte[] compressed = input.readByteArray();
103
return decompress(compressed);
104
}
105
106
@Override
107
public void writeTo(Output output, int number, String value, boolean repeated) throws IOException {
108
if (value != null) {
109
byte[] compressed = compress(value);
110
output.writeByteArray(number, compressed, repeated);
111
}
112
}
113
114
@Override
115
public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated)
116
throws IOException {
117
output.writeByteArray(number, input.readByteArray(), repeated);
118
}
119
120
@Override
121
public Class<?> typeClass() {
122
return String.class;
123
}
124
125
private byte[] compress(String value) {
126
// Implement compression logic
127
return value.getBytes(); // Simplified
128
}
129
130
private String decompress(byte[] compressed) {
131
// Implement decompression logic
132
return new String(compressed); // Simplified
133
}
134
}
135
```
136
137
### Date Delegate Example
138
139
```java
140
import java.util.Date;
141
142
/**
143
* Custom delegate for Date serialization as long timestamp
144
*/
145
public class DateDelegate implements Delegate<Date> {
146
147
@Override
148
public FieldType getFieldType() {
149
return FieldType.INT64;
150
}
151
152
@Override
153
public Date readFrom(Input input) throws IOException {
154
long timestamp = input.readInt64();
155
return new Date(timestamp);
156
}
157
158
@Override
159
public void writeTo(Output output, int number, Date value, boolean repeated) throws IOException {
160
if (value != null) {
161
output.writeInt64(number, value.getTime(), repeated);
162
}
163
}
164
165
@Override
166
public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated)
167
throws IOException {
168
output.writeInt64(number, input.readInt64(), repeated);
169
}
170
171
@Override
172
public Class<?> typeClass() {
173
return Date.class;
174
}
175
}
176
```
177
178
### Enum Delegate Example
179
180
```java
181
/**
182
* Custom delegate for enum serialization by name with fallback
183
*/
184
public class SafeEnumDelegate<E extends Enum<E>> implements Delegate<E> {
185
186
private final Class<E> enumClass;
187
private final E defaultValue;
188
189
public SafeEnumDelegate(Class<E> enumClass, E defaultValue) {
190
this.enumClass = enumClass;
191
this.defaultValue = defaultValue;
192
}
193
194
@Override
195
public FieldType getFieldType() {
196
return FieldType.STRING;
197
}
198
199
@Override
200
public E readFrom(Input input) throws IOException {
201
String name = input.readString();
202
try {
203
return Enum.valueOf(enumClass, name);
204
} catch (IllegalArgumentException e) {
205
return defaultValue; // Fallback for unknown values
206
}
207
}
208
209
@Override
210
public void writeTo(Output output, int number, E value, boolean repeated) throws IOException {
211
if (value != null) {
212
output.writeString(number, value.name(), repeated);
213
}
214
}
215
216
@Override
217
public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated)
218
throws IOException {
219
output.writeString(number, input.readString(), repeated);
220
}
221
222
@Override
223
public Class<?> typeClass() {
224
return enumClass;
225
}
226
}
227
```
228
229
### Collection Delegate Example
230
231
```java
232
import java.util.*;
233
234
/**
235
* Custom delegate for Set serialization with duplicate detection
236
*/
237
public class SetDelegate<T> implements Delegate<Set<T>> {
238
239
private final Schema<T> elementSchema;
240
241
public SetDelegate(Schema<T> elementSchema) {
242
this.elementSchema = elementSchema;
243
}
244
245
@Override
246
public FieldType getFieldType() {
247
return FieldType.MESSAGE;
248
}
249
250
@Override
251
public Set<T> readFrom(Input input) throws IOException {
252
Set<T> set = new LinkedHashSet<>();
253
254
// Read count
255
int count = input.readInt32();
256
257
// Read elements
258
for (int i = 0; i < count; i++) {
259
T element = elementSchema.newMessage();
260
input.mergeObject(element, elementSchema);
261
set.add(element);
262
}
263
264
return set;
265
}
266
267
@Override
268
public void writeTo(Output output, int number, Set<T> value, boolean repeated) throws IOException {
269
if (value != null && !value.isEmpty()) {
270
output.writeObject(number, value, this, repeated);
271
}
272
}
273
274
@Override
275
public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated)
276
throws IOException {
277
// Implementation for pipe transfer
278
output.writeObject(number, pipe, this, repeated);
279
}
280
281
@Override
282
public Class<?> typeClass() {
283
return Set.class;
284
}
285
}
286
```
287
288
## Registration and Usage
289
290
### Registering Delegates
291
292
```java
293
import io.protostuff.runtime.DefaultIdStrategy;
294
295
// Create ID strategy
296
DefaultIdStrategy strategy = new DefaultIdStrategy();
297
298
// Register various delegates
299
strategy.registerDelegate(new DateDelegate());
300
strategy.registerDelegate(new CompressedStringDelegate());
301
strategy.registerDelegate(new SafeEnumDelegate<>(Status.class, Status.UNKNOWN));
302
303
// Register by class name (useful for optional dependencies)
304
strategy.registerDelegate("java.time.LocalDateTime", new LocalDateTimeDelegate());
305
strategy.registerDelegate("java.math.BigDecimal", new BigDecimalDelegate());
306
307
// Use strategy with schema
308
Schema<MyClass> schema = RuntimeSchema.getSchema(MyClass.class, strategy);
309
```
310
311
### Conditional Delegates
312
313
```java
314
/**
315
* Delegate that chooses serialization strategy based on value characteristics
316
*/
317
public class OptimizedStringDelegate implements Delegate<String> {
318
319
private static final int COMPRESSION_THRESHOLD = 100;
320
321
@Override
322
public FieldType getFieldType() {
323
return FieldType.BYTES;
324
}
325
326
@Override
327
public String readFrom(Input input) throws IOException {
328
byte[] data = input.readByteArray();
329
330
// First byte indicates compression
331
if (data[0] == 1) {
332
return decompress(Arrays.copyOfRange(data, 1, data.length));
333
} else {
334
return new String(Arrays.copyOfRange(data, 1, data.length));
335
}
336
}
337
338
@Override
339
public void writeTo(Output output, int number, String value, boolean repeated) throws IOException {
340
if (value != null) {
341
byte[] data;
342
343
// Compress long strings
344
if (value.length() > COMPRESSION_THRESHOLD) {
345
byte[] compressed = compress(value);
346
data = new byte[compressed.length + 1];
347
data[0] = 1; // Compression flag
348
System.arraycopy(compressed, 0, data, 1, compressed.length);
349
} else {
350
byte[] raw = value.getBytes();
351
data = new byte[raw.length + 1];
352
data[0] = 0; // No compression flag
353
System.arraycopy(raw, 0, data, 1, raw.length);
354
}
355
356
output.writeByteArray(number, data, repeated);
357
}
358
}
359
360
// ... other methods
361
}
362
```
363
364
## Advanced Delegate Patterns
365
366
### Versioned Delegate
367
368
```java
369
/**
370
* Delegate supporting multiple versions of serialization format
371
*/
372
public class VersionedUserDelegate implements Delegate<User> {
373
374
private static final int CURRENT_VERSION = 2;
375
376
@Override
377
public FieldType getFieldType() {
378
return FieldType.MESSAGE;
379
}
380
381
@Override
382
public User readFrom(Input input) throws IOException {
383
int version = input.readInt32();
384
385
switch (version) {
386
case 1:
387
return readV1(input);
388
case 2:
389
return readV2(input);
390
default:
391
throw new IOException("Unsupported version: " + version);
392
}
393
}
394
395
@Override
396
public void writeTo(Output output, int number, User value, boolean repeated) throws IOException {
397
if (value != null) {
398
// Always write current version
399
output.writeInt32(1, CURRENT_VERSION, false);
400
writeV2(output, value);
401
}
402
}
403
404
private User readV1(Input input) throws IOException {
405
// Legacy format handling
406
User user = new User();
407
user.setName(input.readString());
408
user.setAge(input.readInt32());
409
return user;
410
}
411
412
private User readV2(Input input) throws IOException {
413
// Current format handling
414
User user = new User();
415
user.setName(input.readString());
416
user.setAge(input.readInt32());
417
user.setEmail(input.readString());
418
return user;
419
}
420
421
private void writeV2(Output output, User user) throws IOException {
422
output.writeString(2, user.getName(), false);
423
output.writeInt32(3, user.getAge(), false);
424
output.writeString(4, user.getEmail(), false);
425
}
426
427
// ... other methods
428
}
429
```
430
431
### Nullable Delegate Wrapper
432
433
```java
434
/**
435
* Wrapper delegate that handles null values explicitly
436
*/
437
public class NullableDelegate<T> implements Delegate<T> {
438
439
private final Delegate<T> innerDelegate;
440
441
public NullableDelegate(Delegate<T> innerDelegate) {
442
this.innerDelegate = innerDelegate;
443
}
444
445
@Override
446
public FieldType getFieldType() {
447
return FieldType.MESSAGE;
448
}
449
450
@Override
451
public T readFrom(Input input) throws IOException {
452
boolean isNull = input.readBool();
453
if (isNull) {
454
return null;
455
}
456
return innerDelegate.readFrom(input);
457
}
458
459
@Override
460
public void writeTo(Output output, int number, T value, boolean repeated) throws IOException {
461
output.writeBool(1, value == null, false);
462
if (value != null) {
463
innerDelegate.writeTo(output, 2, value, false);
464
}
465
}
466
467
@Override
468
public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated)
469
throws IOException {
470
boolean isNull = input.readBool();
471
output.writeBool(1, isNull, false);
472
if (!isNull) {
473
innerDelegate.transfer(pipe, input, output, 2, false);
474
}
475
}
476
477
@Override
478
public Class<?> typeClass() {
479
return innerDelegate.typeClass();
480
}
481
}
482
```
483
484
## Best Practices
485
486
1. **Field Type Selection**: Choose appropriate `FieldType` for efficient wire format
487
2. **Null Handling**: Always check for null values in `writeTo` method
488
3. **Error Handling**: Provide meaningful error messages in delegates
489
4. **Performance**: Consider performance implications of custom serialization logic
490
5. **Versioning**: Plan for backward compatibility in delegate implementations
491
6. **Transfer Method**: Implement `transfer` method for efficient pipe operations
492
7. **Registration Order**: Register delegates before creating schemas that use them
493
8. **Testing**: Thoroughly test delegates with various input scenarios including edge cases