Serial
Light-weight, fast framework for object serialization in Java, with Android support.
Install / Use
/learn @twitter/SerialREADME
|badge1| |badge2| |badge3|
.. |badge1| image:: https://travis-ci.org/twitter/Serial.svg?branch=master :target: https://travis-ci.org/twitter/Serial
.. |badge2| image:: https://img.shields.io/maven-central/v/com.twitter.serial/serial.svg :target: https://repo1.maven.org/maven2/com/twitter/serial/serial/
.. |badge3| image:: https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat :target: https://raw.githubusercontent.com/twitter/Serial/master/LICENSE.txt
Twitter Serial
中文文档 <https://github.com/twitter/Serial/blob/master/README-CHINESE.rst/>_
Download
Grab the latest version via Gradle from Maven Central:
.. code-block:: java
repositories { mavenCentral() }
dependencies { implementation 'com.twitter.serial:serial:0.1.6' }
Overview
Twitter Serial is a custom serialization implementation that's intended to improve performance and increase developer visibility into and control over an object's serialization.
This framework uses Serializers to explicitly define how a class should be serialized. Some of the major advantages of this approach include:
-
more efficient serialization avoiding reflection - preliminary metrics for a large object showed
- more than 3x faster for roundtrip serialization (5x faster to serialize, 2.5x to deserialize)
- around 5x smaller in byte array size
-
greater control over what's serialized for an object - all serialization is defined explicitly
-
better debugging capabilities (see
debugging_)
Basic Structure
To serialize an object to a byte array, use:
.. code-block:: java
final Serial serial = new ByteBufferSerial(); final byte[] serializedData = serial.toByteArray(object, ExampleObject.SERIALIZER)
To deserialize from a byte array back to an object, use:
.. code-block:: java
final ExampleObject object = serial.fromByteArray(ExampleObject.SERIALIZER)
Defining Serializers
- Instead of implementing Serializable, define a serializer for every object that needs to be serialized
- Serializers explicitly write and read each field of the object by using read/write for primitives or recursively calling serializers for other objects
- Serializers handle null objects for you, as does read/writeString; primitive read/write methods do not
- Serializers are stateless, so they are written as static inner classes of the object and accessed as a static
instance variable
SERIALIZER
For most classes, you can create a subclass of ObjectSerializer and implement serializeObject and
deserializeObject
.. code-block:: java
public static class ExampleObject { public static final ObjectSerializer<ExampleObject> SERIALIZER = new ExampleObjectSerializer();
public final int num;
public final SubObject obj;
public ExampleObject(int num, @NotNull SubObject obj) {
this.num = num;
this.obj = obj;
}
...
private static final class ExampleObjectSerializer extends ObjectSerializer<ExampleObject> {
@Override
protected void serializeObject(@NotNull SerializationContext context, @NotNull SerializerOutput output,
@NotNull ExampleObject object) throws IOException {
output
.writeInt(object.num) // first field
.writeObject(object.obj, SubObject.SERIALIZER); // second field
}
@Override
@NotNull
protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input,
int versionNumber) throws IOException, ClassNotFoundException {
final int num = input.readInt(); // first field
final SubObject obj = input.readObject(SubObject.SERIALIZER); // second field
return new ExampleObject(num, obj);
}
}
}
For classes that are constructed using builders, or have optional fields added (see updating-serializers_), you
can use a BuilderSerializer, in which you implement the methods createBuilder (which just returns a new builder
object for that class) and deserializeToBuilder (where you populate the builder with the deserialized fields)
.. code-block:: java
public static class ExampleObject { ...
public ExampleObject(@NotNull Builder builder) {
this.num = builder.mNum;
this.obj = builder.mObj;
}
...
public static class Builder extends ModelBuilder<ExampleObject> {
...
}
private static final class ExampleObjectSerializer extends BuilderSerializer<ExampleObject, Builder> {
@Override
@NotNull
protected Builder createBuilder() {
return new Builder();
}
@Override
protected void serializeObject(@NotNull SerializationContext context, @NotNull SerializerOutput output,
@NotNull ExampleObject object) throws IOException {
output.writeInt(object.num)
.writeObject(object.obj, SubObject.SERIALIZER);
}
@Override
protected void deserializeToBuilder(@NotNull SerializationContext context, @NotNull SerializerInput input,
@NotNull Builder builder, int versionNumber) throws IOException, ClassNotFoundException {
builder.setNum(input.readInt())
.setObj(input.readObject(SubObject.SERIALIZER));
}
}
}
Serialization Utility Methods
-
CoreSerializersandCollectionSerializerscontain serializers for boxed primitives and have helper methods to serialize objects like collections, enums and comparators.-
For example, to serialize a list of Strings, you can use:
.. code-block:: java
CollectionSerializers.getListSerializer(Serializers.STRING);
-
-
In order to serialize an object as its base class, you can construct a base class serializer from the subclass's serializers using the getBaseClassSerializer in
Serializers-
For example, if you have ClassA and ClassB that both extend ClassC, and you want to serialize the objects as ClassC objects, you can create a serializer in ClassC using the serializers of the subclasses:
.. code-block:: java
final Serializer<ClassC> SERIALIZER = Serializers.getBaseClassSerializer( SerializableClass.create(ClassA.class, new ClassA.ClassASerializer()), SerializableClass.create(ClassB.class, new ClassB.ClassBSerializer()));
.. note:: You must create new instances of ClassA and B serializers rather than using the static object defined in those classes. Since ClassC is initialized as part of its subclasses, using static objects of its subclasses in its initialization will create a cyclic dependency that will likely lead to a cryptic NPE.
-
.. _updating-serializers:
Updating Serializers
If you add or remove a field for an object that's being stored as serialized data, there are a few ways to handle it:
OptionalFieldException
If you add a field to the end of an object, your new serializer will reach the end of an old object when trying to
read the new field, which will cause it to throw an ``OptionalFieldException``.
``BuilderSerializer`` handles ``OptionalFieldExceptions`` for you by just ignoring that field in the builder,
stopping deserialization, and building the rest of the object as is. If you're using a regular Serializer instead,
you can explicitly catch the OptionalFieldException and set the remaining field(s) to default values as appropriate.
- Say, for example, you wanted to add a String 'name' to the end of the ExampleObject above
- For both serializer types, you could simply add ``.writeString(obj.name)`` to ``serializeObject``
- For the BuilderSerializer, to deserialize you would add ``.setName(input.readString())`` to the end of
``deserializeToBuilder``. In the case where an older object without the name field is being deserialized, an
``OptionalFieldException`` would be thrown and caught when reading the String, causing the object to be built
as is without the name field explicitly set.
- For the regular Serializer, you would change ``deserializeObject`` as follows:
.. code-block:: java
@Override
@NotNull
protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input,
int versionNumber) throws IOException, ClassNotFoundException {
final int num = input.readInt();
final SubObject obj = input.readObject(SubObject.SERIALIZER);
final String name;
try {
name = input.readString();
} catch (OptionalFieldException e) {
name = DEFAULT_NAME;
}
return new ExampleObject(num, obj, name);
}
Version numbers
~~~~~~~~~~~~~~~
Another option is to increase the version number of the serializer, and define the deserialization behavior for
older versions. To do this, pass the version number into the constructor of the ``SERIALIZER`` object, and then
in the deserialize method you can specify what to do differently for previous versions.
- To change the above example to use version numbers, do the following:
.. code-block:: java
final Serializer<ExampleObject> SERIALIZER = new ExampleObjectSerializer(1);
...
@Override
@NotNull
protected ExampleObject deserializeObject(@NotNull SerializationContext context, @NotNull SerializerInput input, int versionNumber)
throws IOException, ClassNotFoundException {
final int num = input.readInt();
final SubObject obj = input.readObject(SubObject.SERIALIZER);
final String name;
if (versionNumber < 1) {
name = DEFAULT_NAME;
} else {
name = input.readSt
Related Skills
openhue
336.5kControl Philips Hue lights and scenes via the OpenHue CLI.
sag
336.5kElevenLabs text-to-speech with mac-style say UX.
weather
336.5kGet current weather and forecasts via wttr.in or Open-Meteo
cc-switch
33.7kA cross-platform desktop All-in-One assistant tool for Claude Code, Codex, OpenCode, openclaw & Gemini CLI.
