Skip to content

Commit 05a639e

Browse files
authored
[DE-636] Non-interfering Jackson annotations (#513)
* removed JacksonAnnotationsInside from com.arangodb.serde.jackson.Key annotation * handle Jackson Serde annotations with custom JacksonAnnotationIntrospector
1 parent 7ad7d5f commit 05a639e

File tree

8 files changed

+298
-35
lines changed

8 files changed

+298
-35
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
package com.arangodb.serde;
2+
3+
import com.arangodb.serde.jackson.*;
4+
import com.arangodb.serde.jackson.json.JacksonJsonSerdeProvider;
5+
import com.fasterxml.jackson.databind.JsonNode;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
8+
import com.fasterxml.jackson.databind.node.ObjectNode;
9+
import org.junit.jupiter.api.BeforeEach;
10+
import org.junit.jupiter.api.Test;
11+
12+
import java.io.IOException;
13+
import java.util.UUID;
14+
import java.util.function.BiFunction;
15+
import java.util.function.Function;
16+
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
19+
class JacksonInterferenceTest {
20+
21+
private final ObjectMapper mapper = new ObjectMapper();
22+
private final ArangoSerde serde = new JacksonJsonSerdeProvider().create();
23+
24+
private FooField fooField;
25+
private FooProp fooProp;
26+
27+
static class FooField {
28+
@Id
29+
public String myId;
30+
@Key
31+
public String myKey;
32+
@Rev
33+
public String myRev;
34+
@From
35+
public String myFrom;
36+
@To
37+
public String myTo;
38+
}
39+
40+
static class FooProp {
41+
public String myId;
42+
public String myKey;
43+
public String myRev;
44+
public String myFrom;
45+
public String myTo;
46+
47+
@Id
48+
public String getMyId() {
49+
return myId;
50+
}
51+
52+
@Id
53+
public void setMyId(String myId) {
54+
this.myId = myId;
55+
}
56+
57+
@Key
58+
public String getMyKey() {
59+
return myKey;
60+
}
61+
62+
@Key
63+
public void setMyKey(String myKey) {
64+
this.myKey = myKey;
65+
}
66+
67+
@Rev
68+
public String getMyRev() {
69+
return myRev;
70+
}
71+
72+
@Rev
73+
public void setMyRev(String myRev) {
74+
this.myRev = myRev;
75+
}
76+
77+
@From
78+
public String getMyFrom() {
79+
return myFrom;
80+
}
81+
82+
@From
83+
public void setMyFrom(String myFrom) {
84+
this.myFrom = myFrom;
85+
}
86+
87+
@To
88+
public String getMyTo() {
89+
return myTo;
90+
}
91+
92+
@To
93+
public void setMyTo(String myTo) {
94+
this.myTo = myTo;
95+
}
96+
}
97+
98+
@BeforeEach
99+
void init() {
100+
fooField = new FooField();
101+
fooProp = new FooProp();
102+
103+
fooField.myId = "myId";
104+
fooProp.myId = "myId";
105+
106+
fooField.myKey = "myKey";
107+
fooProp.myKey = "myKey";
108+
109+
fooField.myRev = "myRev";
110+
fooProp.myRev = "myRev";
111+
112+
fooField.myFrom = "myFrom";
113+
fooProp.myFrom = "myFrom";
114+
115+
fooField.myTo = "myTo";
116+
fooProp.myTo = "myTo";
117+
}
118+
119+
@Test
120+
void serializeField() {
121+
// id
122+
testSerialize(fooField, "myId", fooField.myId, this::jacksonSerialize);
123+
testSerialize(fooField, "_id", fooField.myId, this::serdeSerialize);
124+
// key
125+
testSerialize(fooField, "myKey", fooField.myKey, this::jacksonSerialize);
126+
testSerialize(fooField, "_key", fooField.myKey, this::serdeSerialize);
127+
// rev
128+
testSerialize(fooField, "myRev", fooField.myRev, this::jacksonSerialize);
129+
testSerialize(fooField, "_rev", fooField.myRev, this::serdeSerialize);
130+
// from
131+
testSerialize(fooField, "myFrom", fooField.myFrom, this::jacksonSerialize);
132+
testSerialize(fooField, "_from", fooField.myFrom, this::serdeSerialize);
133+
// to
134+
testSerialize(fooField, "myTo", fooField.myTo, this::jacksonSerialize);
135+
testSerialize(fooField, "_to", fooField.myTo, this::serdeSerialize);
136+
}
137+
138+
@Test
139+
void serializeProp() {
140+
// id
141+
testSerialize(fooProp, "myId", fooProp.myId, this::jacksonSerialize);
142+
testSerialize(fooProp, "_id", fooProp.myId, this::serdeSerialize);
143+
// key
144+
testSerialize(fooProp, "myKey", fooProp.myKey, this::jacksonSerialize);
145+
testSerialize(fooProp, "_key", fooProp.myKey, this::serdeSerialize);
146+
// rev
147+
testSerialize(fooProp, "myRev", fooProp.myRev, this::jacksonSerialize);
148+
testSerialize(fooProp, "_rev", fooProp.myRev, this::serdeSerialize);
149+
// from
150+
testSerialize(fooProp, "myFrom", fooProp.myFrom, this::jacksonSerialize);
151+
testSerialize(fooProp, "_from", fooProp.myFrom, this::serdeSerialize);
152+
// to
153+
testSerialize(fooProp, "myTo", fooProp.myTo, this::jacksonSerialize);
154+
testSerialize(fooProp, "_to", fooProp.myTo, this::serdeSerialize);
155+
}
156+
157+
@Test
158+
void deserializeField() throws IOException {
159+
// id
160+
testDeserialize("myId", FooField.class, foo -> foo.myId, this::jacksonDeserialize);
161+
testDeserialize("_id", FooField.class, foo -> foo.myId, this::serdeDeserialize);
162+
// key
163+
testDeserialize("myKey", FooField.class, foo -> foo.myKey, this::jacksonDeserialize);
164+
testDeserialize("_key", FooField.class, foo -> foo.myKey, this::serdeDeserialize);
165+
// rev
166+
testDeserialize("myRev", FooField.class, foo -> foo.myRev, this::jacksonDeserialize);
167+
testDeserialize("_rev", FooField.class, foo -> foo.myRev, this::serdeDeserialize);
168+
// from
169+
testDeserialize("myFrom", FooField.class, foo -> foo.myFrom, this::jacksonDeserialize);
170+
testDeserialize("_from", FooField.class, foo -> foo.myFrom, this::serdeDeserialize);
171+
// to
172+
testDeserialize("myTo", FooField.class, foo -> foo.myTo, this::jacksonDeserialize);
173+
testDeserialize("_to", FooField.class, foo -> foo.myTo, this::serdeDeserialize);
174+
}
175+
176+
@Test
177+
void deserializeProp() throws IOException {
178+
// id
179+
testDeserialize("myId", FooProp.class, FooProp::getMyId, this::jacksonDeserialize);
180+
testDeserialize("_id", FooProp.class, FooProp::getMyId, this::serdeDeserialize);
181+
// key
182+
testDeserialize("myKey", FooProp.class, FooProp::getMyKey, this::jacksonDeserialize);
183+
testDeserialize("_key", FooProp.class, FooProp::getMyKey, this::serdeDeserialize);
184+
// rev
185+
testDeserialize("myRev", FooProp.class, FooProp::getMyRev, this::jacksonDeserialize);
186+
testDeserialize("_rev", FooProp.class, FooProp::getMyRev, this::serdeDeserialize);
187+
// from
188+
testDeserialize("myFrom", FooProp.class, FooProp::getMyFrom, this::jacksonDeserialize);
189+
testDeserialize("_from", FooProp.class, FooProp::getMyFrom, this::serdeDeserialize);
190+
// to
191+
testDeserialize("myTo", FooProp.class, FooProp::getMyTo, this::jacksonDeserialize);
192+
testDeserialize("_to", FooProp.class, FooProp::getMyTo, this::serdeDeserialize);
193+
}
194+
195+
void testSerialize(Object data, String fieldName, String expectedValue, Function<Object, JsonNode> serializer) {
196+
JsonNode jn = serializer.apply(data).get(fieldName);
197+
assertThat(jn).isNotNull();
198+
assertThat(jn.textValue()).isEqualTo(expectedValue);
199+
}
200+
201+
<T> void testDeserialize(String fieldName, Class<T> clazz, Function<T, String> getter,
202+
BiFunction<byte[], Class<T>, T> deserializer) throws IOException {
203+
String fieldValue = UUID.randomUUID().toString();
204+
ObjectNode on = JsonNodeFactory.instance.objectNode().put(fieldName, fieldValue);
205+
byte[] bytes = mapper.writeValueAsBytes(on);
206+
T deser = deserializer.apply(bytes, clazz);
207+
assertThat(getter.apply(deser)).isEqualTo(fieldValue);
208+
}
209+
210+
private JsonNode jacksonSerialize(Object data) {
211+
try {
212+
return mapper.readTree(mapper.writeValueAsBytes(data));
213+
} catch (IOException e) {
214+
throw new RuntimeException(e);
215+
}
216+
}
217+
218+
private JsonNode serdeSerialize(Object data) {
219+
try {
220+
return mapper.readTree(serde.serialize(data));
221+
} catch (IOException e) {
222+
throw new RuntimeException(e);
223+
}
224+
}
225+
226+
private <T> T jacksonDeserialize(byte[] bytes, Class<T> clazz) {
227+
try {
228+
return mapper.readValue(bytes, clazz);
229+
} catch (IOException e) {
230+
throw new RuntimeException(e);
231+
}
232+
}
233+
234+
private <T> T serdeDeserialize(byte[] bytes, Class<T> clazz) {
235+
return serde.deserialize(bytes, clazz);
236+
}
237+
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package com.arangodb.serde.jackson;
22

3-
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
4-
import com.fasterxml.jackson.annotation.JsonInclude;
5-
import com.fasterxml.jackson.annotation.JsonProperty;
6-
73
import java.lang.annotation.ElementType;
84
import java.lang.annotation.Retention;
95
import java.lang.annotation.RetentionPolicy;
@@ -14,8 +10,5 @@
1410
*/
1511
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
1612
@Retention(RetentionPolicy.RUNTIME)
17-
@JacksonAnnotationsInside
18-
@JsonProperty("_from")
19-
@JsonInclude(JsonInclude.Include.NON_NULL)
2013
public @interface From {
2114
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package com.arangodb.serde.jackson;
22

3-
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
4-
import com.fasterxml.jackson.annotation.JsonInclude;
5-
import com.fasterxml.jackson.annotation.JsonProperty;
6-
73
import java.lang.annotation.ElementType;
84
import java.lang.annotation.Retention;
95
import java.lang.annotation.RetentionPolicy;
@@ -14,8 +10,5 @@
1410
*/
1511
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
1612
@Retention(RetentionPolicy.RUNTIME)
17-
@JacksonAnnotationsInside
18-
@JsonProperty("_id")
19-
@JsonInclude(JsonInclude.Include.NON_NULL)
2013
public @interface Id {
2114
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package com.arangodb.serde.jackson;
22

3-
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
4-
import com.fasterxml.jackson.annotation.JsonInclude;
5-
import com.fasterxml.jackson.annotation.JsonProperty;
6-
73
import java.lang.annotation.ElementType;
84
import java.lang.annotation.Retention;
95
import java.lang.annotation.RetentionPolicy;
@@ -14,8 +10,5 @@
1410
*/
1511
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
1612
@Retention(RetentionPolicy.RUNTIME)
17-
@JacksonAnnotationsInside
18-
@JsonProperty("_key")
19-
@JsonInclude(JsonInclude.Include.NON_NULL)
2013
public @interface Key {
2114
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package com.arangodb.serde.jackson;
22

3-
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
4-
import com.fasterxml.jackson.annotation.JsonInclude;
5-
import com.fasterxml.jackson.annotation.JsonProperty;
6-
73
import java.lang.annotation.ElementType;
84
import java.lang.annotation.Retention;
95
import java.lang.annotation.RetentionPolicy;
@@ -14,8 +10,5 @@
1410
*/
1511
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
1612
@Retention(RetentionPolicy.RUNTIME)
17-
@JacksonAnnotationsInside
18-
@JsonProperty("_rev")
19-
@JsonInclude(JsonInclude.Include.NON_NULL)
2013
public @interface Rev {
2114
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package com.arangodb.serde.jackson;
22

3-
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
4-
import com.fasterxml.jackson.annotation.JsonInclude;
5-
import com.fasterxml.jackson.annotation.JsonProperty;
6-
73
import java.lang.annotation.ElementType;
84
import java.lang.annotation.Retention;
95
import java.lang.annotation.RetentionPolicy;
@@ -14,8 +10,5 @@
1410
*/
1511
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
1612
@Retention(RetentionPolicy.RUNTIME)
17-
@JacksonAnnotationsInside
18-
@JsonProperty("_to")
19-
@JsonInclude(JsonInclude.Include.NON_NULL)
2013
public @interface To {
2114
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.arangodb.serde.jackson.internal;
2+
3+
import com.arangodb.serde.jackson.*;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
5+
import com.fasterxml.jackson.databind.PropertyName;
6+
import com.fasterxml.jackson.databind.introspect.Annotated;
7+
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
8+
9+
import java.lang.annotation.Annotation;
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
import java.util.Optional;
13+
14+
class ArangoSerdeAnnotationIntrospector extends JacksonAnnotationIntrospector {
15+
private static final JsonInclude JSON_INCLUDE_NON_NULL = JsonIncludeNonNull.class.getAnnotation(JsonInclude.class);
16+
private static final Map<Class<? extends Annotation>, String> MAPPINGS;
17+
private static final Class<? extends Annotation>[] ANNOTATIONS;
18+
19+
static {
20+
MAPPINGS = new HashMap<>();
21+
MAPPINGS.put(Id.class, "_id");
22+
MAPPINGS.put(Key.class, "_key");
23+
MAPPINGS.put(Rev.class, "_rev");
24+
MAPPINGS.put(From.class, "_from");
25+
MAPPINGS.put(To.class, "_to");
26+
ANNOTATIONS = MAPPINGS.keySet().toArray(new Class[0]);
27+
}
28+
29+
@JsonInclude(JsonInclude.Include.NON_NULL)
30+
private static class JsonIncludeNonNull {
31+
}
32+
33+
@Override
34+
public PropertyName findNameForSerialization(Annotated a) {
35+
return Optional.ofNullable(findMapping(a)).orElseGet(() -> super.findNameForSerialization(a));
36+
}
37+
38+
@Override
39+
public PropertyName findNameForDeserialization(Annotated a) {
40+
return Optional.ofNullable(findMapping(a)).orElseGet(() -> super.findNameForDeserialization(a));
41+
}
42+
43+
private PropertyName findMapping(Annotated a) {
44+
for (Map.Entry<Class<? extends Annotation>, String> e : MAPPINGS.entrySet()) {
45+
if (_hasAnnotation(a, e.getKey())) {
46+
return PropertyName.construct(e.getValue());
47+
}
48+
}
49+
return null;
50+
}
51+
52+
@Override
53+
public JsonInclude.Value findPropertyInclusion(Annotated a) {
54+
if (_hasOneOf(a, ANNOTATIONS)) {
55+
return new JsonInclude.Value(JSON_INCLUDE_NON_NULL);
56+
} else {
57+
return super.findPropertyInclusion(a);
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)