ProblemHandlerTest.java
package com.fasterxml.jackson.databind.deser.filter;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import static org.junit.jupiter.api.Assertions.*;
import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.*;
/**
* Tests to exercise handler methods of {@link DeserializationProblemHandler}.
*
* @since 2.8
*/
public class ProblemHandlerTest
{
/*
/**********************************************************
/* Test handler types
/**********************************************************
*/
static class WeirdKeyHandler
extends DeserializationProblemHandler
{
protected final Object key;
public WeirdKeyHandler(Object key0) {
key = key0;
}
@Override
public Object handleWeirdKey(DeserializationContext ctxt,
Class<?> rawKeyType, String keyValue,
String failureMsg)
throws IOException
{
return key;
}
}
static class WeirdNumberHandler
extends DeserializationProblemHandler
{
protected final Object value;
public WeirdNumberHandler(Object v0) {
value = v0;
}
@Override
public Object handleWeirdNumberValue(DeserializationContext ctxt,
Class<?> targetType, Number n,
String failureMsg)
throws IOException
{
return value;
}
}
static class WeirdStringHandler
extends DeserializationProblemHandler
{
protected final Object value;
public WeirdStringHandler(Object v0) {
value = v0;
}
@Override
public Object handleWeirdStringValue(DeserializationContext ctxt,
Class<?> targetType, String v,
String failureMsg)
throws IOException
{
return value;
}
}
static class InstantiationProblemHandler
extends DeserializationProblemHandler
{
protected final Object value;
public InstantiationProblemHandler(Object v0) {
value = v0;
}
@Override
public Object handleInstantiationProblem(DeserializationContext ctxt,
Class<?> instClass, Object argument, Throwable t)
throws IOException
{
if (!(t instanceof ValueInstantiationException)) {
throw new IllegalArgumentException("Should have gotten `ValueInstantiationException`, instead got: "+t);
}
return value;
}
}
static class MissingInstantiationHandler
extends DeserializationProblemHandler
{
protected final Object value;
public MissingInstantiationHandler(Object v0) {
value = v0;
}
@Override
public Object handleMissingInstantiator(DeserializationContext ctxt,
Class<?> instClass, ValueInstantiator inst, JsonParser p, String msg)
throws IOException
{
p.skipChildren();
return value;
}
}
static class WeirdTokenHandler
extends DeserializationProblemHandler
{
protected final Object value;
public WeirdTokenHandler(Object v) {
value = v;
}
@Override
public Object handleUnexpectedToken(DeserializationContext ctxt,
JavaType targetType, JsonToken t, JsonParser p,
String failureMsg)
throws IOException
{
p.skipChildren();
return value;
}
}
static class UnknownTypeIdHandler
extends DeserializationProblemHandler
{
protected final Class<?> raw;
public UnknownTypeIdHandler(Class<?> r) { raw = r; }
@Override
public JavaType handleUnknownTypeId(DeserializationContext ctxt,
JavaType baseType, String subTypeId, TypeIdResolver idResolver,
String failureMsg)
throws IOException
{
return ctxt.constructType(raw);
}
}
static class MissingTypeIdHandler
extends DeserializationProblemHandler
{
protected final Class<?> raw;
public MissingTypeIdHandler(Class<?> r) { raw = r; }
@Override
public JavaType handleMissingTypeId(DeserializationContext ctxt,
JavaType baseType, TypeIdResolver idResolver,
String failureMsg)
throws IOException
{
return ctxt.constructType(raw);
}
}
/*
/**********************************************************
/* Other helper types
/**********************************************************
*/
static class IntKeyMapWrapper {
public Map<Integer,String> stuff;
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
static class Base { }
static class BaseImpl extends Base {
public int a;
}
static class BaseWrapper {
public Base value;
}
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "clazz")
static class Base2 { }
static class Base2Impl extends Base2 {
public int a;
}
static class Base2Wrapper {
public Base2 value;
}
enum SingleValuedEnum {
A;
}
static class BustedCtor {
public final static BustedCtor INST = new BustedCtor(true);
public BustedCtor() {
throw new RuntimeException("Fail! (to be caught by handler)");
}
private BustedCtor(boolean b) { }
}
static class NoDefaultCtor {
public int value;
public NoDefaultCtor(int v) { value = v; }
}
/*
/**********************************************************
/* Test methods
/**********************************************************
*/
private final ObjectMapper MAPPER = newJsonMapper();
@Test
public void testWeirdKeyHandling() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.addHandler(new WeirdKeyHandler(7))
.build();
IntKeyMapWrapper w = mapper.readValue("{\"stuff\":{\"foo\":\"abc\"}}",
IntKeyMapWrapper.class);
Map<Integer,String> map = w.stuff;
assertEquals(1, map.size());
assertEquals("abc", map.values().iterator().next());
assertEquals(Integer.valueOf(7), map.keySet().iterator().next());
}
@Test
public void testWeirdNumberHandling() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.addHandler(new WeirdNumberHandler(SingleValuedEnum.A))
.build();
SingleValuedEnum result = mapper.readValue("3", SingleValuedEnum.class);
assertEquals(SingleValuedEnum.A, result);
}
@Test
public void testWeirdStringHandling() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.addHandler(new WeirdStringHandler(SingleValuedEnum.A))
.build();
SingleValuedEnum result = mapper.readValue("\"B\"", SingleValuedEnum.class);
assertEquals(SingleValuedEnum.A, result);
// also, write [databind#1629] try this
mapper = new ObjectMapper()
.addHandler(new WeirdStringHandler(null));
UUID result2 = mapper.readValue(q("not a uuid!"), UUID.class);
assertNull(result2);
}
// [databind#3784]: Base64 decoding
@Test
public void testWeirdStringForBase64() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.addHandler(new WeirdStringHandler(new byte[0]))
.build();
byte[] binary = mapper.readValue(q("foobar"), byte[].class);
assertNotNull(binary);
assertEquals(0, binary.length);
JsonNode tree = mapper.readTree(q("foobar"));
binary = mapper.treeToValue(tree, byte[].class);
assertNotNull(binary);
assertEquals(0, binary.length);
}
@Test
public void testInvalidTypeId() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.addHandler(new UnknownTypeIdHandler(BaseImpl.class))
.build();
BaseWrapper w = mapper.readValue("{\"value\":{\"type\":\"foo\",\"a\":4}}",
BaseWrapper.class);
assertNotNull(w);
assertEquals(BaseImpl.class, w.value.getClass());
}
@Test
public void testInvalidClassAsId() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.addHandler(new UnknownTypeIdHandler(Base2Impl.class))
.build();
Base2Wrapper w = mapper.readValue("{\"value\":{\"clazz\":\"com.fizz\",\"a\":4}}",
Base2Wrapper.class);
assertNotNull(w);
assertEquals(Base2Impl.class, w.value.getClass());
}
// 2.9: missing type id, distinct from unknown
@Test
public void testMissingTypeId() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.addHandler(new MissingTypeIdHandler(BaseImpl.class))
.build();
BaseWrapper w = mapper.readValue("{\"value\":{\"a\":4}}",
BaseWrapper.class);
assertNotNull(w);
assertEquals(BaseImpl.class, w.value.getClass());
}
@Test
public void testMissingClassAsId() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.addHandler(new MissingTypeIdHandler(Base2Impl.class))
.build();
Base2Wrapper w = mapper.readValue("{\"value\":{\"a\":4}}",
Base2Wrapper.class);
assertNotNull(w);
assertEquals(Base2Impl.class, w.value.getClass());
}
// verify that by default we get special exception type
@Test
public void testInvalidTypeIdFail() throws Exception
{
try {
MAPPER.readValue("{\"value\":{\"type\":\"foo\",\"a\":4}}",
BaseWrapper.class);
fail("Should not pass");
} catch (InvalidTypeIdException e) {
verifyException(e, "Could not resolve type id 'foo'");
assertEquals(Base.class, e.getBaseType().getRawClass());
assertEquals("foo", e.getTypeId());
}
}
@Test
public void testInstantiationExceptionHandling() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.addHandler(new InstantiationProblemHandler(BustedCtor.INST))
.build();
BustedCtor w = mapper.readValue("{ }",
BustedCtor.class);
assertNotNull(w);
}
@Test
public void testMissingInstantiatorHandling() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
// 14-Jan-2025, tatu: Need to disable trailing tokens (for 3.0)
// for this to work (handler not consuming all tokens as it should
// but no time to fully fix right now)
.disable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)
.addHandler(new MissingInstantiationHandler(new NoDefaultCtor(13)))
.build();
NoDefaultCtor w = mapper.readValue("{ \"x\" : true }", NoDefaultCtor.class);
assertNotNull(w);
assertEquals(13, w.value);
}
@Test
public void testUnexpectedTokenHandling() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.addHandler(new WeirdTokenHandler(Integer.valueOf(13)))
.build();
Integer v = mapper.readValue("true", Integer.class);
assertEquals(Integer.valueOf(13), v);
// Just for code coverage really...
mapper = newJsonMapper();
mapper.addHandler(new WeirdTokenHandler(Integer.valueOf(13)));
mapper.clearProblemHandlers();
try {
mapper.readValue("true", Integer.class);
fail("Should not pass");
} catch (MismatchedInputException e) {
verifyException(e, "from Boolean value (token `JsonToken.VALUE_TRUE`)");
}
}
}