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