or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotations.mdcustom-serialization.mdfield-access.mdindex.mdpolymorphic-schemas.mdruntime-environment.mdschema-generation.mdtype-strategy.md

custom-serialization.mddocs/

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