Skip to content

Commit 8a8327e

Browse files
authored
Backport flattening deserializer to track 1 (#696)
* Backport flattening deserializer to track 1 * revert some changes
1 parent f5e7994 commit 8a8327e

File tree

8 files changed

+64
-41
lines changed

8 files changed

+64
-41
lines changed

client-runtime/src/main/java/com/microsoft/rest/serializer/FlatteningDeserializer.java

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import com.fasterxml.jackson.databind.JsonMappingException;
1818
import com.fasterxml.jackson.databind.JsonNode;
1919
import com.fasterxml.jackson.databind.ObjectMapper;
20-
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
2120
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
2221
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
2322
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
@@ -35,6 +34,8 @@
3534
* will be mapped to a top level "name" property in the POJO model.
3635
*/
3736
public final class FlatteningDeserializer extends StdDeserializer<Object> implements ResolvableDeserializer {
37+
private static final long serialVersionUID = -2133095337545715498L;
38+
3839
/**
3940
* The default mapperAdapter for the current type.
4041
*/
@@ -68,9 +69,12 @@ public static SimpleModule getModule(final ObjectMapper mapper) {
6869
SimpleModule module = new SimpleModule();
6970
module.setDeserializerModifier(new BeanDeserializerModifier() {
7071
@Override
71-
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
72-
if (BeanDeserializer.class.isAssignableFrom(deserializer.getClass())) {
73-
// Apply flattening deserializer on all POJO types.
72+
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
73+
BeanDescription beanDesc,
74+
JsonDeserializer<?> deserializer) {
75+
if (beanDesc.getBeanClass().getAnnotation(JsonFlatten.class) != null) {
76+
// Register 'FlatteningDeserializer' for complex type so that 'deserializeWithType'
77+
// will get called for complex types and it can analyze typeId discriminator.
7478
return new FlatteningDeserializer(beanDesc.getBeanClass(), deserializer, mapper);
7579
} else {
7680
return deserializer;
@@ -82,27 +86,26 @@ public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, Bean
8286

8387
@SuppressWarnings("unchecked")
8488
@Override
85-
public Object deserializeWithType(JsonParser jp, DeserializationContext cxt, TypeDeserializer tDeserializer) throws IOException {
86-
// This method will be called by Jackson for each "Json object with TypeId" in the input wire stream
87-
// it is trying to deserialize.
88-
// The below variable 'currentJsonNode' will hold the JsonNode corresponds to current
89-
// Json object this method is called to handle.
89+
public Object deserializeWithType(JsonParser jp,
90+
DeserializationContext cxt,
91+
TypeDeserializer tDeserializer) throws IOException {
92+
// This method will be called from Jackson for each "Json object with TypeId" as it
93+
// process the input data. This enable us to pre-process then give it to the next
94+
// deserializer in the Jackson pipeline.
95+
//
96+
// The parameter 'jp' is the reader to read "Json object with TypeId"
9097
//
9198
JsonNode currentJsonNode = mapper.readTree(jp);
9299
final Class<?> tClass = this.defaultDeserializer.handledType();
93100
for (Class<?> c : TypeToken.of(tClass).getTypes().classes().rawTypes()) {
94-
if (c.isAssignableFrom(Object.class)) {
95-
continue;
96-
} else {
97-
final JsonTypeInfo typeInfo = c.getAnnotation(com.fasterxml.jackson.annotation.JsonTypeInfo.class);
98-
if (typeInfo != null) {
99-
String typeId = typeInfo.property();
100-
if (containsDot(typeId)) {
101-
final String typeIdOnWire = unescapeEscapedDots(typeId);
102-
JsonNode typeIdValue = ((ObjectNode) currentJsonNode).remove(typeIdOnWire);
103-
if (typeIdValue != null) {
104-
((ObjectNode) currentJsonNode).put(typeId, typeIdValue);
105-
}
101+
final JsonTypeInfo typeInfo = c.getAnnotation(com.fasterxml.jackson.annotation.JsonTypeInfo.class);
102+
if (typeInfo != null) {
103+
String typeId = typeInfo.property();
104+
if (containsDot(typeId)) {
105+
final String typeIdOnWire = unescapeEscapedDots(typeId);
106+
JsonNode typeIdValue = ((ObjectNode) currentJsonNode).remove(typeIdOnWire);
107+
if (typeIdValue != null) {
108+
((ObjectNode) currentJsonNode).put(typeId, typeIdValue);
106109
}
107110
}
108111
}
@@ -114,8 +117,8 @@ public Object deserializeWithType(JsonParser jp, DeserializationContext cxt, Typ
114117
public Object deserialize(JsonParser jp, DeserializationContext cxt) throws IOException {
115118
// This method will be called by Jackson for each "Json object" in the input wire stream
116119
// it is trying to deserialize.
117-
// The below variable 'currentJsonNode' will hold the JsonNode corresponds to current
118-
// Json object this method is called to handle.
120+
//
121+
// The parameter 'jp' is the reader to read "Json object with TypeId"
119122
//
120123
JsonNode currentJsonNode = mapper.readTree(jp);
121124
if (currentJsonNode.isNull()) {
@@ -152,15 +155,34 @@ private static void handleFlatteningForField(Field classField, JsonNode jsonNode
152155
final JsonProperty jsonProperty = classField.getAnnotation(JsonProperty.class);
153156
if (jsonProperty != null) {
154157
final String jsonPropValue = jsonProperty.value();
158+
if (jsonNode.has(jsonPropValue)) {
159+
// There is an additional property with it's key conflicting with the
160+
// JsonProperty value, escape this additional property's key.
161+
final String escapedJsonPropValue = jsonPropValue.replace(".", "\\.");
162+
((ObjectNode) jsonNode).set(escapedJsonPropValue, jsonNode.get(jsonPropValue));
163+
}
155164
if (containsFlatteningDots(jsonPropValue)) {
165+
// The jsonProperty value contains flattening dots, uplift the nested
166+
// json node that this value resolving to the current level.
156167
JsonNode childJsonNode = findNestedNode(jsonNode, jsonPropValue);
157-
((ObjectNode) jsonNode).put(jsonPropValue, childJsonNode);
168+
((ObjectNode) jsonNode).set(jsonPropValue, childJsonNode);
158169
}
159170
}
160171
}
161172

162173
/**
163-
* Given a json node, find a nested node using given composed key.
174+
* Checks whether the given key has flattening dots in it.
175+
* Flattening dots are dot '.' characters those are not preceded by slash '\'
176+
*
177+
* @param key the key
178+
* @return true if the key has flattening dots, false otherwise.
179+
*/
180+
private static boolean containsFlatteningDots(String key) {
181+
return key.matches(".+[^\\\\]\\..+");
182+
}
183+
184+
/**
185+
* Given a json node, find a nested node in it identified by the given composed key.
164186
*
165187
* @param jsonNode the parent json node
166188
* @param composedKey a key combines multiple keys using flattening dots.
@@ -179,17 +201,6 @@ private static JsonNode findNestedNode(JsonNode jsonNode, String composedKey) {
179201
return jsonNode;
180202
}
181203

182-
/**
183-
* Checks whether the given key has flattening dots in it.
184-
* Flattening dots are dot character '.' those are not preceded by slash '\'
185-
*
186-
* @param key the key
187-
* @return true if the key has flattening dots, false otherwise.
188-
*/
189-
private static boolean containsFlatteningDots(String key) {
190-
return key.matches(".+[^\\\\]\\..+");
191-
}
192-
193204
/**
194205
* Split the key by flattening dots.
195206
* Flattening dots are dot character '.' those are not preceded by slash '\'
@@ -220,18 +231,19 @@ private static String unescapeEscapedDots(String key) {
220231
* @return true if at least one dot found
221232
*/
222233
private static boolean containsDot(String str) {
223-
return str != null && str != "" && str.contains(".");
234+
return str != null && !str.isEmpty() && str.contains(".");
224235
}
225236

226237
/**
227238
* Create a JsonParser for a given json node.
239+
*
228240
* @param jsonNode the json node
229241
* @return the json parser
230-
* @throws IOException
242+
* @throws IOException if underlying reader fails to read the json string
231243
*/
232244
private static JsonParser newJsonParserForNode(JsonNode jsonNode) throws IOException {
233245
JsonParser parser = new JsonFactory().createParser(jsonNode.toString());
234246
parser.nextToken();
235247
return parser;
236248
}
237-
}
249+
}

client-runtime/src/test/java/com/microsoft/rest/AdditionalPropertiesSerializerTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public void canSerializeAdditionalProperties() throws Exception {
3434
foo.additionalProperties.put("properties.bar", "barbar");
3535

3636
String serialized = new JacksonAdapter().serialize(foo);
37-
Assert.assertEquals("{\"$type\":\"foo\",\"properties\":{\"bar\":\"hello.world\",\"props\":{\"baz\":[\"hello\",\"hello.world\"],\"q\":{\"qux\":{\"hello\":\"world\",\"a.b\":\"c.d\",\"bar.b\":\"uuzz\",\"bar.a\":\"ttyy\"}}}},\"bar\":\"baz\",\"a.b\":\"c.d\",\"properties.bar\":\"barbar\"}", serialized);
37+
String expected = "{\"$type\":\"foo\",\"properties\":{\"bar\":\"hello.world\",\"props\":{\"baz\":[\"hello\",\"hello.world\"],\"q\":{\"qux\":{\"hello\":\"world\",\"a.b\":\"c.d\",\"bar.b\":\"uuzz\",\"bar.a\":\"ttyy\"}}}},\"bar\":\"baz\",\"a.b\":\"c.d\",\"properties.bar\":\"barbar\"}";
38+
Assert.assertEquals(expected, serialized);
3839
}
3940

4041
@Test

client-runtime/src/test/java/com/microsoft/rest/AnimalWithTypeIdContainingDot.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import com.fasterxml.jackson.annotation.JsonSubTypes;
44
import com.fasterxml.jackson.annotation.JsonTypeInfo;
55
import com.fasterxml.jackson.annotation.JsonTypeName;
6+
import com.microsoft.rest.serializer.JsonFlatten;
67

8+
@JsonFlatten
79
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@odata\\.type", defaultImpl = AnimalWithTypeIdContainingDot.class)
810
@JsonTypeName("AnimalWithTypeIdContainingDot")
911
@JsonSubTypes({

client-runtime/src/test/java/com/microsoft/rest/CatWithTypeIdContainingDot.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import com.fasterxml.jackson.annotation.JsonProperty;
44
import com.fasterxml.jackson.annotation.JsonTypeInfo;
55
import com.fasterxml.jackson.annotation.JsonTypeName;
6+
import com.microsoft.rest.serializer.JsonFlatten;
67

8+
@JsonFlatten
79
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@odata\\.type", defaultImpl = CatWithTypeIdContainingDot.class)
810
@JsonTypeName("#Favourite.Pet.CatWithTypeIdContainingDot")
911
public class CatWithTypeIdContainingDot extends AnimalWithTypeIdContainingDot {

client-runtime/src/test/java/com/microsoft/rest/DogWithTypeIdContainingDot.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import com.fasterxml.jackson.annotation.JsonProperty;
44
import com.fasterxml.jackson.annotation.JsonTypeInfo;
55
import com.fasterxml.jackson.annotation.JsonTypeName;
6+
import com.microsoft.rest.serializer.JsonFlatten;
67

8+
@JsonFlatten
79
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@odata\\.type", defaultImpl = DogWithTypeIdContainingDot.class)
810
@JsonTypeName("#Favourite.Pet.DogWithTypeIdContainingDot")
911
public class DogWithTypeIdContainingDot extends AnimalWithTypeIdContainingDot {

client-runtime/src/test/java/com/microsoft/rest/NonEmptyAnimalWithTypeIdContainingDot.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import com.fasterxml.jackson.annotation.JsonSubTypes;
55
import com.fasterxml.jackson.annotation.JsonTypeInfo;
66
import com.fasterxml.jackson.annotation.JsonTypeName;
7+
import com.microsoft.rest.serializer.JsonFlatten;
78

9+
@JsonFlatten
810
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@odata\\.type", defaultImpl = NonEmptyAnimalWithTypeIdContainingDot.class)
911
@JsonTypeName("NonEmptyAnimalWithTypeIdContainingDot")
1012
@JsonSubTypes({

client-runtime/src/test/java/com/microsoft/rest/RabbitWithTypeIdContainingDot.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import com.fasterxml.jackson.annotation.JsonProperty;
44
import com.fasterxml.jackson.annotation.JsonTypeInfo;
55
import com.fasterxml.jackson.annotation.JsonTypeName;
6+
import com.microsoft.rest.serializer.JsonFlatten;
67

78
import java.util.List;
89

10+
@JsonFlatten
911
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@odata\\.type", defaultImpl = RabbitWithTypeIdContainingDot.class)
1012
@JsonTypeName("#Favourite.Pet.RabbitWithTypeIdContainingDot")
1113
public class RabbitWithTypeIdContainingDot extends AnimalWithTypeIdContainingDot {

client-runtime/src/test/java/com/microsoft/rest/TurtleWithTypeIdContainingDot.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import com.fasterxml.jackson.annotation.JsonTypeInfo;
55
import com.fasterxml.jackson.annotation.JsonTypeName;
66

7-
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@odata\\.type", defaultImpl = TurtleWithTypeIdContainingDot.class)
7+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@odata.type", defaultImpl = TurtleWithTypeIdContainingDot.class)
88
@JsonTypeName("#Favourite.Pet.TurtleWithTypeIdContainingDot")
99
public class TurtleWithTypeIdContainingDot extends NonEmptyAnimalWithTypeIdContainingDot {
1010
@JsonProperty(value = "size")

0 commit comments

Comments
 (0)