I am using Jackson 2.10.x to deserialise a Json of the format
{ myKey: "true"}
. The possible variations are { myKey: "True"}
, { myKey: "TRUE"}
and similarly for false.
The POJO that I need to deserialize to has attribute myKey::Boolean.class.
I do not own the POJO source and hence cannot set Json property on the specific attribute.
Jackson is able to deserialize when the value is "true" and "True" but not when it is "TRUE". I tried using the MapperFeature ACCEPT_CASE_INSENSITIVE_VALUES as follows but that did not help
objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES)
The exception message is
Cannot deserialize value of type `java.lang.Boolean` from String "TRUE": only "true" or "false" recognized at [Source: UNKNOWN; line: -1, column: -1]
You can add your custom com.fasterxml.jackson.databind.deser.DeserializationProblemHandler
and implement handleWeirdStringValue
method in which you can check text and return Boolean.TRUE
or Boolean.FALSE
for other cases you want to handle:
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.databind.json.JsonMapper;
import java.io.IOException;
public class JsonBooleanApp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = JsonMapper.builder()
.addHandler(new DeserializationProblemHandler() {
@Override
public Object handleWeirdStringValue(DeserializationContext ctxt, Class<?> targetType, String valueToConvert, String failureMsg) throws IOException {
if (targetType == Boolean.class) {
return Boolean.TRUE.toString().equalsIgnoreCase(valueToConvert);
}
return super.handleWeirdStringValue(ctxt, targetType, valueToConvert, failureMsg);
}
})
.build();
System.out.println(mapper.readValue("{\"value\": \"True\"}", BooleanHolder.class));
System.out.println(mapper.readValue("{\"value\": \"true\"}", BooleanHolder.class));
System.out.println(mapper.readValue("{\"value\": \"TRUE\"}", BooleanHolder.class));
}
}
class BooleanHolder {
private Boolean value;
public Boolean getValue() {
return value;
}
public void setValue(Boolean value) {
this.value = value;
}
@Override
public String toString() {
return "BooleanHolder{" +
"value=" + value +
'}';
}
}
Above code prints:
BooleanHolder{value=true}
BooleanHolder{value=true}
BooleanHolder{value=true}
Default Boolean
deserialiser in version 2.10.0
does not check MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES
feature and is a final class which does not allow to override it easily. To make it aware about a feature we need to create a copy-paste
version with some changes. To make it as close as possible to original I created com.fasterxml.jackson.databind.deser.std
package and moved there below class:
package com.fasterxml.jackson.databind.deser.std;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import java.io.IOException;
public final class BooleanDeserializerIgnoreCase extends NumberDeserializers.PrimitiveOrWrapperDeserializer<Boolean> {
private static final long serialVersionUID = 1L;
public final static BooleanDeserializerIgnoreCase primitiveInstance = new BooleanDeserializerIgnoreCase(Boolean.TYPE, Boolean.FALSE);
public final static BooleanDeserializerIgnoreCase wrapperInstance = new BooleanDeserializerIgnoreCase(Boolean.class, null);
public BooleanDeserializerIgnoreCase(Class<Boolean> cls, Boolean nvl) {
super(cls, nvl, Boolean.FALSE);
}
@Override
public Boolean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonToken t = p.getCurrentToken();
if (t == JsonToken.VALUE_TRUE) {
return Boolean.TRUE;
}
if (t == JsonToken.VALUE_FALSE) {
return Boolean.FALSE;
}
return _parseBoolean(p, ctxt);
}
// Since we can never have type info ("natural type"; String, Boolean, Integer, Double):
// (is it an error to even call this version?)
@Override
public Boolean deserializeWithType(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer)
throws IOException {
JsonToken t = p.getCurrentToken();
if (t == JsonToken.VALUE_TRUE) {
return Boolean.TRUE;
}
if (t == JsonToken.VALUE_FALSE) {
return Boolean.FALSE;
}
return _parseBoolean(p, ctxt);
}
protected final Boolean _parseBoolean(JsonParser p, DeserializationContext ctxt)
throws IOException {
JsonToken t = p.getCurrentToken();
if (t == JsonToken.VALUE_NULL) {
return (Boolean) _coerceNullToken(ctxt, _primitive);
}
if (t == JsonToken.START_ARRAY) { // unwrapping?
return _deserializeFromArray(p, ctxt);
}
// should accept ints too, (0 == false, otherwise true)
if (t == JsonToken.VALUE_NUMBER_INT) {
return Boolean.valueOf(_parseBooleanFromInt(p, ctxt));
}
// And finally, let's allow Strings to be converted too
if (t == JsonToken.VALUE_STRING) {
return _deserializeFromString(p, ctxt);
}
// usually caller should have handled but:
if (t == JsonToken.VALUE_TRUE) {
return Boolean.TRUE;
}
if (t == JsonToken.VALUE_FALSE) {
return Boolean.FALSE;
}
// Otherwise, no can do:
return (Boolean) ctxt.handleUnexpectedToken(_valueClass, p);
}
protected final Boolean _deserializeFromString(JsonParser p, DeserializationContext ctxt) throws IOException {
String text = p.getText().trim();
if (ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES)) {
if (Boolean.TRUE.toString().equalsIgnoreCase(text)) {
return Boolean.TRUE;
}
if (Boolean.FALSE.toString().equalsIgnoreCase(text)) {
return Boolean.FALSE;
}
} else {
if ("true".equals(text) || "True".equals(text)) {
_verifyStringForScalarCoercion(ctxt, text);
return Boolean.TRUE;
}
if ("false".equals(text) || "False".equals(text)) {
_verifyStringForScalarCoercion(ctxt, text);
return Boolean.FALSE;
}
if (text.length() == 0) {
return (Boolean) _coerceEmptyString(ctxt, _primitive);
}
if (_hasTextualNull(text)) {
return (Boolean) _coerceTextualNull(ctxt, _primitive);
}
}
return (Boolean) ctxt.handleWeirdStringValue(_valueClass, text,
"only \"true\" or \"false\" recognized");
}
}
Test case:
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.BooleanDeserializerIgnoreCase;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class JsonBooleanApp {
public static void main(String[] args) throws Exception {
SimpleModule booleanIgnoreCaseModule = new SimpleModule();
booleanIgnoreCaseModule.addDeserializer(Boolean.class, BooleanDeserializerIgnoreCase.wrapperInstance);
booleanIgnoreCaseModule.addDeserializer(boolean.class, BooleanDeserializerIgnoreCase.primitiveInstance);
ObjectMapper mapper = JsonMapper.builder()
.addModule(booleanIgnoreCaseModule)
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES)
.build();
System.out.println(mapper.readValue("{\"value\": \"True\"}", BooleanHolder.class));
System.out.println(mapper.readValue("{\"value\": \"true\"}", BooleanHolder.class));
System.out.println(mapper.readValue("{\"value\": \"TRUE\"}", BooleanHolder.class));
}
}
class BooleanHolder {
private Boolean value;
public Boolean getValue() {
return value;
}
public void setValue(Boolean value) {
this.value = value;
}
@Override
public String toString() {
return "BooleanHolder{" +
"value=" + value +
'}';
}
}
Above code prints:
BooleanHolder{value=true}
BooleanHolder{value=true}
BooleanHolder{value=true}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With