How to serialize a Path?

1,107 views
Skip to first unread message

John Armstrong

unread,
Nov 16, 2017, 3:31:05 AM11/16/17
to kryo-users
I'm driving myself up a wall just trying to serialize a class that contains a simple java.nio.file.Path field in Kryo 4.0.1.  I've written a PathKryoSerializer:

public class PathKryoSerializer extends Serializer<Path> {
  public void write(Kryo kryo, Output output, Path path) {
    System.out.println("Serializing Path: " + path.toString());
    kryo.writeObject(output, path.toString());
  }

  public Path read(Kryo kryo, Input input, Class<Path> type) {
    return Paths.get(kryo.readObject(input, String.class));
  }
}

I've tried @Binding the field in my class:

public class MyClass {
  @Bind(PathKryoSerializer.class)
  private final Path path

  ...
}

I've registered the serializer with Kryo AND added it as a default serializer

Kryo kryo = new Kryo();
kryo.setRegistrationRequired(true);
kryo.register(Path.class, new PathKryoSerializer());
kryo.addDefaultSerializer(Path.class, new PathKryoSerializer());

I even check that the kryo instance is supposed to use my serializer!

System.out.println("MyClass serializer field 'path' serializer class: "
                  + ((FieldSerializer) kryo.getSerializer(MyClass.class))
                      .getField("path")
                      .getSerializer()
                      .getClass()
                      .getName());

Output:
MyClass serializer field 'path' serializer class: mypackage.serializer.PathKryoSerializer

And yet. Every single time I not only don't get an output from the println in my write method (indicating that it's not being called), I get this error:
com.esotericsoftware.kryo.KryoException: java.lang.IllegalArgumentException: Class is not registered: sun.nio.fs.UnixPath
Note: To register this class use: kryo.register(sun.nio.fs.UnixPath.class);
Serialization trace:
path (mypackage.MyClass)
  at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:101
  at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
  at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:557)

I'm at my wit's end trying to figure out how to do something as simple as serializing a standard Java 7 Path field.  What am I missing?

John Armstrong

unread,
Nov 16, 2017, 4:55:36 PM11/16/17
to kryo-users
In the interest of keeping an answer next to the question: there doesn't seem to be an annotation-driven way to actually make this work, which might speak to an improvement to be made to the annotations.

The basic idea is that all @Bind can do is specify the serializer for the field, but Kryo actually needs more.  As you walk through the ObjectField to serialize it, it first finds that valueClass == null, meaning it must serialize the class first.  But the actual runtime class shouldn't matter, since I'm telling it how to write and read a Path instance.  Worse, in this case the runtime implementation class isn't even available for registration (it's private, and purely internal to Sun's code).

The answer is that we have to create our own FieldSerializer<MyClass> instance when initializing Kryo, and tell it not only to .setSerializer(new PathKryoSerializer()) on the path field, but to .setClass(Path.class, new PathKryoSerializer()) on it.  That way, Kryo doesn't try to serialize the runtime class of the field, and all is well.

It might help if another, optional field were available on @Bind, which could specify the class of the field, and the FieldSerializerAnnotationsUtil could then call the .setClass itself.

Nate

unread,
Nov 24, 2017, 1:21:49 AM11/24/17
to kryo-users
It's an interesting situation. Sorry you had to suffer through that. Hopefully it wasn't too hard to trace.

FWIW, it's always best to post an SSCCE so others don't have to make one:

static public class PathKryoSerializer extends Serializer<Path> {
    public void write (Kryo kryo, Output output, Path path) {

        System.out.println("Serializing Path: " + path.toString());
        kryo.writeObject(output, path.toString());
    }

    public Path read (Kryo kryo, Input input, Class<? extends Path> type) {
        return Paths.get(kryo.readObject(input, String.class));
    }
}

static public class MyClass {
    @Bind(PathKryoSerializer.class) private Path path;
}

static public void main (String[] args) throws Exception {

    Kryo kryo = new Kryo();
    kryo.setRegistrationRequired(true);
    kryo.register(MyClass.class);

    MyClass myClass = new MyClass();
    myClass.path = Paths.get("/");

    Output output = new Output(4096);
    kryo.writeObject(output, myClass);
    output.close();
}

As you found, FieldSerializer goes to write the field, the concrete type is not known, so it calls Kryo#writeClass which eventually throws because UnixPath is unregistered. The right solution is to use setClass on the field:

static public class MyClass {
    private Path path;
}

static public void main (String[] args) throws Exception {

    Kryo kryo = new Kryo();
    kryo.setRegistrationRequired(true);
    FieldSerializer serializer = (FieldSerializer)kryo.register(MyClass.class).getSerializer();
    serializer.getField("path").setClass(Path.class, new PathKryoSerializer());

    MyClass myClass = new MyClass();
    myClass.path = Paths.get("/");

    Output output = new Output(4096);
    kryo.writeObject(output, myClass);
    output.close();
}

There is currently no way to do this with annotations. We could add a BindClass annotation, which would look like:

static public class MyClass {
    @Bind(PathKryoSerializer.class) @BindClass(Path.class) private Path path;
}

static public void main (String[] args) throws Exception {

    Kryo kryo = new Kryo();
    kryo.setRegistrationRequired(true);
    kryo.register(MyClass.class);

    MyClass myClass = new MyClass();
    myClass.path = Paths.get("/");

    Output output = new Output(4096);
    kryo.writeObject(output, myClass);
    output.close();
}

I've added this in the kryo-5.0.00 branch:

Cheers,
-Nate


--
You received this message because you are subscribed to the "kryo-users" group.
http://groups.google.com/group/kryo-users
---
You received this message because you are subscribed to the Google Groups "kryo-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kryo-users+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages