• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.fasterxml.jackson.databind.objectid;
2 
3 import java.util.HashMap;
4 import java.util.Iterator;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.Map.Entry;
8 
9 import com.fasterxml.jackson.annotation.JsonAnySetter;
10 import com.fasterxml.jackson.annotation.JsonIdentityInfo;
11 import com.fasterxml.jackson.annotation.JsonIdentityReference;
12 import com.fasterxml.jackson.annotation.ObjectIdGenerator.IdKey;
13 import com.fasterxml.jackson.annotation.ObjectIdGenerators;
14 import com.fasterxml.jackson.annotation.ObjectIdResolver;
15 import com.fasterxml.jackson.databind.BaseMapTest;
16 import com.fasterxml.jackson.databind.DeserializationContext;
17 import com.fasterxml.jackson.databind.DeserializationFeature;
18 import com.fasterxml.jackson.databind.ObjectMapper;
19 import com.fasterxml.jackson.databind.cfg.ContextAttributes;
20 import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference;
21 import com.fasterxml.jackson.databind.deser.UnresolvedId;
22 import com.fasterxml.jackson.databind.objectid.TestObjectId.Company;
23 import com.fasterxml.jackson.databind.objectid.TestObjectId.Employee;
24 
25 /**
26  * Unit test to verify handling of Object Id deserialization
27  */
28 public class TestObjectIdDeserialization extends BaseMapTest
29 {
30     private static final String POOL_KEY = "POOL";
31 
32     // // Classes for external id use
33 
34     @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="id")
35     static class Identifiable
36     {
37         public int value;
38 
39         public Identifiable next;
40 
Identifiable()41         public Identifiable() { this(0); }
Identifiable(int v)42         public Identifiable(int v) {
43             value = v;
44         }
45     }
46 
47     @JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class, property="#")
48     static class UUIDNode
49     {
50         public int value;
51         public UUIDNode parent;
52         public UUIDNode first;
53         public UUIDNode second;
54 
UUIDNode()55         public UUIDNode() { this(0); }
UUIDNode(int v)56         public UUIDNode(int v) { value = v; }
57     }
58 
59     // // Classes for external id from property annotations:
60 
61     static class IdWrapper
62     {
63         @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
64         public ValueNode node;
65 
IdWrapper()66         public IdWrapper() { }
IdWrapper(int v)67         public IdWrapper(int v) {
68             node = new ValueNode(v);
69         }
70     }
71 
72     static class ValueNode {
73         public int value;
74         public IdWrapper next;
75 
ValueNode()76         public ValueNode() { this(0); }
ValueNode(int v)77         public ValueNode(int v) { value = v; }
78     }
79 
80     // // Classes for external id use
81 
82     @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="customId")
83     static class IdentifiableCustom
84     {
85         public int value;
86 
87         public int customId;
88 
89         public IdentifiableCustom next;
90 
IdentifiableCustom()91         public IdentifiableCustom() { this(-1, 0); }
IdentifiableCustom(int i, int v)92         public IdentifiableCustom(int i, int v) {
93             customId = i;
94             value = v;
95         }
96     }
97 
98     static class IdWrapperExt
99     {
100         @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class,
101         		property="customId")
102         public ValueNodeExt node;
103 
IdWrapperExt()104         public IdWrapperExt() { }
IdWrapperExt(int v)105         public IdWrapperExt(int v) {
106             node = new ValueNodeExt(v);
107         }
108     }
109 
110     static class ValueNodeExt
111     {
112         public int value;
113         protected int customId;
114         public IdWrapperExt next;
115 
ValueNodeExt()116         public ValueNodeExt() { this(0); }
ValueNodeExt(int v)117         public ValueNodeExt(int v) { value = v; }
118 
setCustomId(int i)119         public void setCustomId(int i) {
120         	customId = i;
121         }
122     }
123 
124     static class MappedCompany {
125         public Map<Integer, Employee> employees;
126     }
127 
128     @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class)
129     static class AnySetterObjectId {
130         protected Map<String, AnySetterObjectId> values = new HashMap<String, AnySetterObjectId>();
131 
132         @JsonAnySetter
anySet(String field, AnySetterObjectId value)133         public void anySet(String field, AnySetterObjectId value) {
134             // Ensure that it is never called with null because of unresolved reference.
135             assertNotNull(value);
136             values.put(field, value);
137         }
138     }
139 
140     static class CustomResolutionWrapper {
141         public List<WithCustomResolution> data;
142     }
143 
144     @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", resolver = PoolResolver.class)
145     @JsonIdentityReference(alwaysAsId = true) // #524
146     static class WithCustomResolution {
147         public int id;
148         public int data;
149 
WithCustomResolution(int id, int data)150         public WithCustomResolution(int id, int data)
151         {
152             this.id = id;
153             this.data = data;
154         }
155     }
156 
157     public static class PoolResolver implements ObjectIdResolver {
158         private Map<Object,WithCustomResolution> _pool;
159 
PoolResolver()160         public PoolResolver() {}
PoolResolver(Map<Object,WithCustomResolution> pool)161         public PoolResolver(Map<Object,WithCustomResolution> pool){ _pool = pool; }
162 
163         @Override
bindItem(IdKey id, Object pojo)164         public void bindItem(IdKey id, Object pojo){ }
165 
166         @Override
resolveId(IdKey id)167         public Object resolveId(IdKey id){ return _pool.get(id.key); }
168 
169         @Override
canUseFor(ObjectIdResolver resolverType)170         public boolean canUseFor(ObjectIdResolver resolverType)
171         {
172             return resolverType.getClass() == getClass() && _pool != null && !_pool.isEmpty();
173         }
174 
175         @Override
newForDeserialization(Object c)176         public ObjectIdResolver newForDeserialization(Object c)
177         {
178             DeserializationContext context = (DeserializationContext)c;
179             @SuppressWarnings("unchecked")
180             Map<Object,WithCustomResolution> pool = (Map<Object,WithCustomResolution>)context.getAttribute(POOL_KEY);
181             return new PoolResolver(pool);
182         }
183     }
184 
185     /*
186     /*****************************************************
187     /* Unit tests, external id deserialization
188     /*****************************************************
189      */
190 
191     private final ObjectMapper MAPPER = new ObjectMapper();
192 
193     private final static String EXP_SIMPLE_INT_CLASS = "{\"id\":1,\"value\":13,\"next\":1}";
194 
testSimpleDeserializationClass()195     public void testSimpleDeserializationClass() throws Exception
196     {
197         // then bring back...
198         Identifiable result = MAPPER.readValue(EXP_SIMPLE_INT_CLASS, Identifiable.class);
199         assertEquals(13, result.value);
200         assertSame(result, result.next);
201     }
202 
203     // Should be ok NOT to have Object id, as well
testMissingObjectId()204     public void testMissingObjectId() throws Exception
205     {
206         Identifiable result = MAPPER.readValue(aposToQuotes("{'value':28, 'next':{'value':29}}"),
207                 Identifiable.class);
208         assertNotNull(result);
209         assertEquals(28, result.value);
210         assertNotNull(result.next);
211         assertEquals(29, result.next.value);
212     }
213 
testSimpleUUIDForClassRoundTrip()214     public void testSimpleUUIDForClassRoundTrip() throws Exception
215     {
216         UUIDNode root = new UUIDNode(1);
217         UUIDNode child1 = new UUIDNode(2);
218         UUIDNode child2 = new UUIDNode(3);
219         root.first = child1;
220         root.second = child2;
221         child1.parent = root;
222         child2.parent = root;
223         child1.first = child2;
224 
225         String json = MAPPER.writeValueAsString(root);
226 
227         // and should come back the same too...
228         UUIDNode result = MAPPER.readValue(json, UUIDNode.class);
229         assertEquals(1, result.value);
230         UUIDNode result2 = result.first;
231         UUIDNode result3 = result.second;
232         assertNotNull(result2);
233         assertNotNull(result3);
234         assertEquals(2, result2.value);
235         assertEquals(3, result3.value);
236 
237         assertSame(result, result2.parent);
238         assertSame(result, result3.parent);
239         assertSame(result3, result2.first);
240     }
241 
242     // Bit more complex, due to extra wrapping etc:
243     private final static String EXP_SIMPLE_INT_PROP = "{\"node\":{\"@id\":1,\"value\":7,\"next\":{\"node\":1}}}";
244 
testSimpleDeserializationProperty()245     public void testSimpleDeserializationProperty() throws Exception
246     {
247         IdWrapper result = MAPPER.readValue(EXP_SIMPLE_INT_PROP, IdWrapper.class);
248         assertEquals(7, result.node.value);
249         assertSame(result.node, result.node.next.node);
250     }
251 
252     // Another test to ensure ordering is not required (i.e. can do front references)
testSimpleDeserWithForwardRefs()253     public void testSimpleDeserWithForwardRefs() throws Exception
254     {
255         IdWrapper result = MAPPER.readValue("{\"node\":{\"value\":7,\"next\":{\"node\":1}, \"@id\":1}}"
256                 ,IdWrapper.class);
257         assertEquals(7, result.node.value);
258         assertSame(result.node, result.node.next.node);
259     }
260 
testForwardReference()261     public void testForwardReference()
262         throws Exception
263     {
264         String json = "{\"employees\":["
265                       + "{\"id\":1,\"name\":\"First\",\"manager\":2,\"reports\":[]},"
266                       + "{\"id\":2,\"name\":\"Second\",\"manager\":null,\"reports\":[1]}"
267                       + "]}";
268         Company company = MAPPER.readValue(json, Company.class);
269         assertEquals(2, company.employees.size());
270         Employee firstEmployee = company.employees.get(0);
271         Employee secondEmployee = company.employees.get(1);
272         assertEquals(1, firstEmployee.id);
273         assertEquals(2, secondEmployee.id);
274         assertEquals(secondEmployee, firstEmployee.manager); // Ensure that forward reference was properly resolved.
275         assertEquals(firstEmployee, secondEmployee.reports.get(0)); // And that back reference is also properly resolved.
276     }
277 
testForwardReferenceInCollection()278     public void testForwardReferenceInCollection()
279         throws Exception
280     {
281         String json = "{\"employees\":["
282                       + "{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
283                       + "{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}"
284                       + "]}";
285         Company company = MAPPER.readValue(json, Company.class);
286         assertEquals(2, company.employees.size());
287         Employee firstEmployee = company.employees.get(0);
288         Employee secondEmployee = company.employees.get(1);
289         assertEmployees(firstEmployee, secondEmployee);
290     }
291 
testForwardReferenceInMap()292     public void testForwardReferenceInMap()
293         throws Exception
294     {
295         String json = "{\"employees\":{"
296                       + "\"1\":{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
297                       + "\"2\": 2,"
298                       + "\"3\":{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}"
299                       + "}}";
300         MappedCompany company = MAPPER.readValue(json, MappedCompany.class);
301         assertEquals(3, company.employees.size());
302         Employee firstEmployee = company.employees.get(1);
303         Employee secondEmployee = company.employees.get(3);
304         assertEmployees(firstEmployee, secondEmployee);
305     }
306 
assertEmployees(Employee firstEmployee, Employee secondEmployee)307     private void assertEmployees(Employee firstEmployee, Employee secondEmployee)
308     {
309         assertEquals(1, firstEmployee.id);
310         assertEquals(2, secondEmployee.id);
311         assertEquals(1, firstEmployee.reports.size());
312         assertSame(secondEmployee, firstEmployee.reports.get(0)); // Ensure that forward reference was properly resolved and in order.
313         assertSame(firstEmployee, secondEmployee.manager); // And that back reference is also properly resolved.
314     }
315 
testForwardReferenceAnySetterCombo()316     public void testForwardReferenceAnySetterCombo() throws Exception {
317         String json = "{\"@id\":1, \"foo\":2, \"bar\":{\"@id\":2, \"foo\":1}}";
318         AnySetterObjectId value = MAPPER.readValue(json, AnySetterObjectId.class);
319         assertSame(value.values.get("bar"), value.values.get("foo"));
320     }
321 
testUnresolvedForwardReference()322     public void testUnresolvedForwardReference()
323         throws Exception
324     {
325         String json = "{\"employees\":["
326                       + "{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[3]},"
327                       + "{\"id\":2,\"name\":\"Second\",\"manager\":3,\"reports\":[]}"
328                       + "]}";
329         try {
330             MAPPER.readValue(json, Company.class);
331             fail("Should have thrown.");
332         } catch (UnresolvedForwardReference exception) {
333             // Expected
334             List<UnresolvedId> unresolvedIds = exception.getUnresolvedIds();
335             assertEquals(2, unresolvedIds.size());
336             UnresolvedId firstUnresolvedId = unresolvedIds.get(0);
337             assertEquals(3, firstUnresolvedId.getId());
338             assertEquals(Employee.class, firstUnresolvedId.getType());
339             UnresolvedId secondUnresolvedId = unresolvedIds.get(1);
340             assertEquals(firstUnresolvedId.getId(), secondUnresolvedId.getId());
341             assertEquals(Employee.class, secondUnresolvedId.getType());
342         }
343     }
344 
345     // [databind#299]: Allow unresolved ids to become nulls
testUnresolvableAsNull()346     public void testUnresolvableAsNull() throws Exception
347     {
348         IdWrapper w = MAPPER.readerFor(IdWrapper.class)
349                 .without(DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS)
350                 .readValue(aposToQuotes("{'node':123}"));
351         assertNotNull(w);
352         assertNull(w.node);
353     }
354 
testKeepCollectionOrdering()355     public void testKeepCollectionOrdering() throws Exception
356     {
357         String json = "{\"employees\":[2,1,"
358                 + "{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
359                 + "{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}"
360                 + "]}";
361         Company company = MAPPER.readValue(json, Company.class);
362         assertEquals(4, company.employees.size());
363         // Deser must keep object ordering.
364         Employee firstEmployee = company.employees.get(1);
365         Employee secondEmployee = company.employees.get(0);
366         assertSame(firstEmployee, company.employees.get(2));
367         assertSame(secondEmployee, company.employees.get(3));
368         assertEmployees(firstEmployee, secondEmployee);
369     }
370 
testKeepMapOrdering()371     public void testKeepMapOrdering()
372         throws Exception
373     {
374         String json = "{\"employees\":{"
375                       + "\"1\":2, \"2\":1,"
376                       + "\"3\":{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
377                       + "\"4\":{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}"
378                       + "}}";
379         MappedCompany company = MAPPER.readValue(json, MappedCompany.class);
380         assertEquals(4, company.employees.size());
381         Employee firstEmployee = company.employees.get(2);
382         Employee secondEmployee = company.employees.get(1);
383         assertEmployees(firstEmployee, secondEmployee);
384         // Deser must keep object ordering. Not sure if it's really important for maps,
385         // but since default map is LinkedHashMap might as well ensure it does...
386         Iterator<Entry<Integer,Employee>> iterator = company.employees.entrySet().iterator();
387         assertSame(secondEmployee, iterator.next().getValue());
388         assertSame(firstEmployee, iterator.next().getValue());
389         assertSame(firstEmployee, iterator.next().getValue());
390         assertSame(secondEmployee, iterator.next().getValue());
391     }
392 
393     /*
394     /*****************************************************
395     /* Unit tests, custom (property-based) id deserialization
396     /*****************************************************
397      */
398 
399     private final static String EXP_CUSTOM_VIA_CLASS = "{\"customId\":123,\"value\":-900,\"next\":123}";
400 
testCustomDeserializationClass()401     public void testCustomDeserializationClass() throws Exception
402     {
403         // then bring back...
404         IdentifiableCustom result = MAPPER.readValue(EXP_CUSTOM_VIA_CLASS, IdentifiableCustom.class);
405         assertEquals(-900, result.value);
406         assertSame(result, result.next);
407     }
408 
409     private final static String EXP_CUSTOM_VIA_PROP = "{\"node\":{\"customId\":3,\"value\":99,\"next\":{\"node\":3}}}";
410 
testCustomDeserializationProperty()411     public void testCustomDeserializationProperty() throws Exception
412     {
413         // then bring back...
414         IdWrapperExt result = MAPPER.readValue(EXP_CUSTOM_VIA_PROP, IdWrapperExt.class);
415         assertEquals(99, result.node.value);
416         assertSame(result.node, result.node.next.node);
417         assertEquals(3, result.node.customId);
418     }
419 
420     /*
421     /*****************************************************
422     /* Unit tests, custom id resolver
423     /*****************************************************
424      */
425 
testCustomPoolResolver()426     public void testCustomPoolResolver() throws Exception
427     {
428         Map<Object,WithCustomResolution> pool = new HashMap<Object,WithCustomResolution>();
429         pool.put(1, new WithCustomResolution(1, 1));
430         pool.put(2, new WithCustomResolution(2, 2));
431         pool.put(3, new WithCustomResolution(3, 3));
432         pool.put(4, new WithCustomResolution(4, 4));
433         pool.put(5, new WithCustomResolution(5, 5));
434         ContextAttributes attrs = MAPPER.getDeserializationConfig().getAttributes().withSharedAttribute(POOL_KEY, pool);
435         String content = "{\"data\":[1,2,3,4,5]}";
436         CustomResolutionWrapper wrapper = MAPPER.readerFor(CustomResolutionWrapper.class).with(attrs).readValue(content);
437         assertFalse(wrapper.data.isEmpty());
438         for (WithCustomResolution ob : wrapper.data) {
439             assertSame(pool.get(ob.id), ob);
440         }
441     }
442 
443     /*
444     /*****************************************************
445     /* Unit tests, missing/null Object id [databind#742]
446     /*****************************************************
447      */
448 
449     /*
450     private final static String EXP_SIMPLE_INT_CLASS = "{\"id\":1,\"value\":13,\"next\":1}";
451     @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="id")
452     static class Identifiable
453     {
454         public int value;
455 
456         public Identifiable next;
457     }
458     */
459 
testNullObjectId()460     public void testNullObjectId() throws Exception
461     {
462         // Ok, so missing Object Id is ok, but so is null.
463 
464         Identifiable value = MAPPER.readValue
465                 (aposToQuotes("{'value':3, 'next':null, 'id':null}"), Identifiable.class);
466         assertNotNull(value);
467         assertEquals(3, value.value);
468     }
469 }
470