Eric
Eric

Reputation: 655

How java jackson deserializer handle both Boolean and Object on same field

I'm working with a 3rd party JSON API, it returns data like this:

{details: {...}, ...}

I use Java Jackson to deserialize this JSON string into a POJO object, the field declaration is :

@JsonProperty("details") public Details getDetails(){...}

and Details is another class.

Everything is fine until I found that API may return data like this:

{details: false, ...}

If details is empty, it returns false!!! And jackson gave me this exception:

com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class Details] from Boolean value; no single-boolean/Boolean-arg constructor/factory method (through reference chain: ...["details"])

So, how to handle this kind of JSON string? I only need this field to set to null if empty.

Upvotes: 2

Views: 3874

Answers (2)

Sharon Ben Asher
Sharon Ben Asher

Reputation: 14328

The error message from Jackson hints that the library has bulit in support for static factory methods. This is (perhaps) a simpler solution than a custom deserializer:

I created this example POJO, with a static factory method, annotated so that Jackson uses it:

public class Details {
    public String name;  // example property

    @JsonCreator
    public static Details factory(Map<String,Object> props) {

        if (props.get("details") instanceof Boolean) return null;

        Details details = new Details();
        Map<String,Object> detailsProps = (Map<String,Object>)props.get("details");
        details.name = (String)detailsProps.get("name");
        return details;
    }
}

test method:

public static void main(String[] args)
{
    String fullDetailsJson = "{\"details\": {\"name\":\"My Details\"}} ";
    String emptyDetailsJson = "{\"details\": false} ";
    ObjectMapper mapper = new ObjectMapper();
    try {
        Details details = mapper.readValue(fullDetailsJson, Details.class);
        System.out.println(details.name);
        details = mapper.readValue(emptyDetailsJson, Details.class);
        System.out.println(details);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

result is as expected:

My Details
null

Upvotes: 2

Jure Kolenko
Jure Kolenko

Reputation: 809

Make a custom JsonDeserializer to handle deserializing your Details object in which you either return null if you get false or pass the object to the default deserializer if it's an actual object. Pseudocode:

public class CustomDeserializer extends JsonDeserializer<Details>{
    @Override
    public Details deserialize(JsonParser jsonParser, DeserializationContext ctx){
        //if object use default deserializer else return null
    }
}

You'll also have to write an ObjectMapperProvider to register your deserializer like so:

@Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper>{
    private ObjectMapper mapper;

    public ObjectMapperProvider(){
        mapper = new ObjectMapper();
        SimpleModule sm = new SimpleModule();
        sm.addDeserializer(Details.class, new CustomDeserializer());
        mapper.registerModule(sm);
    }

    public ObjectMapper getContext(Class<?> arg0){
        return mapper;
    }
}

Upvotes: 1

Related Questions