• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.fasterxml.jackson.databind.objectid;
2 
3 import com.fasterxml.jackson.annotation.JsonIdentityInfo;
4 import com.fasterxml.jackson.annotation.JsonIdentityReference;
5 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
6 import com.fasterxml.jackson.annotation.ObjectIdGenerators;
7 import com.fasterxml.jackson.databind.*;
8 import com.fasterxml.jackson.databind.json.JsonMapper;
9 
10 /**
11  * Unit test to verify handling of Object Id deserialization
12  */
13 public class TestObjectIdSerialization extends BaseMapTest
14 {
15     @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="id")
16     static class Identifiable
17     {
18         public int value;
19 
20         public Identifiable next;
21 
Identifiable()22         public Identifiable() { this(0); }
Identifiable(int v)23         public Identifiable(int v) {
24             value = v;
25         }
26     }
27 
28     @JsonIdentityInfo(generator=ObjectIdGenerators.StringIdGenerator.class, property="id")
29     static class StringIdentifiable
30     {
31         public int value;
32 
33         public StringIdentifiable next;
34 
StringIdentifiable()35         public StringIdentifiable() { this(0); }
StringIdentifiable(int v)36         public StringIdentifiable(int v) {
37             value = v;
38         }
39     }
40 
41     @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="customId")
42     static class IdentifiableWithProp
43     {
44         public int value;
45 
46         // Property that contains Object Id to use
47         public int customId;
48 
49         public IdentifiableWithProp next;
50 
IdentifiableWithProp()51         public IdentifiableWithProp() { this(0, 0); }
IdentifiableWithProp(int id, int value)52         public IdentifiableWithProp(int id, int value) {
53             this.customId = id;
54             this.value = value;
55         }
56     }
57 
58     // For property reference, need another class:
59 
60     static class IdWrapper
61     {
62         @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
63         public ValueNode node;
64 
IdWrapper()65         public IdWrapper() { }
IdWrapper(int v)66         public IdWrapper(int v) {
67             node = new ValueNode(v);
68         }
69     }
70 
71     static class ValueNode {
72         public int value;
73         public IdWrapper next;
74 
ValueNode()75         public ValueNode() { this(0); }
ValueNode(int v)76         public ValueNode(int v) { value = v; }
77     }
78 
79     // Similarly for property-ref via property:
80 
81     protected static class IdWrapperCustom
82     {
83         @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
84         public ValueNodeCustom node;
85 
IdWrapperCustom()86         public IdWrapperCustom() { }
IdWrapperCustom(int id, int value)87         public IdWrapperCustom(int id, int value) {
88             node = new ValueNodeCustom(id, value);
89         }
90     }
91 
92     protected static class ValueNodeCustom {
93         public int value;
94         private int id;
95         public IdWrapperCustom next;
96 
getId()97         public int getId() { return id; }
98 
ValueNodeCustom()99         public ValueNodeCustom() { this(0, 0); }
ValueNodeCustom(int id, int value)100         public ValueNodeCustom(int id, int value) {
101             this.id = id;
102             this.value = value;
103         }
104     }
105 
106     @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="id")
107     static class AlwaysAsId
108     {
109         public int value;
110 
AlwaysAsId()111         public AlwaysAsId() { this(0); }
AlwaysAsId(int v)112         public AlwaysAsId(int v) {
113             value = v;
114         }
115     }
116 
117     // For [https://github.com/FasterXML/jackson-annotations/issues/4]
118     @JsonPropertyOrder(alphabetic=true)
119     static class AlwaysContainer
120     {
121         @JsonIdentityReference(alwaysAsId=true)
122         public AlwaysAsId a = new AlwaysAsId(13);
123 
124         @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="id")
125         @JsonIdentityReference(alwaysAsId=true)
126         public Value b = new Value();
127     }
128 
129     static class Value {
130         public int x = 3;
131     }
132 
133     @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
134     static class TreeNode
135     {
136         public int id;
137         public String name;
138 
139         @JsonIdentityReference(alwaysAsId=true)
140         public TreeNode parent;
141 
142         // children serialized with ids if need be
143         public TreeNode child;
144 
TreeNode()145         public TreeNode() { }
TreeNode(TreeNode p, int id, String name)146         public TreeNode(TreeNode p, int id, String name) {
147             parent = p;
148             this.id = id;
149             this.name = name;
150         }
151     }
152 
153     // // Let's also have one 'broken' test
154 
155     // no "id" property
156     @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
157     static class Broken
158     {
159         public int value;
160         public int customId;
161     }
162 
163     // [databind#370]
164     @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
165     public static class EmptyObject { }
166 
167     //for [databind#1150]
168     @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
169     static class IdentifiableStringId
170     {
171         public String id;
172         public int value;
173 
174         public Identifiable next;
175 
IdentifiableStringId()176         public IdentifiableStringId() { this(0); }
IdentifiableStringId(int v)177         public IdentifiableStringId(int v) {
178             value = v;
179         }
180     }
181 
182     /*
183     /*****************************************************
184     /* Unit tests, external id serialization
185     /*****************************************************
186      */
187 
188     private final static String EXP_SIMPLE_INT_CLASS = "{\"id\":1,\"next\":1,\"value\":13}";
189 
190     private final ObjectMapper MAPPER = objectMapper();
191 
testSimpleSerializationClass()192     public void testSimpleSerializationClass() throws Exception
193     {
194         Identifiable src = new Identifiable(13);
195         src.next = src;
196 
197         // First, serialize:
198         JsonMapper mapper = JsonMapper.builder().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY).build();
199         String json = mapper.writeValueAsString(src);
200         assertEquals(EXP_SIMPLE_INT_CLASS, json);
201 
202         // and ensure that state is cleared in-between as well:
203         json = mapper.writeValueAsString(src);
204         assertEquals(EXP_SIMPLE_INT_CLASS, json);
205     }
206 
207     // Bit more complex, due to extra wrapping etc:
208     private final static String EXP_SIMPLE_INT_PROP = "{\"node\":{\"@id\":1,\"next\":{\"node\":1},\"value\":7}}";
209 
testSimpleSerializationProperty()210     public void testSimpleSerializationProperty() throws Exception
211     {
212         IdWrapper src = new IdWrapper(7);
213         src.node.next = src;
214 
215         // First, serialize:
216         JsonMapper mapper = JsonMapper.builder().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY).build();
217         String json = mapper.writeValueAsString(src);
218         assertEquals(EXP_SIMPLE_INT_PROP, json);
219         // and second time too, for a good measure
220         json = mapper.writeValueAsString(src);
221         assertEquals(EXP_SIMPLE_INT_PROP, json);
222     }
223 
224     // [databind#370]
testEmptyObjectWithId()225     public void testEmptyObjectWithId() throws Exception
226     {
227         final ObjectMapper mapper = new ObjectMapper();
228         String json = mapper.writeValueAsString(new EmptyObject());
229         assertEquals(aposToQuotes("{'@id':1}"), json);
230     }
231 
testSerializeWithOpaqueStringId()232     public void testSerializeWithOpaqueStringId() throws Exception
233     {
234         StringIdentifiable ob1 = new StringIdentifiable(12);
235         StringIdentifiable ob2 = new StringIdentifiable(34);
236         ob1.next = ob2;
237         ob2.next = ob1;
238 
239         // first just verify we get some output
240         String json = MAPPER.writeValueAsString(ob1);
241         assertNotNull(json);
242 
243         // then get them back
244         StringIdentifiable output = MAPPER.readValue(json, StringIdentifiable.class);
245         assertNotNull(output);
246         assertEquals(12, output.value);
247         assertNotNull(output.next);
248         assertEquals(34, output.next.value);
249         assertSame(output.next.next, output);
250 
251         String json2 = aposToQuotes("{'id':'foobar','value':3, 'next':{'id':'barf','value':5,'next':'foobar'}}");
252         output = MAPPER.readValue(json2, StringIdentifiable.class);
253         assertNotNull(output);
254         assertEquals(3, output.value);
255         assertNotNull(output.next);
256         assertEquals(5, output.next.value);
257         assertSame(output.next.next, output);
258     }
259 
260     /*
261     /*****************************************************
262     /* Unit tests, custom (property) id serialization
263     /*****************************************************
264      */
265 
266     private final static String EXP_CUSTOM_PROP = "{\"customId\":123,\"next\":123,\"value\":-19}";
267     // Test for verifying that custom
testCustomPropertyForClass()268     public void testCustomPropertyForClass() throws Exception
269     {
270         IdentifiableWithProp src = new IdentifiableWithProp(123, -19);
271         src.next = src;
272 
273         JsonMapper mapper =  JsonMapper.builder().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY).build();
274         // First, serialize:
275         String json = mapper.writeValueAsString(src);
276         assertEquals(EXP_CUSTOM_PROP, json);
277 
278         // and ensure that state is cleared in-between as well:
279         json = mapper.writeValueAsString(src);
280         assertEquals(EXP_CUSTOM_PROP, json);
281     }
282 
283     private final static String EXP_CUSTOM_PROP_VIA_REF = "{\"node\":{\"id\":123,\"next\":{\"node\":123},\"value\":7}}";
284     // Test for verifying that custom
testCustomPropertyViaProperty()285     public void testCustomPropertyViaProperty() throws Exception
286     {
287         IdWrapperCustom src = new IdWrapperCustom(123, 7);
288         src.node.next = src;
289         JsonMapper mapper = JsonMapper.builder().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY).build();
290         // First, serialize:
291         String json = mapper.writeValueAsString(src);
292         assertEquals(EXP_CUSTOM_PROP_VIA_REF, json);
293         // and second time too, for a good measure
294         json = mapper.writeValueAsString(src);
295         assertEquals(EXP_CUSTOM_PROP_VIA_REF, json);
296     }
297 
testAlwaysAsId()298     public void testAlwaysAsId() throws Exception
299     {
300         String json = MAPPER.writeValueAsString(new AlwaysContainer());
301         assertEquals("{\"a\":1,\"b\":2}", json);
302     }
303 
testAlwaysIdForTree()304     public void testAlwaysIdForTree() throws Exception
305     {
306         TreeNode root = new TreeNode(null, 1, "root");
307         TreeNode leaf = new TreeNode(root, 2, "leaf");
308         root.child = leaf;
309         JsonMapper mapper = JsonMapper.builder().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY).build();
310         String json = mapper.writeValueAsString(root);
311         assertEquals("{\"id\":1,\"child\":"
312                 +"{\"id\":2,\"child\":null,\"name\":\"leaf\",\"parent\":1},\"name\":\"root\",\"parent\":null}",
313                 json);
314 
315     }
316 
317     //for [databind#1150]
testNullStringPropertyId()318     public void testNullStringPropertyId() throws Exception
319     {
320         IdentifiableStringId value = MAPPER.readValue
321                 (aposToQuotes("{'value':3, 'next':null, 'id':null}"), IdentifiableStringId.class);
322         assertNotNull(value);
323         assertEquals(3, value.value);
324     }
325 
326     /*
327     /*****************************************************
328     /* Unit tests, error handling
329     /*****************************************************
330      */
331 
testInvalidProp()332     public void testInvalidProp() throws Exception
333     {
334         try {
335             MAPPER.writeValueAsString(new Broken());
336             fail("Should have thrown an exception");
337         } catch (JsonMappingException e) {
338             verifyException(e, "cannot find property with name 'id'");
339         }
340     }
341 }
342