简体   繁体   中英

Enable Jackson Deserialization of Empty Objects to Null

I was asked to change our jackson mapping configuration so that each empty object we deserialize (from JSON) is going to be deserialized as null.

The problem is that I'm struggling to do it, but without any luck. Here is a sample of our ObjectMapper configuration (and example):

ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, true);
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ISO_DATE_TIME));
javaTimeModule.addDeserializer(Instant.class, InstantDeserializer.INSTANT);
mapper.registerModule(javaTimeModule);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
warmupMapper(mapper);

return mapper;

I thought about something like adding:

mapper.configure(
    DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);

but it just works on strings.

I'm afraid that using a custom deserializer will not help me, because I'm writing a generic (for all objects) mapper. So I probably need something like a delegator or a post process deserialization method.

So for json like "" or {} I expect to be converted to null in java (and not to empty string or Object instance).

What is a empty object for you? A object with null value fields? A object with no fields? You can create a custom to check the nodes and deserialize how you want. I see no problem to use it in a generic way.

I did a little example:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.util.Objects;

public class DeserializerExample<T> extends StdDeserializer<T> {

    private final ObjectMapper defaultMapper;

    public DeserializerExample(Class<T> clazz) {
        super(clazz);
        defaultMapper = new ObjectMapper();
    }

    @Override
    public T deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
        System.out.println("Deserializing...");

        JsonNode node = jp.getCodec().readTree(jp);

        for (JsonNode jsonNode : node) {
            if (!jsonNode.isNull()) {
                return defaultMapper.treeToValue(node, (Class<T>) getValueClass());
            }
        }

        return null;
    }

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

        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addDeserializer(Person.class, new DeserializerExample(Person.class));
        mapper.registerModule(module);

        Person person = mapper.readValue("{\"id\":1, \"name\":\"Joseph\"}", Person.class);

        Person nullPerson = mapper.readValue("{\"id\":null, \"name\":null}", Person.class);

        System.out.println("Is null: " + Objects.isNull(person));
        System.out.println("Is null: " + Objects.isNull(nullPerson));
    }

}

I had the same problem.

I hava a City class and sometimes I recive 'city':{} from a web service request.

So, the standard serializer create a new City with all empty field.

I created a custom deserializer in this way

public class CityJsonDeSerializer extends StdDeserializer<City> {

@Override
public City deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
    
    JsonNode node = jp.getCodec().readTree(jp);
    
    if(node.isNull() || node.asText().isEmpty()|| node.size()==0)
        return null;
    
    City city = new City();
    ... // set all fields
    return city;
}
}

The if check the conditions:

  • 'city' : null
  • 'city' : ''
  • 'city' : '{}'

and if it's true, the deserializer returns null .

The only way to do this is to use a custom deserializer:

class CustomDeserializer extends JsonDeserializer<String> {

@Override
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
    JsonNode node = jsonParser.readValueAsTree();
    if (node.asText().isEmpty()) {
        return null;
    }
    return node.toString();
}
}

Then do:

class EventBean {
public Long eventId;
public String title;

@JsonDeserialize(using = CustomDeserializer.class)
public String location;
}

This solution courtesy of Sach141 on this question .

Another approach is to use a com.fasterxml.jackson.databind.util.Converter<IN,OUT> , which is essentially a postprocessor for deserialization.

Imagine we have a class:

public class Person {
    public String id;
    public String name;
}

Now imagine we want to deserialize an empty JSON object {} as null , rather than a Person with null values for id and name . We can create the following Converter :

public PersonConverter implements Converter<Person,Person> {
    @Override
    public Person convert(Person p) {
        return isEmpty(p) ? null : value;
    }

    private static boolean isEmpty(Person p) {
        if(p == null) {
            return true;
        }
        if(Optional.ofNullable(p.id).orElse("").isEmpty() && 
                Optional.ofNullable(p.name).orElse("").isEmpty()) {
            return true;
        }
        return false;
    }

    @Override
    public JavaType getInputType(TypeFactory typeFactory) {
        return typeFactory.constructType(Person.class);
    }

    @Override
    public JavaType getOutputType(TypeFactory typeFactory) {
        return typeFactory.constructType(Person.class);
    }
}

Note that we have to handle the blank String case because that is (counter-intuitively) the default value assigned to properties not given in JSON, as opposed to null .

Given the converter, we can then annotate our original Person class:

@JsonDeserialize(converter=PersonConverter.class)
public class Person {
    public String id;
    public String name;
}

The benefit of this approach is that you don't have to think or care about deserialization at all; you're simply deciding what to do with the deserialized object after it's deserialized. And there are many other transformations you can do with a Converter , too. But this works nicely for nullifying "empty" values.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM