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