How to Call the Default Deserializer from a Custom Deserializer in Jackson

How do I call the default deserializer from a custom deserializer in Jackson

As StaxMan already suggested you can do this by writing a BeanDeserializerModifier and registering it via SimpleModule. The following example should work:

public class UserEventDeserializer extends StdDeserializer<User> implements ResolvableDeserializer
{
private static final long serialVersionUID = 7923585097068641765L;

private final JsonDeserializer<?> defaultDeserializer;

public UserEventDeserializer(JsonDeserializer<?> defaultDeserializer)
{
super(User.class);
this.defaultDeserializer = defaultDeserializer;
}

@Override public User deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
User deserializedUser = (User) defaultDeserializer.deserialize(jp, ctxt);

// Special logic

return deserializedUser;
}

// for some reason you have to implement ResolvableDeserializer when modifying BeanDeserializer
// otherwise deserializing throws JsonMappingException??
@Override public void resolve(DeserializationContext ctxt) throws JsonMappingException
{
((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
}

public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException
{
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier()
{
@Override public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer)
{
if (beanDesc.getBeanClass() == User.class)
return new UserEventDeserializer(deserializer);
return deserializer;
}
});

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
User user = mapper.readValue(new File("test.json"), User.class);
}
}

How do I call the default deserializer in a custom deserializer in Jackson?

You can use JsonParser object to read all properties. See below example:

class BJsonDeserializer extends JsonDeserializer<B> {

@Override
public B deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
B result = new B();
result.setFields(new HashMap<String, C>());

while (parser.nextToken() != JsonToken.END_OBJECT) {
String name = parser.getCurrentName();
if ("scale1".equals(name) || "scale2".equals(name) || "anotherProperty".equals(name)) {
parser.nextToken();
result.getFields().put(name, parser.readValueAs(C.class));
}
}
return result;
}
}
  • Jackson JSON Processing API in Java Example Tutorial.

Custom deserialization with Jackson: extend default deserializer

As Sharon said (based on How do I call the default deserializer from a custom deserializer in Jackson)

@Bean
public ObjectMapper serializingObjectMapper() {

ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();

simpleModule.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (Dto.class.isAssignableFrom(beanDesc.getBeanClass())) {
return new JsonDtoDeserializer<>(deserializer, beanDesc.getBeanClass());
}
return deserializer;
}
});

objectMapper.registerModule(simpleModule);
return objectMapper;
}

public class JsonDtoDeserializer<T extends Dto> extends StdDeserializer<T> implements ResolvableDeserializer /*StdDeserializer<Dto<T>>*/ /*UntypedObjectDeserializer.Vanilla*/ /*<T>*/ /*implements ResolvableDeserializer*/ {

private final JsonDeserializer<?> defaultDeserializer;

public JsonDtoDeserializer(JsonDeserializer<?> defaultDeserializer, Class<?> clazz) {
super(clazz);
this.defaultDeserializer = defaultDeserializer;
}

@Override
public T deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {

@SuppressWarnings("unchecked")
T itemObj = (T) defaultDeserializer.deserialize(p, ctxt);
return itemObj;
}

// for some reason you have to implement ResolvableDeserializer when modifying BeanDeserializer
// otherwise deserializing throws JsonMappingException??
@Override public void resolve(DeserializationContext ctxt) throws JsonMappingException
{
((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
}
}

Jackson Deserializer delegate to next applicable deserializer

Credit to schummar answer :How do I call the default deserializer from a custom deserializer in Jackson. Following the above answer,

1. @JsonComponent annotation should be removed from the custom serializer as we need to construct the custom serializer using the default serializer, and this is not supported by @JsonComponent.

2. Register a SimpleModule to the ObjectMapper with a BeanDeserializerModifier and modify the serializer with our custom serializer constructed with the default serializer.

3. In the serialize method of the custom serializer, handle the special case, and delegate the serialization to the default serializer for normal case.

The following code demonstrates how to implement above points.

Main class

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.module.SimpleModule;

public class DelegateDeserializer {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {

ObjectMapper mapper = new ObjectMapper();

SimpleModule simpleModule = new SimpleModule();

simpleModule.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc,
JsonDeserializer<?> deserializer) {
if (Outer.Foo.class.isAssignableFrom(beanDesc.getBeanClass())) {
return new FooDeserializer(deserializer, beanDesc.getBeanClass());
}
return deserializer;
}
});

mapper.registerModule(simpleModule);

Outer outer1 = mapper.readValue(getType1Json(), Outer.class);
Outer outer2 = mapper.readValue(getType2Json(), Outer.class);
System.out.println("deserialize json with object structure:");
System.out.println(outer1.getFoo().getName());
System.out.println(outer1.getFoo().getBar());
System.out.println("deserialize json with string field only:");
System.out.println(outer2.getFoo().getName());
System.out.println(outer2.getFoo().getBar());
}

private static String getType1Json() {

return " { "
+ " \"foo\": { "
+ " \"name\": \"John Smith\", "
+ " \"bar\": \"baz\" "
+ " } "
+ "} ";

}

