How to deserialize an interface with json-b?

夙愿已清 提交于 2019-12-22 06:49:11

问题


I'm adapting this Jackson code:

@JsonDeserialize(as = EntityImpl.class)
public interface Entity { ... }

The original code works well, even for nested Entity objects.

How to do the same with the new json-b specification? I tried using @JsonbTypeDeserializer but

  1. Is that really the way to go? It seems to lack the simplicity of just specifying a class.
  2. It doesn't seem to work with nested entities, which is my biggest problem:

    javax.json.bind.JsonbException: Can't infer a type for unmarshalling into: Entity

  3. The annotation is not picked up on Entity. I have to add manually with JsonbConfig::withDeserializers.

Here is my deserializer code:

public class EntityDeserializer implements JsonbDeserializer<Entity> {

    @Override
    public Entity deserialize(JsonParser parser, DeserializationContextdeserializationContext, Type runtimeType) {
        Class<? extends Entity> entityClass = EntityImpl.class.asSubclass(Entity.class);
        return deserializationContext.deserialize(entityClass, parser);
    }
}

Any hint or help greatly appreciated :-)


回答1:


JSON-B doesn't declare a standard way of serializing polymorphic types. But you can manually achieve it using custom serializer and deserializer. I'll explain it on a simple sample.

Imagine that you have Shape interface and two classes Square and Circle implementing it.

public interface Shape {
    double surface();
    double perimeter();
}

public static class Square implements Shape {
    private double side;

    public Square() {
    }

    public Square(double side) {
        this.side = side;
    }

    public double getSide() {
        return side;
    }

    public void setSide(double side) {
        this.side = side;
    }

    @Override
    public String toString() {
        return String.format("Square[side=%s]", side);
    }

    @Override
    public double surface() {
        return side * side;
    }

    @Override
    public double perimeter() {
        return 4 * side;
    }
}

public static class Circle implements Shape {
    private double radius;

    public Circle() {
    }

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    public String toString() {
        return String.format("Circle[radius=%s]", radius);
    }

    @Override
    public double surface() {
        return Math.PI * radius * radius;
    }

    @Override
    public double perimeter() {
        return 2 * Math.PI * radius;
    }
}

You need to serialize and deserialize List which can contain any Shape implementations.

Serialization works out of the box:

JsonbConfig config = new JsonbConfig().withFormatting(true);
Jsonb jsonb = JsonbBuilder.create(config);

// Create a sample list
List<SerializerSample.Shape> shapes = Arrays.asList(
            new SerializerSample.Square(2),
            new SerializerSample.Circle(5));

// Serialize
String json = jsonb.toJson(shapes);
System.out.println(json);

The result will be:

[
    {
        "side": 2.0
    },
    {
        "radius": 5.0
    }
]

It's ok, but it won't work if you try to deserialize it. During deserialization JSON-B needs to create an instance of Square or Circle and there is no information about object type in the JSON document.

In order to fix it we need to manually add this information there. Here serializers and deserializers will help. We can create a serializer which puts a type of serialized object in JSON document and deserializer which reads it and creates a proper instance. It can be done like this:

public static class ShapeSerializer implements JsonbSerializer<SerializerSample.Shape> {
    @Override
    public void serialize(SerializerSample.Shape shape, JsonGenerator generator, SerializationContext ctx) {
        generator.writeStartObject();
        ctx.serialize(shape.getClass().getName(), shape, generator);
        generator.writeEnd();
    }
}

public static class ShapeDeserializer implements JsonbDeserializer<SerializerSample.Shape> {
    @Override
    public SerializerSample.Shape deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
        parser.next();

        String className = parser.getString();
        parser.next();

        try {
            return ctx.deserialize(Class.forName(className).asSubclass(Shape.class), parser);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new JsonbException("Cannot deserialize object.");
        }
    }
}

Now we need to plug it into JSON-B engine and try serialization. You should not forget to pass a generic type to JSON-B engine during serialization/deserialization. Otherwise it won't work properly.

// Create JSONB engine with pretty output and custom serializer/deserializer
JsonbConfig config = new JsonbConfig()
        .withFormatting(true)
        .withSerializers(new SerializerSample.ShapeSerializer())
        .withDeserializers(new SerializerSample.ShapeDeserializer());
Jsonb jsonb = JsonbBuilder.create(config);

// Create a sample list
List<SerializerSample.Shape> shapes = Arrays.asList(
        new SerializerSample.Square(2),
        new SerializerSample.Circle(5));

// Type of our list
Type type = new ArrayList<SerializerSample.Shape>() {}.getClass().getGenericSuperclass();

// Serialize
System.out.println("Serialization:");
String json = jsonb.toJson(shapes);
System.out.println(json);

The result of serialization will be:

[
    {
        "jsonb.sample.SerializerSample$Square": {
            "side": 2.0
        }
    },
    {
        "jsonb.sample.SerializerSample$Circle": {
            "radius": 5.0
        }
    }

]

You see that object type is added by ShapeSerializer. Now let's try to deserialize it and print results:

// Deserialize
List<SerializerSample.Shape> deserializedShapes = jsonb.fromJson(json, type);

// Print results
System.out.println("Deserialization:");
for (SerializerSample.Shape shape : deserializedShapes) {
    System.out.println(shape);
}

The result is:

Square[side=2.0]
Circle[radius=5.0]

So, it perfectly works. Hope it helps. :)



来源:https://stackoverflow.com/questions/46050845/how-to-deserialize-an-interface-with-json-b

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!