1 package com.fasterxml.jackson.databind.deser.merge; 2 3 import java.util.*; 4 5 import com.fasterxml.jackson.annotation.JsonMerge; 6 import com.fasterxml.jackson.annotation.JsonSetter; 7 import com.fasterxml.jackson.annotation.Nulls; 8 import com.fasterxml.jackson.databind.*; 9 10 public class MapMergeTest extends BaseMapTest 11 { 12 static class MergedMap 13 { 14 @JsonMerge 15 public Map<String,Object> values; 16 MergedMap()17 protected MergedMap() { 18 values = new LinkedHashMap<>(); 19 values.put("a", "x"); 20 } 21 MergedMap(String a, String b)22 public MergedMap(String a, String b) { 23 values = new LinkedHashMap<>(); 24 values.put(a, b); 25 } 26 MergedMap(Map<String,Object> src)27 public MergedMap(Map<String,Object> src) { 28 values = src; 29 } 30 } 31 32 static class MergedIntMap 33 { 34 @JsonMerge 35 public Map<Integer,Object> values; 36 MergedIntMap()37 protected MergedIntMap() { 38 values = new LinkedHashMap<>(); 39 values.put(Integer.valueOf(13), "a"); 40 } 41 } 42 43 /* 44 /******************************************************** 45 /* Test methods, Map merging 46 /******************************************************** 47 */ 48 49 private final ObjectMapper MAPPER = jsonMapperBuilder() 50 // 26-Oct-2016, tatu: Make sure we'll report merge problems by default 51 .disable(MapperFeature.IGNORE_MERGE_FOR_UNMERGEABLE) 52 .build(); 53 54 private final ObjectMapper MAPPER_SKIP_NULLS = newJsonMapper() 55 .setDefaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP)); 56 ; 57 testShallowMapMerging()58 public void testShallowMapMerging() throws Exception 59 { 60 final String JSON = aposToQuotes("{'values':{'c':'y','d':null}}"); 61 MergedMap v = MAPPER.readValue(JSON, MergedMap.class); 62 assertEquals(3, v.values.size()); 63 assertEquals("y", v.values.get("c")); 64 assertEquals("x", v.values.get("a")); 65 assertNull(v.values.get("d")); 66 67 // but also, skip nulls 68 v = MAPPER_SKIP_NULLS.readValue(JSON, MergedMap.class); 69 assertEquals(2, v.values.size()); 70 assertEquals("y", v.values.get("c")); 71 assertEquals("x", v.values.get("a")); 72 } 73 testShallowNonStringMerging()74 public void testShallowNonStringMerging() throws Exception 75 { 76 final String JSON = aposToQuotes("{'values':{'72':'b','666':null}}"); 77 MergedIntMap v = MAPPER.readValue(JSON , MergedIntMap.class); 78 assertEquals(3, v.values.size()); 79 assertEquals("a", v.values.get(Integer.valueOf(13))); 80 assertEquals("b", v.values.get(Integer.valueOf(72))); 81 assertNull(v.values.get(Integer.valueOf(666))); 82 83 v = MAPPER_SKIP_NULLS.readValue(JSON , MergedIntMap.class); 84 assertEquals(2, v.values.size()); 85 assertEquals("a", v.values.get(Integer.valueOf(13))); 86 assertEquals("b", v.values.get(Integer.valueOf(72))); 87 } 88 89 @SuppressWarnings("unchecked") testDeeperMapMerging()90 public void testDeeperMapMerging() throws Exception 91 { 92 // first, create base Map 93 MergedMap base = new MergedMap("name", "foobar"); 94 Map<String,Object> props = new LinkedHashMap<>(); 95 props.put("default", "yes"); 96 props.put("x", "abc"); 97 Map<String,Object> innerProps = new LinkedHashMap<>(); 98 innerProps.put("z", Integer.valueOf(13)); 99 props.put("extra", innerProps); 100 base.values.put("props", props); 101 102 // to be update 103 MergedMap v = MAPPER.readerForUpdating(base) 104 .readValue(aposToQuotes("{'values':{'props':{'x':'xyz','y' : '...','extra':{ 'ab' : true}}}}")); 105 assertEquals(2, v.values.size()); 106 assertEquals("foobar", v.values.get("name")); 107 assertNotNull(v.values.get("props")); 108 props = (Map<String,Object>) v.values.get("props"); 109 assertEquals(4, props.size()); 110 assertEquals("yes", props.get("default")); 111 assertEquals("xyz", props.get("x")); 112 assertEquals("...", props.get("y")); 113 assertNotNull(props.get("extra")); 114 innerProps = (Map<String,Object>) props.get("extra"); 115 assertEquals(2, innerProps.size()); 116 assertEquals(Integer.valueOf(13), innerProps.get("z")); 117 assertEquals(Boolean.TRUE, innerProps.get("ab")); 118 } 119 120 @SuppressWarnings("unchecked") testMapMergingWithArray()121 public void testMapMergingWithArray() throws Exception 122 { 123 // first, create base Map 124 MergedMap base = new MergedMap("name", "foobar"); 125 Map<String,Object> props = new LinkedHashMap<>(); 126 List<String> names = new ArrayList<>(); 127 names.add("foo"); 128 props.put("names", names); 129 base.values.put("props", props); 130 props.put("extra", "misc"); 131 132 // to be update 133 MergedMap v = MAPPER.readerForUpdating(base) 134 .readValue(aposToQuotes("{'values':{'props':{'names': [ 'bar' ] }}}")); 135 assertEquals(2, v.values.size()); 136 assertEquals("foobar", v.values.get("name")); 137 assertNotNull(v.values.get("props")); 138 props = (Map<String,Object>) v.values.get("props"); 139 assertEquals(2, props.size()); 140 assertEquals("misc", props.get("extra")); 141 assertNotNull(props.get("names")); 142 names = (List<String>) props.get("names"); 143 assertEquals(2, names.size()); 144 assertEquals("foo", names.get(0)); 145 assertEquals("bar", names.get(1)); 146 } 147 148 /* 149 /******************************************************** 150 /* Forcing shallow merge of root Maps: 151 /******************************************************** 152 */ 153 testDefaultDeepMapMerge()154 public void testDefaultDeepMapMerge() throws Exception 155 { 156 // First: deep merge should be enabled by default 157 HashMap<String,Object> input = new HashMap<>(); 158 input.put("list", new ArrayList<>(Arrays.asList("a"))); 159 160 Map<?,?> resultMap = MAPPER.readerForUpdating(input) 161 .readValue(aposToQuotes("{'list':['b']}")); 162 163 List<?> resultList = (List<?>) resultMap.get("list"); 164 assertEquals(Arrays.asList("a", "b"), resultList); 165 } 166 testDisabledMergeViaGlobal()167 public void testDisabledMergeViaGlobal() throws Exception 168 { 169 ObjectMapper mapper = newJsonMapper(); 170 // disable merging, globally; does not affect main level 171 mapper.setDefaultMergeable(false); 172 173 HashMap<String,Object> input = new HashMap<>(); 174 input.put("list", new ArrayList<>(Arrays.asList("a"))); 175 176 Map<?,?> resultMap = mapper.readerForUpdating(input) 177 .readValue(aposToQuotes("{'list':['b']}")); 178 179 List<?> resultList = (List<?>) resultMap.get("list"); 180 181 assertEquals(Arrays.asList("b"), resultList); 182 } 183 testDisabledMergeByType()184 public void testDisabledMergeByType() throws Exception 185 { 186 ObjectMapper mapper = newJsonMapper(); 187 // disable merging for "untyped", that is, `Object.class` 188 mapper.configOverride(Object.class) 189 .setMergeable(false); 190 191 HashMap<String,Object> input = new HashMap<>(); 192 input.put("list", new ArrayList<>(Arrays.asList("a"))); 193 194 Map<?,?> resultMap = mapper.readerForUpdating(input) 195 .readValue(aposToQuotes("{'list':['b']}")); 196 List<?> resultList = (List<?>) resultMap.get("list"); 197 assertEquals(Arrays.asList("b"), resultList); 198 199 // and for extra points, disable by default but ENABLE for type, 200 // which should once again allow merging 201 202 mapper = newJsonMapper(); 203 mapper.setDefaultMergeable(false); 204 mapper.configOverride(Object.class) 205 .setMergeable(true); 206 207 input = new HashMap<>(); 208 input.put("list", new ArrayList<>(Arrays.asList("x"))); 209 210 resultMap = mapper.readerForUpdating(input) 211 .readValue(aposToQuotes("{'list':['y']}")); 212 resultList = (List<?>) resultMap.get("list"); 213 assertEquals(Arrays.asList("x", "y"), resultList); 214 } 215 } 216