如何使用杰克逊以不区分大小写的方式从Json对象反序列化为Boolean.class?

时间:2019-11-22 18:39:02

标签: java json jackson jackson-databind

我正在使用Jackson 2.10.x反序列化格式的Json { myKey: "true"}。可能的变化形式为{ myKey: "True"}{ myKey: "TRUE"},对于false则类似。 我需要反序列化的POJO具有属性myKey :: Boolean.class。

我不拥有POJO源,因此无法在特定属性上设置Json属性。

当值是“ true”和“ True”时,Jackson可以反序列化,而当值是“ TRUE”时,则不能。 我尝试按如下方式使用MapperFeature ACCEPT_CASE_INSENSITIVE_VALUES,但这无济于事

objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES)

异常消息是

Cannot deserialize value of type `java.lang.Boolean` from String "TRUE": only "true" or "false" recognized at [Source: UNKNOWN; line: -1, column: -1] 

1 个答案:

答案 0 :(得分:3)

您可以添加自定义com.fasterxml.jackson.databind.deser.DeserializationProblemHandler并实现handleWeirdStringValue方法,在该方法中,您可以检查文本并针对需要处理的其他情况返回Boolean.TRUEBoolean.FALSE

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 +
                '}';
    }
}

上面的代码显示:

BooleanHolder{value=true}
BooleanHolder{value=true}
BooleanHolder{value=true}

启用MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES

版本Boolean中的默认2.10.0解串器不检查MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES功能,它是最终类,不允许轻易覆盖它。为了使它了解某个功能,我们需要创建一个copy-paste版本并进行一些更改。为了使其与原始版本尽可能接近,我创建了com.fasterxml.jackson.databind.deser.std程序包,并将其移至类下:

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

测试用例:

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 +
                '}';
    }
}

上面的代码显示:

BooleanHolder{value=true}
BooleanHolder{value=true}
BooleanHolder{value=true}