package com.fasterxml.jackson.databind.deser.merge; import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.annotation.JsonFormat.Shape; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; /** * Tests to make sure that the new "merging" property of * <code>JsonSetter</code> annotation works as expected. * * @since 2.9 */ @SuppressWarnings("serial") public class PropertyMergeTest extends BaseMapTest { static class Config { @JsonMerge public AB loc = new AB(1, 2); protected Config() { } public Config(int a, int b) { loc = new AB(a, b); } } static class NonMergeConfig { public AB loc = new AB(1, 2); } // another variant where all we got is a getter static class NoSetterConfig { AB _value = new AB(1, 2); @JsonMerge public AB getValue() { return _value; } } static class AB { public int a; public int b; protected AB() { } public AB(int a0, int b0) { a = a0; b = b0; } } @JsonPropertyOrder(alphabetic=true) @JsonFormat(shape=Shape.ARRAY) static class ABAsArray { public int a; public int b; } // Custom type that would be deserializable by default static class StringReference extends AtomicReference<String> { public StringReference(String str) { set(str); } } static class MergedReference { @JsonMerge public StringReference value = new StringReference("default"); } static class MergedX<T> { @JsonMerge public T value; public MergedX(T v) { value = v; } protected MergedX() { } } // // // Classes with invalid merge definition(s) static class CantMergeInts { @JsonMerge public int value; } /* /******************************************************** /* Test methods, POJO merging /******************************************************** */ private final ObjectMapper MAPPER = newObjectMapper() // 26-Oct-2016, tatu: Make sure we'll report merge problems by default .disable(MapperFeature.IGNORE_MERGE_FOR_UNMERGEABLE) ; public void testBeanMergingViaProp() throws Exception { Config config = MAPPER.readValue(aposToQuotes("{'loc':{'b':3}}"), Config.class); assertEquals(1, config.loc.a); assertEquals(3, config.loc.b); config = MAPPER.readerForUpdating(new Config(5, 7)) .readValue(aposToQuotes("{'loc':{'b':2}}")); assertEquals(5, config.loc.a); assertEquals(2, config.loc.b); } public void testBeanMergingViaType() throws Exception { // by default, no merging NonMergeConfig config = MAPPER.readValue(aposToQuotes("{'loc':{'a':3}}"), NonMergeConfig.class); assertEquals(3, config.loc.a); assertEquals(0, config.loc.b); // not passed, nor merge from original // but with type-overrides ObjectMapper mapper = newObjectMapper(); mapper.configOverride(AB.class).setMergeable(true); config = mapper.readValue(aposToQuotes("{'loc':{'a':3}}"), NonMergeConfig.class); assertEquals(3, config.loc.a); assertEquals(2, config.loc.b); // original, merged } public void testBeanMergingViaGlobal() throws Exception { // but with type-overrides ObjectMapper mapper = newObjectMapper() .setDefaultMergeable(true); NonMergeConfig config = mapper.readValue(aposToQuotes("{'loc':{'a':3}}"), NonMergeConfig.class); assertEquals(3, config.loc.a); assertEquals(2, config.loc.b); // original, merged // also, test with bigger POJO type; just as smoke test FiveMinuteUser user0 = new FiveMinuteUser("Bob", "Bush", true, FiveMinuteUser.Gender.MALE, new byte[] { 1, 2, 3, 4, 5 }); FiveMinuteUser user = mapper.readerFor(FiveMinuteUser.class) .withValueToUpdate(user0) .readValue(aposToQuotes("{'name':{'last':'Brown'}}")); assertEquals("Bob", user.getName().getFirst()); assertEquals("Brown", user.getName().getLast()); } // should even work with no setter public void testBeanMergingWithoutSetter() throws Exception { NoSetterConfig config = MAPPER.readValue(aposToQuotes("{'value':{'b':99}}"), NoSetterConfig.class); assertEquals(99, config._value.b); assertEquals(1, config._value.a); } /* /******************************************************** /* Test methods, as array /******************************************************** */ public void testBeanAsArrayMerging() throws Exception { ABAsArray input = new ABAsArray(); input.a = 4; input.b = 6; assertSame(input, MAPPER.readerForUpdating(input) .readValue("[1, 3]")); assertEquals(1, input.a); assertEquals(3, input.b); // then with one too few assertSame(input, MAPPER.readerForUpdating(input) .readValue("[9]")); assertEquals(9, input.a); assertEquals(3, input.b); // and finally with extra, failing try { MAPPER.readerForUpdating(input) .readValue("[9, 8, 14]"); fail("Should not pass"); } catch (MismatchedInputException e) { verifyException(e, "expected at most 2 properties"); } try { MAPPER.readerForUpdating(input) .readValue("\"blob\""); fail("Should not pass"); } catch (MismatchedInputException e) { verifyException(e, "Can not deserialize"); verifyException(e, "from non-Array representation"); } } /* /******************************************************** /* Test methods, reference types /******************************************************** */ public void testReferenceMerging() throws Exception { MergedReference result = MAPPER.readValue(aposToQuotes("{'value':'override'}"), MergedReference.class); assertEquals("override", result.value.get()); } /* /******************************************************** /* Test methods, failure checking /******************************************************** */ public void testInvalidPropertyMerge() throws Exception { ObjectMapper mapper = newObjectMapper() .disable(MapperFeature.IGNORE_MERGE_FOR_UNMERGEABLE); try { mapper.readValue("{\"value\":3}", CantMergeInts.class); fail("Should not pass"); } catch (InvalidDefinitionException e) { verifyException(e, "can not be merged"); } } }