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 classesSquare
andCircle
implementing it.You need to serialize and deserialize List which can contain any
Shape
implementations.Serialization works out of the box:
The result will be:
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
orCircle
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:
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.
The result of serialization will be:
]
You see that object type is added by
ShapeSerializer
. Now let's try to deserialize it and print results:The result is:
So, it perfectly works. Hope it helps. :)
The answer @Dmitry gave helped me a lot, but it has two flaws:
1: using the full class name from the JSON is a serious security issue. An attacker could make you deserialize an arbitrary class, and some classes can cause remote code execution. You must use a mapping (or whitelist the allowed subclasses). E.g.:
2: wrapping the actual object within the type may not be what we want our JSON to look like. Or when we receive the JSON from a different system, we commonly get a different structure, e.g. with a
@type
field. And the field order is not defined in JSON; a producer might sometimes send the@type
last. E.g.The solution I found is this:
Note that reading from the
Parser
advances the cursor; but we need to re-read the complete object – remember: the@type
may not be the first field. As there is no API to reset the cursor, I produce a new JSON string by callingtoString
and use that to start a new parser. This is not perfect, but the performance impact should be generally acceptable. YMMV.And I'm eager to see polymorphic type supported directly by JSON-B as discussed here.