Enable Jackson Deserialization of Empty Objects to Null
Asked Answered
C

4

9

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).

Cruikshank answered 12/8, 2019 at 15:57 Comment(0)
R
4

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));
    }

}
Racine answered 12/8, 2019 at 16:16 Comment(2)
an empty object for me is a one with all its members to be null. And not a serializer, but deserializer (if that change anything).Cruikshank
Sorry for the mistake.Racine
M
2

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.

Messner answered 12/8, 2019 at 20:16 Comment(2)
I think this property is related to serialization and not to deserializationCruikshank
From what I can see you are serializing, then deserializing. You can't deserialize something that isn't there.Messner
J
1

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.

Judon answered 22/7, 2020 at 9:2 Comment(4)
Custom deserializer is a solution which I could not apply since the demand was to apply it for every object. Anyway, I don't work for that company anymore and as I remember this task was not implemented since it was stupid in the first place - no value but only risks and possibly performance penalties. I believe the best solution is to avoid implementing it in the first place, because it may mean that something is wrong with your flows.Cruikshank
@Cruikshank I disagree. I find this very reasonable. I want to do this. One Rest endpoint I call send me some empty objects, and I just don't want to take one random property from it to know if it came empty, I think if it's null is more natural. By the way, I only mean {} , not "" or []Duffel
In your case you can just write a custom deserializer for the request object of your specific rest point. I was talking about something generic for all endpoints.Cruikshank
I don't remember if it works with all the jackson' versions, but if you put your serializer on top of the class with @JsonDeserializer(...), it will be always usedJudon
C
0

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.

Chiropody answered 14/1, 2023 at 17:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.