1 package com.fasterxml.jackson.databind.objectid; 2 3 import com.fasterxml.jackson.annotation.JsonIdentityInfo; 4 import com.fasterxml.jackson.annotation.JsonProperty; 5 import com.fasterxml.jackson.annotation.JsonTypeInfo; 6 import com.fasterxml.jackson.annotation.JsonTypeInfo.As; 7 import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; 8 import com.fasterxml.jackson.annotation.ObjectIdGenerator; 9 import com.fasterxml.jackson.core.JsonParser; 10 import com.fasterxml.jackson.databind.BaseMapTest; 11 import com.fasterxml.jackson.databind.DeserializationContext; 12 import com.fasterxml.jackson.databind.JsonDeserializer; 13 import com.fasterxml.jackson.databind.JsonMappingException; 14 import com.fasterxml.jackson.databind.JsonNode; 15 import com.fasterxml.jackson.databind.ObjectMapper; 16 import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 17 18 import java.io.IOException; 19 20 /** 21 * Unit test(s) for [databind#622], supporting non-scalar-Object-ids, 22 * to support things like JSOG. 23 */ 24 public class JSOGDeserialize622Test extends BaseMapTest 25 { 26 /** the key of the property that holds the ref */ 27 public static final String REF_KEY = "@ref"; 28 29 /** 30 * JSON input 31 */ 32 private static final String EXP_EXAMPLE_JSOG = aposToQuotes( 33 "{'@id':'1','foo':66,'next':{'"+REF_KEY+"':'1'}}"); 34 35 /** 36 * Customer IdGenerator 37 */ 38 static class JSOGGenerator extends ObjectIdGenerator<JSOGRef> { 39 40 private static final long serialVersionUID = 1L; 41 protected transient int _nextValue; 42 protected final Class<?> _scope; 43 JSOGGenerator()44 protected JSOGGenerator() { this(null, -1); } 45 JSOGGenerator(Class<?> scope, int nextValue)46 protected JSOGGenerator(Class<?> scope, int nextValue) { 47 _scope = scope; 48 _nextValue = nextValue; 49 } 50 51 @Override getScope()52 public Class<?> getScope() { 53 return _scope; 54 } 55 56 @Override canUseFor(ObjectIdGenerator<?> gen)57 public boolean canUseFor(ObjectIdGenerator<?> gen) { 58 return (gen.getClass() == getClass()) && (gen.getScope() == _scope); 59 } 60 61 @Override forScope(Class<?> scope)62 public ObjectIdGenerator<JSOGRef> forScope(Class<?> scope) { 63 return (_scope == scope) ? this : new JSOGGenerator(scope, _nextValue); 64 } 65 66 @Override newForSerialization(Object context)67 public ObjectIdGenerator<JSOGRef> newForSerialization(Object context) { 68 return new JSOGGenerator(_scope, 1); 69 } 70 71 @Override key(Object key)72 public com.fasterxml.jackson.annotation.ObjectIdGenerator.IdKey key(Object key) { 73 return new IdKey(getClass(), _scope, key); 74 } 75 76 // important: otherwise won't get proper handling 77 @Override maySerializeAsObject()78 public boolean maySerializeAsObject() { return true; } 79 80 // ditto: needed for handling Object-valued Object references 81 @Override isValidReferencePropertyName(String name, Object parser)82 public boolean isValidReferencePropertyName(String name, Object parser) { 83 return REF_KEY.equals(name); 84 } 85 86 @Override generateId(Object forPojo)87 public JSOGRef generateId(Object forPojo) { 88 int id = _nextValue; 89 ++_nextValue; 90 return new JSOGRef(id); 91 } 92 } 93 94 /** 95 * The reference deserializer 96 */ 97 static class JSOGRefDeserializer extends JsonDeserializer<JSOGRef> 98 { 99 @Override deserialize(JsonParser p, DeserializationContext ctx)100 public JSOGRef deserialize(JsonParser p, DeserializationContext ctx) throws IOException { 101 JsonNode node = p.readValueAsTree(); 102 if (node.isTextual()) { 103 return new JSOGRef(node.asInt()); 104 } 105 JsonNode n = node.get(REF_KEY); 106 if (n == null) { 107 throw new JsonMappingException(p, "Could not find key '"+REF_KEY 108 +"' from ("+node.getClass().getName()+"): "+node); 109 } 110 return new JSOGRef(n.asInt()); 111 } 112 } 113 114 /** 115 * The reference object 116 */ 117 @JsonDeserialize(using=JSOGRefDeserializer.class) 118 static class JSOGRef 119 { 120 @JsonProperty(REF_KEY) 121 public int ref; 122 JSOGRef()123 public JSOGRef() { } 124 JSOGRef(int val)125 public JSOGRef(int val) { 126 ref = val; 127 } 128 129 @Override toString()130 public String toString() { return "[JSOGRef#"+ref+"]"; } 131 132 @Override hashCode()133 public int hashCode() { 134 return ref; 135 } 136 137 @Override equals(Object other)138 public boolean equals(Object other) { 139 return (other instanceof JSOGRef) 140 && ((JSOGRef) other).ref == this.ref; 141 } 142 } 143 144 /** 145 * Example class using JSOGGenerator 146 */ 147 @JsonIdentityInfo(generator=JSOGGenerator.class, property="@id") 148 public static class IdentifiableExampleJSOG { 149 public int foo; 150 public IdentifiableExampleJSOG next; 151 IdentifiableExampleJSOG()152 protected IdentifiableExampleJSOG() { } IdentifiableExampleJSOG(int v)153 public IdentifiableExampleJSOG(int v) { 154 foo = v; 155 } 156 } 157 158 public static class JSOGWrapper { 159 public int value; 160 161 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 162 public Object jsog; 163 JSOGWrapper()164 JSOGWrapper() { } JSOGWrapper(int v)165 public JSOGWrapper(int v) { value = v; } 166 } 167 168 // For [databind#669] 169 170 @JsonIdentityInfo(generator=JSOGGenerator.class) 171 @JsonTypeInfo(use=Id.CLASS, include= As.PROPERTY, property="@class") 172 public static class Inner { 173 public String bar; 174 Inner()175 protected Inner() {} Inner(String bar)176 public Inner(String bar) { this.bar = bar; } 177 } 178 179 public static class SubInner extends Inner { 180 public String extra; 181 SubInner()182 protected SubInner() {} SubInner(String bar, String extra)183 public SubInner(String bar, String extra) { 184 super(bar); 185 this.extra = extra; 186 } 187 } 188 189 @JsonIdentityInfo(generator=JSOGGenerator.class) 190 public static class Outer { 191 public String foo; 192 public Inner inner1; 193 public Inner inner2; 194 } 195 196 /* 197 /********************************************************************** 198 /* Test methods 199 /********************************************************************** 200 */ 201 202 private final ObjectMapper MAPPER = new ObjectMapper(); 203 204 // Basic for [databind#622] testStructJSOGRef()205 public void testStructJSOGRef() throws Exception 206 { 207 IdentifiableExampleJSOG result = MAPPER.readValue(EXP_EXAMPLE_JSOG, 208 IdentifiableExampleJSOG.class); 209 assertEquals(66, result.foo); 210 assertSame(result, result.next); 211 } 212 213 // polymorphic alternative for [databind#622] testPolymorphicRoundTrip()214 public void testPolymorphicRoundTrip() throws Exception 215 { 216 JSOGWrapper w = new JSOGWrapper(15); 217 // create a nice little loop 218 IdentifiableExampleJSOG ex = new IdentifiableExampleJSOG(123); 219 ex.next = ex; 220 w.jsog = ex; 221 222 String json = MAPPER.writeValueAsString(w); 223 224 JSOGWrapper out = MAPPER.readValue(json, JSOGWrapper.class); 225 assertNotNull(out); 226 assertEquals(15, out.value); 227 assertTrue(out.jsog instanceof IdentifiableExampleJSOG); 228 IdentifiableExampleJSOG jsog = (IdentifiableExampleJSOG) out.jsog; 229 assertEquals(123, jsog.foo); 230 assertSame(jsog, jsog.next); 231 } 232 233 // polymorphic alternative for [databind#669] testAlterativePolymorphicRoundTrip669()234 public void testAlterativePolymorphicRoundTrip669() throws Exception 235 { 236 Outer outer = new Outer(); 237 outer.foo = "foo"; 238 outer.inner1 = outer.inner2 = new SubInner("bar", "extra"); 239 240 String jsog = MAPPER.writeValueAsString(outer); 241 242 Outer back = MAPPER.readValue(jsog, Outer.class); 243 244 assertSame(back.inner1, back.inner2); 245 } 246 } 247