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
- Is that really the way to go? It seems to lack the simplicity of just specifying a class.
- 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
- 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 :-)
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