ConstructorDetectorTest.java
package com.fasterxml.jackson.databind.deser.creators;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.*;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.exc.InvalidNullException;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
// Tests for [databind#1498], [databind#3241] (Jackson 2.12)
public class ConstructorDetectorTest extends DatabindTestUtil
{
static class SingleArgNotAnnotated {
protected int v;
SingleArgNotAnnotated() { v = -1; }
public SingleArgNotAnnotated(@ImplicitName("value") int value) {
v = value;
}
}
static class SingleArgByte {
protected byte v;
SingleArgByte() { v = -1; }
public SingleArgByte(@ImplicitName("value") byte value) {
v = value;
}
}
static class SingleArgShort {
protected short v;
SingleArgShort() { v = -1; }
public SingleArgShort(@ImplicitName("value") short value) {
v = value;
}
}
static class SingleArgLong {
protected long v;
SingleArgLong() { v = -1; }
public SingleArgLong(@ImplicitName("value") long value) {
v = value;
}
}
static class SingleArgFloat {
protected float v;
SingleArgFloat() { v = -1.0f; }
public SingleArgFloat(@ImplicitName("value") float value) {
v = value;
}
}
static class SingleArgDouble {
protected double v;
SingleArgDouble() { v = -1.0; }
public SingleArgDouble(@ImplicitName("value") double value) {
v = value;
}
}
static class SingleArgNoMode {
protected int v;
SingleArgNoMode() { v = -1; }
@JsonCreator
public SingleArgNoMode(@ImplicitName("value") int value) {
v = value;
}
}
static class SingleArg2CtorsNotAnnotated {
protected int v;
SingleArg2CtorsNotAnnotated() { v = -1; }
public SingleArg2CtorsNotAnnotated(@ImplicitName("value") int value) {
v = value;
}
public SingleArg2CtorsNotAnnotated(@ImplicitName("value") long value) {
v = (int) (value * 2);
}
}
static class SingleArg1498 {
final int _bar;
// note: annotation only to inject "implicit name" without needing parameter-names module
SingleArg1498(@ImplicitName("bar") int b) {
_bar = b;
}
}
static class TwoArgsNotAnnotated {
protected int _a, _b;
public TwoArgsNotAnnotated(@ImplicitName("a") int a, @ImplicitName("b") int b) {
_a = a;
_b = b;
}
}
// [databind#3241]
static class Input3241 {
private final Boolean field;
// @JsonCreator gone!
public Input3241(@ImplicitName("field") Boolean field) {
if (field == null) {
throw new NullPointerException("Field should not remain null!");
}
this.field = field;
}
public Boolean field() {
return field;
}
}
// [databind#4860]
@JsonPropertyOrder({ "id", "name "})
static class Foo4860 {
public String id;
public String name;
public Foo4860() { }
public Foo4860(String id) {
// should not be called as of Jackson 2.x
throw new IllegalStateException("Should not auto-detect args-taking constructor");
}
}
private final ObjectMapper MAPPER_PROPS = mapperFor(ConstructorDetector.USE_PROPERTIES_BASED);
private final ObjectMapper MAPPER_DELEGATING = mapperFor(ConstructorDetector.USE_DELEGATING);
private final ObjectMapper MAPPER_DEFAULT = mapperFor(ConstructorDetector.DEFAULT);
private final ObjectMapper MAPPER_EXPLICIT = mapperFor(ConstructorDetector.EXPLICIT_ONLY);
private final ObjectMapper MAPPER_MUST_ANNOTATE = mapperFor(ConstructorDetector.DEFAULT
.withRequireAnnotation(true));
/*
/**********************************************************************
/* Test methods, selecting from 1-arg constructors, properties-based
/**********************************************************************
*/
@Test
public void test1ArgDefaultsToPropertiesNonAnnotated() throws Exception
{
SingleArgNotAnnotated value = MAPPER_PROPS.readValue(a2q("{'value' : 137 }"),
SingleArgNotAnnotated.class);
assertEquals(137, value.v);
}
@Test
public void test1ArgDefaultsToPropertiesNonAnnotatedDecimal() throws Exception
{
SingleArgNotAnnotated value = MAPPER_PROPS.readValue(a2q("{'value' : 137.0 }"),
SingleArgNotAnnotated.class);
assertEquals(137, value.v);
}
@Test
public void test1ArgDefaultsToPropertiesByte() throws Exception
{
SingleArgByte value = MAPPER_PROPS.readValue(a2q("{'value' : -99 }"),
SingleArgByte.class);
assertEquals(-99, value.v);
}
@Test
public void test1ArgDefaultsToPropertiesShort() throws Exception
{
SingleArgShort value = MAPPER_PROPS.readValue(a2q("{'value' : 137 }"),
SingleArgShort.class);
assertEquals(137, value.v);
}
@Test
public void test1ArgDefaultsToPropertiesLong() throws Exception
{
String val = Long.toString(Long.MAX_VALUE);
SingleArgLong value = MAPPER_PROPS.readValue(a2q("{'value' : " + val + " }"),
SingleArgLong.class);
assertEquals(Long.MAX_VALUE, value.v);
}
@Test
public void test1ArgDefaultsToPropertiesFloat() throws Exception
{
SingleArgFloat value = MAPPER_PROPS.readValue(a2q("{'value' : 136.99 }"),
SingleArgFloat.class);
assertEquals(136.99f, value.v);
}
@Test
public void test1ArgDefaultsToPropertiesDouble() throws Exception
{
SingleArgDouble value = MAPPER_PROPS.readValue(a2q("{'value' : 999999999999999999.99 }"),
SingleArgDouble.class);
assertEquals(999999999999999999.99, value.v);
}
@Test
public void test1ArgDefaultsToPropertiesNoMode() throws Exception
{
SingleArgNoMode value = MAPPER_PROPS.readValue(a2q("{'value' : 137 }"),
SingleArgNoMode.class);
assertEquals(137, value.v);
}
// And specific test for original [databind#1498]
@Test
public void test1ArgDefaultsToPropertiesIssue1498() throws Exception
{
SingleArg1498 value = MAPPER_PROPS.readValue(a2q("{'bar' : 404 }"),
SingleArg1498.class);
assertEquals(404, value._bar);
}
// This was working already but verify
@Test
public void testMultiArgAsProperties() throws Exception
{
TwoArgsNotAnnotated value = MAPPER_PROPS.readValue(a2q("{'a' : 3, 'b':4 }"),
TwoArgsNotAnnotated.class);
assertEquals(3, value._a);
assertEquals(4, value._b);
}
// 18-Sep-2020, tatu: For now there is a problematic case of multiple eligible
// choices; not cleanly solvable for 2.12
@Test
public void test1ArgDefaultsToPropsMultipleCtors() throws Exception
{
// 23-May-2024, tatu: Will fail differently with [databind#4515]; default
// constructor available, implicit ones ignored
try {
MAPPER_PROPS.readValue(a2q("{'value' : 137 }"),
SingleArg2CtorsNotAnnotated.class);
fail("Should not pass");
} catch (UnrecognizedPropertyException e) {
verifyException(e, "\"value\"");
}
/*
} catch (InvalidDefinitionException e) {
verifyException(e, "Conflicting property-based creators");
}
*/
}
/*
/**********************************************************************
/* Test methods, selecting from 1-arg constructors, delegating
/**********************************************************************
*/
@Test
public void test1ArgDefaultsToDelegatingNoAnnotation() throws Exception
{
// No annotation, should be fine?
SingleArgNotAnnotated value = MAPPER_DELEGATING.readValue("1972", SingleArgNotAnnotated.class);
assertEquals(1972, value.v);
}
@Test
public void test1ArgDefaultsToDelegatingNoMode() throws Exception
{
// One with `@JsonCreator` no mode annotation (ok since indicated)
SingleArgNoMode value = MAPPER_DELEGATING.readValue(" 2812 ", SingleArgNoMode.class);
assertEquals(2812, value.v);
}
/*
/**********************************************************************
/* Test methods, selecting from 1-arg constructors, heuristic (pre-2.12)
/**********************************************************************
*/
@Test
public void test1ArgDefaultsToHeuristics() throws Exception
{
final ObjectMapper mapper = mapperFor(ConstructorDetector.DEFAULT);
final String DOC = " 13117 ";
// First: unannotated is ok, defaults to delegating
SingleArgNotAnnotated v1 = mapper.readValue(DOC, SingleArgNotAnnotated.class);
assertEquals(13117, v1.v);
// and ditto for mode-less
SingleArgNoMode v2 = mapper.readValue(DOC, SingleArgNoMode.class);
assertEquals(13117, v2.v);
}
/*
/**********************************************************************
/* Test methods, selecting from 1-arg constructors, explicit fails
/**********************************************************************
*/
// 15-Sep-2020, tatu: Tricky semantics... should this require turning
// off of auto-detection? If there is 0-arg ctor, that is to be used
/*
public void test1ArgFailsNoAnnotation() throws Exception
{
// First: fail if nothing annotated (for 1-arg case)
try {
MAPPER_EXPLICIT.readValue(" 2812 ", SingleArgNotAnnotated.class);
fail("Should not pass");
} catch (InvalidDefinitionException e) {
verifyException(e, "foobar");
}
}
*/
@Test
public void test1ArgFailsNoMode() throws Exception
{
// Second: also fail also if no "mode" indicated
try {
MAPPER_EXPLICIT.readValue(" 2812 ", SingleArgNoMode.class);
fail("Should not pass");
} catch (InvalidDefinitionException e) {
verifyException(e, "no 'mode' defined");
verifyException(e, "SingleArgConstructor.REQUIRE_MODE");
}
}
@Test
public void test1ArgRequiresAnnotation() throws Exception
{
// First: if there is a 0-arg ctor, fine, must use that
SingleArgNotAnnotated value = MAPPER_MUST_ANNOTATE.readValue("{ }",
SingleArgNotAnnotated.class);
assertEquals(new SingleArgNotAnnotated().v, value.v);
// But if no such ctor, will fail
try {
MAPPER_MUST_ANNOTATE.readValue(" { } ", SingleArg1498.class);
fail("Should not pass");
} catch (InvalidDefinitionException e) {
verifyException(e, "no Creators, like default constructor");
}
}
@Test
public void testMultiArgRequiresAnnotation() throws Exception
{
try {
MAPPER_MUST_ANNOTATE.readValue(" { } ", TwoArgsNotAnnotated.class);
fail("Should not pass");
} catch (InvalidDefinitionException e) {
verifyException(e, "no Creators, like default constructor");
}
}
// [databind#3241]
@Test
void nullHandlingCreator3241() throws Exception {
ObjectMapper mapper = mapperBuilder()
.constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED) // new!
.defaultSetterInfo(JsonSetter.Value.construct(Nulls.FAIL, Nulls.FAIL))
.build();
try {
mapper.readValue("{ \"field\": null }", Input3241.class);
fail("InvalidNullException expected");
} catch (InvalidNullException e) {
verifyException(e, "Invalid `null` value encountered");
}
}
// [databind#4860]
@Test
public void testDeserialization4860PropsBased() throws Exception {
_test4680With(MAPPER_PROPS);
}
@Test
public void testDeserialization4860Delegating() throws Exception {
_test4680With(MAPPER_DELEGATING);
}
@Test
public void testDeserialization4860Default() throws Exception {
_test4680With(MAPPER_DEFAULT);
}
@Test
public void testDeserialization4860Explicit() throws Exception {
_test4680With(MAPPER_EXPLICIT);
}
private void _test4680With(ObjectMapper mapper) throws Exception
{
_test4680With(mapper, "{}", a2q("{'id':null,'name':null}"));
_test4680With(mapper, a2q("{'id':'something'}"),
a2q("{'id':'something','name':null}"));
_test4680With(mapper, a2q("{'id':'something','name':'name'}"),
a2q("{'id':'something','name':'name'}"));
}
private void _test4680With(ObjectMapper mapper, String input, String output) throws Exception
{
Foo4860 result = mapper.readValue(input, Foo4860.class);
assertEquals(output, mapper.writeValueAsString(result));
}
/*
/**********************************************************************
/* Helper methods
/**********************************************************************
*/
private JsonMapper.Builder mapperBuilder() {
return JsonMapper.builder()
.annotationIntrospector(new ImplicitNameIntrospector());
}
private ObjectMapper mapperFor(ConstructorDetector cd) {
return mapperBuilder()
.constructorDetector(cd)
.build();
}
}