package com.fasterxml.jackson.databind.deser.builder; import java.util.*; import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector; public class BuilderSimpleTest extends BaseMapTest { // // Simple 2-property value class, builder with standard naming @JsonDeserialize(builder=SimpleBuilderXY.class) static class ValueClassXY { final int _x, _y; protected ValueClassXY(int x, int y) { _x = x+1; _y = y+1; } } static class SimpleBuilderXY { public int x, y; public SimpleBuilderXY withX(int x0) { this.x = x0; return this; } public SimpleBuilderXY withY(int y0) { this.y = y0; return this; } public ValueClassXY build() { return new ValueClassXY(x, y); } } // // 3-property value, with more varied builder @JsonDeserialize(builder=BuildABC.class) static class ValueClassABC { final int a, b, c; protected ValueClassABC(int a, int b, int c) { this.a = a; this.b = b; this.c = c; } } @JsonIgnoreProperties({ "d" }) static class BuildABC { public int a; // to be used as is private int b, c; @JsonProperty("b") public BuildABC assignB(int b0) { this.b = b0; return this; } // Also ok NOT to return 'this' @JsonSetter("c") public void c(int c0) { this.c = c0; } public ValueClassABC build() { return new ValueClassABC(a, b, c); } } // // Then Builder that is itself immutable @JsonDeserialize(builder=BuildImmutable.class) static class ValueImmutable { final int value; protected ValueImmutable(int v) { value = v; } } static class BuildImmutable { private final int value; private BuildImmutable() { this(0); } private BuildImmutable(int v) { value = v; } public BuildImmutable withValue(int v) { return new BuildImmutable(v); } public ValueImmutable build() { return new ValueImmutable(value); } } // And then with custom naming: @JsonDeserialize(builder=BuildFoo.class) static class ValueFoo { final int value; protected ValueFoo(int v) { value = v; } } @JsonPOJOBuilder(withPrefix="foo", buildMethodName="construct") static class BuildFoo { private int value; public BuildFoo fooValue(int v) { value = v; return this; } public ValueFoo construct() { return new ValueFoo(value); } } // for [databind#761] @JsonDeserialize(builder=ValueInterfaceBuilder.class) interface ValueInterface { int getX(); } @JsonDeserialize(builder=ValueInterface2Builder.class) interface ValueInterface2 { int getX(); } static class ValueInterfaceImpl implements ValueInterface { final int _x; protected ValueInterfaceImpl(int x) { _x = x+1; } @Override public int getX() { return _x; } } static class ValueInterface2Impl implements ValueInterface2 { final int _x; protected ValueInterface2Impl(int x) { _x = x+1; } @Override public int getX() { return _x; } } static class ValueInterfaceBuilder { public int x; public ValueInterfaceBuilder withX(int x0) { this.x = x0; return this; } public ValueInterface build() { return new ValueInterfaceImpl(x); } } static class ValueInterface2Builder { public int x; public ValueInterface2Builder withX(int x0) { this.x = x0; return this; } // should also be ok: more specific type public ValueInterface2Impl build() { return new ValueInterface2Impl(x); } } // [databind#777] @JsonDeserialize(builder = SelfBuilder777.class) @JsonPOJOBuilder(buildMethodName = "", withPrefix = "with") static class SelfBuilder777 { public int x; public SelfBuilder777 withX(int value) { x = value; return this; } } // [databind#822] @JsonPOJOBuilder(buildMethodName = "build", withPrefix = "with") static class ValueBuilder822 { public int x; private Map<String,Object> stuff = new HashMap<String,Object>(); public ValueBuilder822 withX(int x0) { this.x = x0; return this; } @JsonAnySetter public void addStuff(String key, Object value) { stuff.put(key, value); } public ValueClass822 build() { return new ValueClass822(x, stuff); } } @JsonDeserialize(builder = ValueBuilder822.class) static class ValueClass822 { public int x; public Map<String,Object> stuff; public ValueClass822(int x, Map<String,Object> stuff) { this.x = x; this.stuff = stuff; } } protected static class NopModule1557 extends Module { @Override public String getModuleName() { return "NopModule"; } @Override public Version version() { return Version.unknownVersion(); } @Override public void setupModule(SetupContext setupContext) { // This annotation introspector has no opinion about builders, make sure it doesn't interfere setupContext.insertAnnotationIntrospector(new NopAnnotationIntrospector() { private static final long serialVersionUID = 1L; @Override public Version version() { return Version.unknownVersion(); } }); } } /* /********************************************************** /* Unit tests /********************************************************** */ private final ObjectMapper MAPPER = new ObjectMapper(); public void testSimple() throws Exception { String json = aposToQuotes("{'x':1,'y':2}"); Object o = MAPPER.readValue(json, ValueClassXY.class); assertNotNull(o); assertSame(ValueClassXY.class, o.getClass()); ValueClassXY value = (ValueClassXY) o; // note: ctor adds one to both values assertEquals(value._x, 2); assertEquals(value._y, 3); } // related to [databind#1214] public void testSimpleWithIgnores() throws Exception { // 'z' is unknown, and would fail by default: final String json = aposToQuotes("{'x':1,'y':2,'z':4}"); Object o = null; try { o = MAPPER.readValue(json, ValueClassXY.class); fail("Should not pass"); } catch (UnrecognizedPropertyException e) { assertEquals("z", e.getPropertyName()); verifyException(e, "Unrecognized field \"z\""); } // but with config overrides should pass ObjectMapper ignorantMapper = new ObjectMapper(); ignorantMapper.configOverride(SimpleBuilderXY.class) .setIgnorals(JsonIgnoreProperties.Value.forIgnoreUnknown(true)); o = ignorantMapper.readValue(json, ValueClassXY.class); assertNotNull(o); assertSame(ValueClassXY.class, o.getClass()); ValueClassXY value = (ValueClassXY) o; // note: ctor adds one to both values assertEquals(value._x, 2); assertEquals(value._y, 3); } public void testMultiAccess() throws Exception { String json = aposToQuotes("{'c':3,'a':2,'b':-9}"); ValueClassABC value = MAPPER.readValue(json, ValueClassABC.class); assertNotNull(value); assertEquals(2, value.a); assertEquals(-9, value.b); assertEquals(3, value.c); // also, since we can ignore some properties: value = MAPPER.readValue(aposToQuotes("{'c':3,'d':5,'b':-9}"), ValueClassABC.class); assertNotNull(value); assertEquals(0, value.a); assertEquals(-9, value.b); assertEquals(3, value.c); } // test for Immutable builder, to ensure return value is used public void testImmutable() throws Exception { final String json = "{\"value\":13}"; ValueImmutable value = MAPPER.readValue(json, ValueImmutable.class); assertEquals(13, value.value); } // test with custom 'with-prefix' public void testCustomWith() throws Exception { final String json = "{\"value\":1}"; ValueFoo value = MAPPER.readValue(json, ValueFoo.class); assertEquals(1, value.value); } // for [databind#761] public void testBuilderMethodReturnMoreGeneral() throws Exception { final String json = "{\"x\":1}"; ValueInterface value = MAPPER.readValue(json, ValueInterface.class); assertEquals(2, value.getX()); } public void testBuilderMethodReturnMoreSpecific() throws Exception { final String json = "{\"x\":1}"; ValueInterface2 value = MAPPER.readValue(json, ValueInterface2.class); assertEquals(2, value.getX()); } public void testSelfBuilder777() throws Exception { SelfBuilder777 result = MAPPER.readValue(aposToQuotes("{'x':3}'"), SelfBuilder777.class); assertNotNull(result); assertEquals(3, result.x); } public void testWithAnySetter822() throws Exception { final String json = "{\"extra\":3,\"foobar\":[ ],\"x\":1,\"name\":\"bob\"}"; ValueClass822 value = MAPPER.readValue(json, ValueClass822.class); assertEquals(1, value.x); assertNotNull(value.stuff); assertEquals(3, value.stuff.size()); assertEquals(Integer.valueOf(3), value.stuff.get("extra")); assertEquals("bob", value.stuff.get("name")); Object ob = value.stuff.get("foobar"); assertNotNull(ob); assertTrue(ob instanceof List); assertTrue(((List<?>) ob).isEmpty()); } public void testPOJOConfigResolution1557() throws Exception { final String json = "{\"value\":1}"; MAPPER.registerModule(new NopModule1557()); ValueFoo value = MAPPER.readValue(json, ValueFoo.class); assertEquals(1, value.value); } }