private static String getType2Json() {

return " { "
+ " \"foo\": \"John Smith\" "
+ "} ";

}
}

FooDeserializer class

import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import jackson.Outer.Foo;

public class FooDeserializer extends StdDeserializer<Outer.Foo> implements ResolvableDeserializer {

private static final long serialVersionUID = 1L;
private final JsonDeserializer<?> defaultDeserializer;

public FooDeserializer(JsonDeserializer<?> defaultDeserializer, Class<?> clazz) {
super(clazz);
this.defaultDeserializer = defaultDeserializer;
}

@Override
public Outer.Foo deserialize(JsonParser parser, DeserializationContext context) throws IOException {
if (parser.getCurrentToken() == JsonToken.VALUE_STRING) {
JsonNode node = parser.getCodec().readTree(parser);
if (node.isTextual()) {
return new Foo(node.asText(), null);
}
}

return (Foo) defaultDeserializer.deserialize(parser, context);
}

@Override
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
}

}

Outer class

 public class Outer {
private Foo foo;

public Foo getFoo() {
return foo;
}

public void setFoo(Foo foo) {
this.foo = foo;
}

public static class Foo {
private String bar;
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getBar() {
return bar;
}

public void setBar(String bar) {
this.bar = bar;
}

public Foo() {
}

public Foo(String name, String bar) {
this.name = name;
this.bar = bar;
}
}

}

Calling the default deserializer in a custom deserializer don't effect instance

It is not quite an answer to the question, but my initial goal was:

'm trying to deserialize a Json into an existing instance in my process. So it only creates a new instance if none exists. Alls objects contains an id to Identifiy them.

For everyone who tries to accomplish the same, here is how I implemented it:

public class ModelInstantiator extends StdValueInstantiator {

private static final long serialVersionUID = -7760885448565898117L;

private Class<? extends ModelObject> clazz;

/**
* @param config
* @param valueType
*/
public ModelInstantiator(DeserializationConfig config, Class<? extends ModelObject> clazz) {
super(config, config.constructType(clazz));

this.clazz = clazz;
}

@Override
public boolean canCreateFromObjectWith() {
return true;
}

@Override
public Object createFromObjectWith(DeserializationContext ctxt, Object[] args) throws IOException, JsonProcessingException {
UUID id = (UUID) args[0];

// get local object
ModelObject object = ...

// if id was not found => create and add
if (object == null) {
try {
object = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new IOException(e);
}

object.setId(id);
// add to local list
...
}

return object;
}

@Override
public SettableBeanProperty[] getFromObjectArguments(DeserializationConfig config) {
CreatorProperty idProp = new CreatorProperty(new PropertyName("id"), config.constructType(UUID.class), null, null, null, null,
0, null, PropertyMetadata.STD_REQUIRED);

return new SettableBeanProperty[] { idProp };
}

}

I had to split the local and json id. Ohterwise the id in the array is null.

how to call default parser (registered in mapper) from custom deserializer

The problem is that you will need a fully constructed default deserializer; and this requires that one gets built, and then your deserializer gets access to it. DeserializationContext is not something you should either create or change; it will be provided by ObjectMapper

So all you need to write in the deserialize() method is:

ObjectMapper mapper = (ObjectMapper)jp.getCodec();
Property property = mapper.readValue(jn.get("propertyValue").toString(), Property.class));

Jackson Custom Deserializer for polymorphic objects and String literals as defaults

This was actually easier than I thought to solve it. I got it working using the following:

  1. Custom deserializer implementation:
public class VehicleDeserializer extends StdDeserializer<Vehicle> {

public VehicleDeserializer() {
super(Vehicle.class);
}

@Override
public Vehicle deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
if (jp.currentToken() == JsonToken.VALUE_STRING) {
Car car = new Car();
car.setName(jp.readValueAs(String.class));
return car;
}
return jp.readValueAs(Vehicle.class);
}
}

  1. To avoid circular dependencies and to make the custom deserializer work with the polymorphic @JsonTypeInfo and @JsonSubTypes annotations I kept those annotations on the class level of Vehicle, but put the following annotations on the container object I am deserializing:
public class Transport {

@JsonDeserialize(using = VehicleDeserializer.class)
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
private Vehicle modeOfTransport;

// Getter, setters
}

This means that by default a Vehicle is deserialized as a polymorphic object, unless explicitly specified to deserialize it using my custom deserializer. This deserializer will then in turn defer to the polymorphism if the input is not a String.

Hopefully this will help someone running into this issue :)

How to override custom Jackson deserializer in child project?

I had to implement BeanPostProcessor and override it there:

@Configuration
public class FinalJacksonConfiguration implements BeanPostProcessor {

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Jackson2ObjectMapperBuilder) {
final Jackson2ObjectMapperBuilder builder = (Jackson2ObjectMapperBuilder) bean;
builder.serializerByType(MyInterface.class, new ChildMyInterfaceDeserializer());
}
return bean;
}
}



Related Topics



Leave a reply



Submit