blob: 4f39697a2ebb526c852722eded3802fcf1eeedf4 [file] [log] [blame]
Tatu Salorantaeffb8fd2012-02-04 19:20:10 -08001package com.fasterxml.jackson.databind.struct;
2
Pascal Gélinas10f89072013-12-19 08:56:31 -05003import java.util.ArrayList;
Pascal Gélinas0da9a302013-12-19 08:44:22 -05004import java.util.EnumMap;
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -05005import java.util.HashMap;
Pascal Gélinas1743f002013-12-18 17:38:17 -05006import java.util.Iterator;
Pascal Gélinas3a92b122013-12-19 08:43:54 -05007import java.util.List;
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -05008import java.util.Map;
Pascal Gélinas1743f002013-12-18 17:38:17 -05009import java.util.Map.Entry;
Pascal Gélinas0da9a302013-12-19 08:44:22 -050010import java.util.concurrent.ArrayBlockingQueue;
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -050011
12import com.fasterxml.jackson.annotation.JsonAnySetter;
Tatu Salorantad4531822012-02-06 22:44:40 -080013import com.fasterxml.jackson.annotation.JsonIdentityInfo;
14import com.fasterxml.jackson.annotation.ObjectIdGenerators;
Tatu Salorantaeffb8fd2012-02-04 19:20:10 -080015import com.fasterxml.jackson.databind.*;
Pascal Gélinas096e02b2013-12-18 17:21:42 -050016import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference;
Pascal Gélinas3a92b122013-12-19 08:43:54 -050017import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference.UnresolvedId;
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -050018import com.fasterxml.jackson.databind.struct.TestObjectId.Company;
19import com.fasterxml.jackson.databind.struct.TestObjectId.Employee;
Pascal Gélinas0da9a302013-12-19 08:44:22 -050020import com.fasterxml.jackson.databind.struct.TestObjectIdDeserialization.EnumMapCompany.FooEnum;
Tatu Salorantaeffb8fd2012-02-04 19:20:10 -080021
Tatu Saloranta05c82f22012-02-09 21:58:10 -080022/**
23 * Unit test to verify handling of Object Id deserialization
24 */
25public class TestObjectIdDeserialization extends BaseMapTest
Tatu Salorantaeffb8fd2012-02-04 19:20:10 -080026{
Tatu Saloranta7fbc6592012-02-09 22:51:09 -080027 // // Classes for external id use
28
Tatu Salorantad4531822012-02-06 22:44:40 -080029 @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="id")
Tatu Salorantaeffb8fd2012-02-04 19:20:10 -080030 static class Identifiable
31 {
Tatu Salorantaeffb8fd2012-02-04 19:20:10 -080032 public int value;
33
34 public Identifiable next;
35
Tatu Salorantad4531822012-02-06 22:44:40 -080036 public Identifiable() { this(0); }
37 public Identifiable(int v) {
Tatu Salorantaeffb8fd2012-02-04 19:20:10 -080038 value = v;
39 }
40 }
Tatu58077e92012-02-07 19:47:33 -080041
Tatu Saloranta7fbc6592012-02-09 22:51:09 -080042 @JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class, property="#")
43 static class UUIDNode
44 {
45 public int value;
46 public UUIDNode parent;
47 public UUIDNode first;
48 public UUIDNode second;
49
50 public UUIDNode() { this(0); }
51 public UUIDNode(int v) { value = v; }
52 }
53
54 // // Classes for external id from property annotations:
55
Tatu58077e92012-02-07 19:47:33 -080056 static class IdWrapper
57 {
58 @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
59 public ValueNode node;
60
61 public IdWrapper() { }
62 public IdWrapper(int v) {
63 node = new ValueNode(v);
64 }
65 }
66
67 static class ValueNode {
68 public int value;
69 public IdWrapper next;
70
71 public ValueNode() { this(0); }
72 public ValueNode(int v) { value = v; }
73 }
Tatu9fc6eeb2012-02-07 19:54:18 -080074
Tatu Saloranta7fbc6592012-02-09 22:51:09 -080075 // // Classes for external id use
76
77 @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="customId")
78 static class IdentifiableCustom
Tatu9fc6eeb2012-02-07 19:54:18 -080079 {
80 public int value;
Tatu9fc6eeb2012-02-07 19:54:18 -080081
Tatu Saloranta7fbc6592012-02-09 22:51:09 -080082 public int customId;
83
84 public IdentifiableCustom next;
85
86 public IdentifiableCustom() { this(-1, 0); }
87 public IdentifiableCustom(int i, int v) {
88 customId = i;
89 value = v;
90 }
Tatu9fc6eeb2012-02-07 19:54:18 -080091 }
Tatu Saloranta7fbc6592012-02-09 22:51:09 -080092
Tatu Saloranta2fd4ffd2012-02-17 22:40:13 -080093 static class IdWrapperExt
94 {
95 @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class,
96 property="customId")
97 public ValueNodeExt node;
98
99 public IdWrapperExt() { }
100 public IdWrapperExt(int v) {
101 node = new ValueNodeExt(v);
102 }
103 }
104
105 static class ValueNodeExt
106 {
107 public int value;
108 private int customId;
109 public IdWrapperExt next;
110
111 public ValueNodeExt() { this(0); }
112 public ValueNodeExt(int v) { value = v; }
113
114 public void setCustomId(int i) {
115 customId = i;
116 }
117 }
118
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500119 static class MappedCompany {
120 public Map<Integer, Employee> employees;
121 }
122
123 static class ArrayCompany {
124 public Employee[] employees;
125 }
126
Pascal Gélinas0da9a302013-12-19 08:44:22 -0500127 static class ArrayBlockingQueueCompany {
128 public ArrayBlockingQueue<Employee> employees;
129 }
130
131 static class EnumMapCompany {
132 public EnumMap<FooEnum,Employee> employees;
133
134 static enum FooEnum {
135 A, B
136 }
137 }
138
Pascal Gélinas10f89072013-12-19 08:56:31 -0500139 static class DefensiveCompany {
140 public List<DefensiveEmployee> employees;
141
142 static class DefensiveEmployee extends Employee {
143
144 public void setReports(List<DefensiveEmployee> reports)
145 {
146 this.reports = new ArrayList<TestObjectId.Employee>(reports);
147 }
148 }
149 }
150
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500151 @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class)
152 static class AnySetterObjectId {
153 private Map<String, AnySetterObjectId> values = new HashMap<String, AnySetterObjectId>();
154
155 @JsonAnySetter
156 public void anySet(String field, AnySetterObjectId value) {
157 // Ensure that it is never called with null because of unresolved reference.
158 assertNotNull(value);
159 values.put(field, value);
160 }
161 }
162
Tatu Saloranta7fbc6592012-02-09 22:51:09 -0800163 private final ObjectMapper mapper = new ObjectMapper();
Tatu Salorantaeffb8fd2012-02-04 19:20:10 -0800164
165 /*
166 /*****************************************************
Tatu Saloranta7fbc6592012-02-09 22:51:09 -0800167 /* Unit tests, external id deserialization
Tatu Salorantaeffb8fd2012-02-04 19:20:10 -0800168 /*****************************************************
169 */
170
Tatu58077e92012-02-07 19:47:33 -0800171 private final static String EXP_SIMPLE_INT_CLASS = "{\"id\":1,\"value\":13,\"next\":1}";
Tatu Saloranta34a8adf2012-02-08 22:07:36 -0800172
Tatu58077e92012-02-07 19:47:33 -0800173 public void testSimpleDeserializationClass() throws Exception
Tatu Saloranta5c8f8102012-02-05 18:10:48 -0800174 {
175 // then bring back...
Tatu58077e92012-02-07 19:47:33 -0800176 Identifiable result = mapper.readValue(EXP_SIMPLE_INT_CLASS, Identifiable.class);
Tatu Salorantaeffb8fd2012-02-04 19:20:10 -0800177 assertEquals(13, result.value);
Tatu Salorantaeffb8fd2012-02-04 19:20:10 -0800178 assertSame(result, result.next);
179 }
Tatu58077e92012-02-07 19:47:33 -0800180
Tatu9fc6eeb2012-02-07 19:54:18 -0800181 public void testSimpleUUIDForClassRoundTrip() throws Exception
182 {
183 UUIDNode root = new UUIDNode(1);
184 UUIDNode child1 = new UUIDNode(2);
185 UUIDNode child2 = new UUIDNode(3);
186 root.first = child1;
187 root.second = child2;
188 child1.parent = root;
189 child2.parent = root;
190 child1.first = child2;
191
192 String json = mapper.writeValueAsString(root);
193
194 // and should come back the same too...
195 UUIDNode result = mapper.readValue(json, UUIDNode.class);
196 assertEquals(1, result.value);
197 UUIDNode result2 = result.first;
198 UUIDNode result3 = result.second;
199 assertNotNull(result2);
200 assertNotNull(result3);
201 assertEquals(2, result2.value);
202 assertEquals(3, result3.value);
203
204 assertSame(result, result2.parent);
205 assertSame(result, result3.parent);
206 assertSame(result3, result2.first);
207 }
Tatu58077e92012-02-07 19:47:33 -0800208
209 // Bit more complex, due to extra wrapping etc:
210 private final static String EXP_SIMPLE_INT_PROP = "{\"node\":{\"@id\":1,\"value\":7,\"next\":{\"node\":1}}}";
Tatu58077e92012-02-07 19:47:33 -0800211
212 public void testSimpleDeserializationProperty() throws Exception
213 {
Tatu58077e92012-02-07 19:47:33 -0800214 IdWrapper result = mapper.readValue(EXP_SIMPLE_INT_PROP, IdWrapper.class);
215 assertEquals(7, result.node.value);
Tatu Saloranta34a8adf2012-02-08 22:07:36 -0800216 assertSame(result.node, result.node.next.node);
Tatu58077e92012-02-07 19:47:33 -0800217 }
Tatu Saloranta7fbc6592012-02-09 22:51:09 -0800218
Tatu Saloranta32fcb5d2012-02-26 16:39:40 -0800219 // Another test to ensure ordering is not required (i.e. can do front references)
220 public void testSimpleDeserWithForwardRefs() throws Exception
221 {
222 IdWrapper result = mapper.readValue("{\"node\":{\"value\":7,\"next\":{\"node\":1}, \"@id\":1}}"
223 ,IdWrapper.class);
224 assertEquals(7, result.node.value);
225 assertSame(result.node, result.node.next.node);
226 }
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500227
Pascal Gélinas6cbeaf42013-12-19 08:42:54 -0500228 public void testForwardReference()
229 throws Exception
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500230 {
231 String json = "{\"employees\":["
232 + "{\"id\":1,\"name\":\"First\",\"manager\":2,\"reports\":[]},"
233 + "{\"id\":2,\"name\":\"Second\",\"manager\":null,\"reports\":[1]}"
234 + "]}";
235 Company company = mapper.readValue(json, Company.class);
236 assertEquals(2, company.employees.size());
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500237 Employee firstEmployee = company.employees.get(0);
238 Employee secondEmployee = company.employees.get(1);
239 assertEquals(1, firstEmployee.id);
240 assertEquals(2, secondEmployee.id);
241 assertEquals(secondEmployee, firstEmployee.manager); // Ensure that forward reference was properly resolved.
242 assertEquals(firstEmployee, secondEmployee.reports.get(0)); // And that back reference is also properly resolved.
243 }
244
Pascal Gélinas6cbeaf42013-12-19 08:42:54 -0500245 public void testForwardReferenceInCollection()
246 throws Exception
247 {
248 String json = "{\"employees\":["
249 + "{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
250 + "{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}"
251 + "]}";
252 Company company = mapper.readValue(json, Company.class);
253 assertEquals(2, company.employees.size());
254 Employee firstEmployee = company.employees.get(0);
255 Employee secondEmployee = company.employees.get(1);
256 assertEmployees(firstEmployee, secondEmployee);
257 }
258
259 public void testForwardReferenceInMap()
260 throws Exception
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500261 {
262 String json = "{\"employees\":{"
Pascal Gélinas6cbeaf42013-12-19 08:42:54 -0500263 + "\"1\":{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500264 + "\"2\": 2,"
Pascal Gélinas6cbeaf42013-12-19 08:42:54 -0500265 + "\"3\":{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}"
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500266 + "}}";
267 MappedCompany company = mapper.readValue(json, MappedCompany.class);
268 assertEquals(3, company.employees.size());
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500269 Employee firstEmployee = company.employees.get(1);
270 Employee secondEmployee = company.employees.get(3);
Pascal Gélinas6cbeaf42013-12-19 08:42:54 -0500271 assertEmployees(firstEmployee, secondEmployee);
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500272 }
273
Pascal Gélinas6cbeaf42013-12-19 08:42:54 -0500274 public void testForwardReferenceInArray()
275 throws Exception
276 {
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500277 String json = "{\"employees\":["
278 + "{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
Pascal Gélinas6cbeaf42013-12-19 08:42:54 -0500279 + "2,"
280 +"{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}"
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500281 + "]}";
282 ArrayCompany company = mapper.readValue(json, ArrayCompany.class);
283 assertEquals(3, company.employees.length);
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500284 Employee firstEmployee = company.employees[0];
285 Employee secondEmployee = company.employees[1];
Pascal Gélinas6cbeaf42013-12-19 08:42:54 -0500286 assertEmployees(firstEmployee, secondEmployee);
287 }
288
Pascal Gélinas0da9a302013-12-19 08:44:22 -0500289 // Do a specific test for ArrayBlockingQueue since it has its own deser.
290 public void testForwardReferenceInQueue()
291 throws Exception
292 {
293 String json = "{\"employees\":["
294 + "{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
295 + "2,"
296 +"{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}"
297 + "]}";
298 ArrayBlockingQueueCompany company = mapper.readValue(json, ArrayBlockingQueueCompany.class);
299 assertEquals(3, company.employees.size());
300 Employee firstEmployee = company.employees.take();
301 Employee secondEmployee = company.employees.take();
302 assertEmployees(firstEmployee, secondEmployee);
303 }
304
305 public void testForwardReferenceInEnumMap()
306 throws Exception
307 {
308 String json = "{\"employees\":{"
309 + "\"A\":{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
310 + "\"B\": 2,"
311 + "\"C\":{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}"
312 + "}}";
313 EnumMapCompany company = mapper.readValue(json, EnumMapCompany.class);
314 assertEquals(3, company.employees.size());
315 Employee firstEmployee = company.employees.get(FooEnum.A);
316 Employee secondEmployee = company.employees.get(FooEnum.B);
317 assertEmployees(firstEmployee, secondEmployee);
318 }
319
Pascal Gélinas10f89072013-12-19 08:56:31 -0500320 public void testForwardReferenceWithDefensiveCopy()
321 throws Exception
322 {
323 String json = "{\"employees\":[" + "{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
324 + "{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}" + "]}";
325 DefensiveCompany company = mapper.readValue(json, DefensiveCompany.class);
326 assertEquals(2, company.employees.size());
327 Employee firstEmployee = company.employees.get(0);
328 Employee secondEmployee = company.employees.get(1);
329 assertEmployees(firstEmployee, secondEmployee);
330 }
331
Pascal Gélinas6cbeaf42013-12-19 08:42:54 -0500332 private void assertEmployees(Employee firstEmployee, Employee secondEmployee)
333 {
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500334 assertEquals(1, firstEmployee.id);
335 assertEquals(2, secondEmployee.id);
Pascal Gélinas10f89072013-12-19 08:56:31 -0500336 assertEquals(1, firstEmployee.reports.size());
Pascal Gélinasa0e8d5d2013-12-12 09:43:17 -0500337 assertSame(secondEmployee, firstEmployee.reports.get(0)); // Ensure that forward reference was properly resolved and in order.
338 assertSame(firstEmployee, secondEmployee.manager); // And that back reference is also properly resolved.
339 }
340
341 public void testForwardReferenceAnySetterCombo() throws Exception {
342 String json = "{\"@id\":1, \"foo\":2, \"bar\":{\"@id\":2, \"foo\":1}}";
343 AnySetterObjectId value = mapper.readValue(json, AnySetterObjectId.class);
344 assertSame(value.values.get("bar"), value.values.get("foo"));
345 }
346
Pascal Gélinas096e02b2013-12-18 17:21:42 -0500347 public void testUnresolvedForwardReference()
348 throws Exception
349 {
Pascal Gélinas1743f002013-12-18 17:38:17 -0500350 String json = "{\"employees\":["
351 + "{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[3]},"
352 + "{\"id\":2,\"name\":\"Second\",\"manager\":3,\"reports\":[]}"
353 + "]}";
Pascal Gélinas096e02b2013-12-18 17:21:42 -0500354 try {
355 mapper.readValue(json, Company.class);
356 fail("Should have thrown.");
357 } catch (UnresolvedForwardReference exception) {
358 // Expected
Pascal Gélinas3a92b122013-12-19 08:43:54 -0500359 List<UnresolvedId> unresolvedIds = exception.getUnresolvedIds();
360 assertEquals(2, unresolvedIds.size());
361 UnresolvedId firstUnresolvedId = unresolvedIds.get(0);
362 assertEquals(3, firstUnresolvedId.getId());
363 assertEquals(Employee.class, firstUnresolvedId.getType());
364 UnresolvedId secondUnresolvedId = unresolvedIds.get(1);
365 assertEquals(firstUnresolvedId.getId(), secondUnresolvedId.getId());
366 assertEquals(firstUnresolvedId.getType(), secondUnresolvedId.getType());
Pascal Gélinas096e02b2013-12-18 17:21:42 -0500367 }
368 }
369
Pascal Gélinas1743f002013-12-18 17:38:17 -0500370 public void testKeepCollectionOrdering()
371 throws Exception
372 {
373 String json = "{\"employees\":[2,1,"
Pascal Gélinas6cbeaf42013-12-19 08:42:54 -0500374 + "{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
375 + "{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}"
Pascal Gélinas1743f002013-12-18 17:38:17 -0500376 + "]}";
377 Company company = mapper.readValue(json, Company.class);
378 assertEquals(4, company.employees.size());
379 // Deser must keep object ordering.
380 Employee firstEmployee = company.employees.get(1);
381 Employee secondEmployee = company.employees.get(0);
Pascal Gélinas6cbeaf42013-12-19 08:42:54 -0500382 assertSame(firstEmployee, company.employees.get(2));
383 assertSame(secondEmployee, company.employees.get(3));
384 assertEmployees(firstEmployee, secondEmployee);
Pascal Gélinas1743f002013-12-18 17:38:17 -0500385 }
386
387 public void testKeepMapOrdering()
388 throws Exception
389 {
390 String json = "{\"employees\":{"
391 + "\"1\":2, \"2\":1,"
Pascal Gélinas6cbeaf42013-12-19 08:42:54 -0500392 + "\"3\":{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
393 + "\"4\":{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}"
Pascal Gélinas1743f002013-12-18 17:38:17 -0500394 + "}}";
395 MappedCompany company = mapper.readValue(json, MappedCompany.class);
396 assertEquals(4, company.employees.size());
397 Employee firstEmployee = company.employees.get(2);
398 Employee secondEmployee = company.employees.get(1);
Pascal Gélinas6cbeaf42013-12-19 08:42:54 -0500399 assertEmployees(firstEmployee, secondEmployee);
400 // Deser must keep object ordering. Not sure if it's really important for maps,
401 // but since default map is LinkedHashMap might as well ensure it does...
Pascal Gélinas1743f002013-12-18 17:38:17 -0500402 Iterator<Entry<Integer,Employee>> iterator = company.employees.entrySet().iterator();
Pascal Gélinas6cbeaf42013-12-19 08:42:54 -0500403 assertSame(secondEmployee, iterator.next().getValue());
404 assertSame(firstEmployee, iterator.next().getValue());
405 assertSame(firstEmployee, iterator.next().getValue());
406 assertSame(secondEmployee, iterator.next().getValue());
Pascal Gélinas1743f002013-12-18 17:38:17 -0500407 }
408
Tatu Saloranta7fbc6592012-02-09 22:51:09 -0800409 /*
410 /*****************************************************
411 /* Unit tests, custom (property-based) id deserialization
412 /*****************************************************
413 */
414
415 private final static String EXP_CUSTOM_VIA_CLASS = "{\"customId\":123,\"value\":-900,\"next\":123}";
416
417 public void testCustomDeserializationClass() throws Exception
418 {
419 // then bring back...
420 IdentifiableCustom result = mapper.readValue(EXP_CUSTOM_VIA_CLASS, IdentifiableCustom.class);
421 assertEquals(-900, result.value);
422 assertSame(result, result.next);
423 }
424
Tatu Saloranta2fd4ffd2012-02-17 22:40:13 -0800425 private final static String EXP_CUSTOM_VIA_PROP = "{\"node\":{\"customId\":3,\"value\":99,\"next\":{\"node\":3}}}";
426
427 public void testCustomDeserializationProperty() throws Exception
428 {
429 // then bring back...
430 IdWrapperExt result = mapper.readValue(EXP_CUSTOM_VIA_PROP, IdWrapperExt.class);
431 assertEquals(99, result.node.value);
432 assertSame(result.node, result.node.next.node);
433 assertEquals(3, result.node.customId);
434 }
Tatu Salorantaeffb8fd2012-02-04 19:20:10 -0800435}