| title | Custom Serializers |
|---|---|
| sidebar_position | 4 |
| id | custom_serializers |
| license | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. |
This page covers the current Java custom serializer API.
Custom serializers should not retain Fory.
- Use
Configwhen the serializer only depends on immutable configuration and can be shared. - Use
TypeResolverwhen the serializer needs type metadata, generics, or nested dynamic dispatch. - If a serializer retains
TypeResolver, it is usually not shareable and should not implementShareable.
Use WriteContext and ReadContext for runtime state. Only get the buffer into a local variable
when you perform multiple reads or writes.
import org.apache.fory.config.Config;
import org.apache.fory.context.ReadContext;
import org.apache.fory.context.WriteContext;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.serializer.Serializer;
import org.apache.fory.serializer.Shareable;
public final class FooSerializer extends Serializer<Foo> implements Shareable {
public FooSerializer(Config config) {
super(config, Foo.class);
}
@Override
public void write(WriteContext writeContext, Foo value) {
writeContext.getBuffer().writeInt64(value.f1);
writeContext.writeString(value.f2);
}
@Override
public Foo read(ReadContext readContext) {
MemoryBuffer buffer = readContext.getBuffer();
Foo foo = new Foo();
foo.f1 = buffer.readInt64();
foo.f2 = readContext.readString(buffer);
return foo;
}
}Register it with a Config-based constructor when the serializer is shareable:
Fory fory = Fory.builder().build();
fory.registerSerializer(Foo.class, new FooSerializer(fory.getConfig()));If your serializer needs to write or read nested objects, use the context helpers instead of
retaining Fory:
import org.apache.fory.config.Config;
import org.apache.fory.context.ReadContext;
import org.apache.fory.context.WriteContext;
import org.apache.fory.serializer.Serializer;
public final class EnvelopeSerializer extends Serializer<Envelope> {
public EnvelopeSerializer(Config config) {
super(config, Envelope.class);
}
@Override
public void write(WriteContext writeContext, Envelope value) {
writeContext.writeRef(value.header);
writeContext.writeRef(value.payload);
}
@Override
public Envelope read(ReadContext readContext) {
Envelope envelope = new Envelope();
envelope.header = (Header) readContext.readRef();
envelope.payload = readContext.readRef();
return envelope;
}
}This serializer can implement Shareable because it retains no runtime-local mutable state.
For Java collections, extend CollectionSerializer or CollectionLikeSerializer.
- Use
CollectionSerializerfor realCollectionimplementations. - Use
CollectionLikeSerializerfor collection-shaped types that do not implementCollection. - Keep
supportCodegenHook == truewhen the collection can use the standard element codegen path. - Set
supportCodegenHook == falseonly when you need to fully control element IO.
Example:
import java.util.ArrayList;
import java.util.Collection;
import org.apache.fory.context.ReadContext;
import org.apache.fory.context.WriteContext;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.resolver.TypeResolver;
import org.apache.fory.serializer.collection.CollectionSerializer;
public final class CustomCollectionSerializer<T extends Collection<?>>
extends CollectionSerializer<T> {
public CustomCollectionSerializer(TypeResolver typeResolver, Class<T> type) {
super(typeResolver, type, true);
}
@Override
public Collection onCollectionWrite(WriteContext writeContext, T value) {
writeContext.getBuffer().writeVarUint32Small7(value.size());
return value;
}
@Override
public T onCollectionRead(Collection collection) {
return (T) collection;
}
@Override
public Collection newCollection(ReadContext readContext) {
MemoryBuffer buffer = readContext.getBuffer();
int numElements = buffer.readVarUint32Small7();
setNumElements(numElements);
return new ArrayList(numElements);
}
}For Java maps, extend MapSerializer or MapLikeSerializer.
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.context.ReadContext;
import org.apache.fory.context.WriteContext;
import org.apache.fory.resolver.TypeResolver;
import org.apache.fory.serializer.collection.MapSerializer;
public final class CustomMapSerializer<T extends Map<?, ?>> extends MapSerializer<T> {
public CustomMapSerializer(TypeResolver typeResolver, Class<T> type) {
super(typeResolver, type, true);
}
@Override
public Map onMapWrite(WriteContext writeContext, T value) {
writeContext.getBuffer().writeVarUint32Small7(value.size());
return value;
}
@Override
public T onMapRead(Map map) {
return (T) map;
}
@Override
public Map newMap(ReadContext readContext) {
MemoryBuffer buffer = readContext.getBuffer();
int numElements = buffer.readVarUint32Small7();
setNumElements(numElements);
return new LinkedHashMap(numElements);
}
}Fory fory = Fory.builder().build();
fory.registerSerializer(Foo.class, new FooSerializer(fory.getConfig()));
fory.registerSerializer(
CustomMap.class, new CustomMapSerializer<>(fory.getTypeResolver(), CustomMap.class));
fory.registerSerializer(
CustomCollection.class,
new CustomCollectionSerializer<>(fory.getTypeResolver(), CustomCollection.class));If you want Fory to construct the serializer lazily, register a factory:
fory.registerSerializer(
CustomMap.class, resolver -> new CustomMapSerializer<>(resolver, CustomMap.class));Implement the Shareable marker interface when the serializer can be safely reused across
equivalent runtimes and concurrent operations. A shareable serializer must not retain operation
state, runtime-local mutable state, or mutable scratch buffers shared across calls. Consumers can
check shareability via serializer instanceof Shareable.
In practice:
Config-only serializers are often shareable.TypeResolver-based serializers are usually not shareable.- Operation state belongs in
WriteContext,ReadContext, andCopyContext, not in serializer fields.