23

I'm writing a JSON Client for a Server that returns Boolean values as "0" and "1". When I try to run my JSON Client I currently get the following Exception:

HttpMessageNotReadableException: Could not read JSON: Can not construct instance of java.lang.Boolean from String value '0': only "true" or "false" recognized

So how can I setup FasterXML\Jackson to correctly parse something like:

{
   "SomeServerType" : {
     "ID" : "12345",
     "ThisIsABoolean" : "0",
     "ThisIsABooleanToo" : "1"
   }
}

Sample Pojo's:

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"someServerType"})
public class myPojo
{
   @JsonProperty("someServerType")
   SomeServerType someServerType;

   @JsonProperty("someServerType")
   public SomeServerType getSomeServerType() { return someServerType; }

   @JsonProperty("someServertype")
   public void setSomeServerType(SomeServerType type)
   { someServerType = type; }
}

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"someServerType"})
public class SomeServerType 
{
   @JsonProperty("ID")
   Integer ID;

   @JsonProperty("ThisIsABoolean")
   Boolean bool;

   @JsonProperty("ThisIsABooleanToo")
   Boolean boolToo;

   @JsonProperty("ID")
   public Integer getID() { return ID; }

   @JsonProperty("ID")
   public void setID(Integer id)
   { ID = id; }

   @JsonProperty("ThisIsABoolean")
   public Boolean getThisIsABoolean() { return bool; }

   @JsonProperty("ThisIsABoolean")
   public void setThisIsABoolean(Boolean b) { bool = b; }

   @JsonProperty("ThisIsABooleanToo")
   public Boolean getThisIsABooleanToo() { return boolToo; }

   @JsonProperty("ThisIsABooleanToo")
   public void setThisIsABooleanToo(Boolean b) { boolToo = b; }
}

Rest Client Line
Note 1: This is using Spring 3.2
Note 2: toJSONString() - is a helper method that uses Jackson to Serialize my Parameters Object
Note 3: The Exception happens on Reading IN the result object

DocInfoResponse result = restTemplate.getForObject(docInfoURI.toString()
                                  + "/?input={input}",
                                  DocInfoResponse.class,
                                  toJSONString(params));
naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
Raystorm
  • 6,180
  • 4
  • 35
  • 62

2 Answers2

45

As Paulo Pedroso's answer mentioned and referenced, you will need to roll your own custom JsonSerializer and JsonDeserializer. Once created, you will need to add the @JsonSerialize and @JsonDeserialize annotations to your property; specifying the class to use for each.

I have provided a small (hopefully straightforward) example below. Neither the serializer nor deserializer implementations are super robust but this should get you started.

public static class SimplePojo {

    @JsonProperty
    @JsonSerialize(using=NumericBooleanSerializer.class)
    @JsonDeserialize(using=NumericBooleanDeserializer.class)
    Boolean bool;
}

public static class NumericBooleanSerializer extends JsonSerializer<Boolean> {

    @Override
    public void serialize(Boolean bool, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException {
        generator.writeString(bool ? "1" : "0");
    }   
}

public static class NumericBooleanDeserializer extends JsonDeserializer<Boolean> {

    @Override
    public Boolean deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
        return !"0".equals(parser.getText());
    }       
}

@Test
public void readAndWrite() throws JsonParseException, JsonMappingException, IOException {
    ObjectMapper mapper = new ObjectMapper();

    // read it
    SimplePojo sp = mapper.readValue("{\"bool\":\"0\"}", SimplePojo.class);
    assertThat(sp.bool, is(false));

    // write it
    StringWriter writer = new StringWriter();
    mapper.writeValue(writer, sp);
    assertThat(writer.toString(), is("{\"bool\":\"0\"}"));
}
callmepills
  • 672
  • 9
  • 13
  • 5
    Actually you don't need the Deserializer: Jackson will automatically deserialize 0/1 to a Boolean POJO property, without any annotation. And in my event I didn't need the Serializer either, since the server I'm calling is accepting and converting true/false literals for this field. I'm awarding the bounty anyway. – Andrew Spencer Feb 15 '16 at 14:38
  • 3
    @AndrewSpencer Thanks, I appreciate the award. :) To be clear, Jackson appears to automatically deserialize the property if it comes in as a **number** but not as a **string**. – callmepills Feb 15 '16 at 16:24
  • God forbid the JSON being deserialized does not contain the field, or it does and the value is empty. Jackson will throw a wrapped NPE that looks like this: Failed converting object com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) – snakedog Dec 09 '19 at 17:36
5

Instead of custom deserializer, you could also simply have a setter like:

public void setThisIsABoolean(String str) {
  if ("0".equals(str)) {
    bool = false;
  } else {
    bool = true;
  }
}

since your method can claim different type than what you use internally.

And if you have to support both Boolean and String, you can indicate value is an Object, and check what you might get.

It should even be possible to have different type for getter method (Boolean) and setter (String or Object).

approxiblue
  • 6,982
  • 16
  • 51
  • 59
StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • Good for mutable objects. If our POJO is immutable, we cannot use this method. – galcyurio May 23 '18 at 15:15
  • 1
    @galcyurio Same idea works with `@JsonCreator` annotated constructor and factory methods too wor what that's worth (or, equivalent for Builder-approach). – StaxMan May 23 '18 at 17:47