Skip to content

Commit

Permalink
Backport flattening deserializer to track 1 (#696)
Browse files Browse the repository at this point in the history
* Backport flattening deserializer to track 1

* revert some changes
  • Loading branch information
jianghaolu authored Jun 16, 2021
1 parent f5e7994 commit 8a8327e
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
Expand All @@ -35,6 +34,8 @@
* will be mapped to a top level "name" property in the POJO model.
*/
public final class FlatteningDeserializer extends StdDeserializer<Object> implements ResolvableDeserializer {
private static final long serialVersionUID = -2133095337545715498L;

/**
* The default mapperAdapter for the current type.
*/
Expand Down Expand Up @@ -68,9 +69,12 @@ public static SimpleModule getModule(final ObjectMapper mapper) {
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (BeanDeserializer.class.isAssignableFrom(deserializer.getClass())) {
// Apply flattening deserializer on all POJO types.
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc,
JsonDeserializer<?> deserializer) {
if (beanDesc.getBeanClass().getAnnotation(JsonFlatten.class) != null) {
// Register 'FlatteningDeserializer' for complex type so that 'deserializeWithType'
// will get called for complex types and it can analyze typeId discriminator.
return new FlatteningDeserializer(beanDesc.getBeanClass(), deserializer, mapper);
} else {
return deserializer;
Expand All @@ -82,27 +86,26 @@ public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, Bean

@SuppressWarnings("unchecked")
@Override
public Object deserializeWithType(JsonParser jp, DeserializationContext cxt, TypeDeserializer tDeserializer) throws IOException {
// This method will be called by Jackson for each "Json object with TypeId" in the input wire stream
// it is trying to deserialize.
// The below variable 'currentJsonNode' will hold the JsonNode corresponds to current
// Json object this method is called to handle.
public Object deserializeWithType(JsonParser jp,
DeserializationContext cxt,
TypeDeserializer tDeserializer) throws IOException {
// This method will be called from Jackson for each "Json object with TypeId" as it
// process the input data. This enable us to pre-process then give it to the next
// deserializer in the Jackson pipeline.
//
// The parameter 'jp' is the reader to read "Json object with TypeId"
//
JsonNode currentJsonNode = mapper.readTree(jp);
final Class<?> tClass = this.defaultDeserializer.handledType();
for (Class<?> c : TypeToken.of(tClass).getTypes().classes().rawTypes()) {
if (c.isAssignableFrom(Object.class)) {
continue;
} else {
final JsonTypeInfo typeInfo = c.getAnnotation(com.fasterxml.jackson.annotation.JsonTypeInfo.class);
if (typeInfo != null) {
String typeId = typeInfo.property();
if (containsDot(typeId)) {
final String typeIdOnWire = unescapeEscapedDots(typeId);
JsonNode typeIdValue = ((ObjectNode) currentJsonNode).remove(typeIdOnWire);
if (typeIdValue != null) {
((ObjectNode) currentJsonNode).put(typeId, typeIdValue);
}
final JsonTypeInfo typeInfo = c.getAnnotation(com.fasterxml.jackson.annotation.JsonTypeInfo.class);
if (typeInfo != null) {
String typeId = typeInfo.property();
if (containsDot(typeId)) {
final String typeIdOnWire = unescapeEscapedDots(typeId);
JsonNode typeIdValue = ((ObjectNode) currentJsonNode).remove(typeIdOnWire);
if (typeIdValue != null) {
((ObjectNode) currentJsonNode).put(typeId, typeIdValue);
}
}
}
Expand All @@ -114,8 +117,8 @@ public Object deserializeWithType(JsonParser jp, DeserializationContext cxt, Typ
public Object deserialize(JsonParser jp, DeserializationContext cxt) throws IOException {
// This method will be called by Jackson for each "Json object" in the input wire stream
// it is trying to deserialize.
// The below variable 'currentJsonNode' will hold the JsonNode corresponds to current
// Json object this method is called to handle.
//
// The parameter 'jp' is the reader to read "Json object with TypeId"
//
JsonNode currentJsonNode = mapper.readTree(jp);
if (currentJsonNode.isNull()) {
Expand Down Expand Up @@ -152,15 +155,34 @@ private static void handleFlatteningForField(Field classField, JsonNode jsonNode
final JsonProperty jsonProperty = classField.getAnnotation(JsonProperty.class);
if (jsonProperty != null) {
final String jsonPropValue = jsonProperty.value();
if (jsonNode.has(jsonPropValue)) {
// There is an additional property with it's key conflicting with the
// JsonProperty value, escape this additional property's key.
final String escapedJsonPropValue = jsonPropValue.replace(".", "\\.");
((ObjectNode) jsonNode).set(escapedJsonPropValue, jsonNode.get(jsonPropValue));
}
if (containsFlatteningDots(jsonPropValue)) {
// The jsonProperty value contains flattening dots, uplift the nested
// json node that this value resolving to the current level.
JsonNode childJsonNode = findNestedNode(jsonNode, jsonPropValue);
((ObjectNode) jsonNode).put(jsonPropValue, childJsonNode);
((ObjectNode) jsonNode).set(jsonPropValue, childJsonNode);
}
}
}

/**
* Given a json node, find a nested node using given composed key.
* Checks whether the given key has flattening dots in it.
* Flattening dots are dot '.' characters those are not preceded by slash '\'
*
* @param key the key
* @return true if the key has flattening dots, false otherwise.
*/
private static boolean containsFlatteningDots(String key) {
return key.matches(".+[^\\\\]\\..+");
}

/**
* Given a json node, find a nested node in it identified by the given composed key.
*
* @param jsonNode the parent json node
* @param composedKey a key combines multiple keys using flattening dots.
Expand All @@ -179,17 +201,6 @@ private static JsonNode findNestedNode(JsonNode jsonNode, String composedKey) {
return jsonNode;
}

/**
* Checks whether the given key has flattening dots in it.
* Flattening dots are dot character '.' those are not preceded by slash '\'
*
* @param key the key
* @return true if the key has flattening dots, false otherwise.
*/
private static boolean containsFlatteningDots(String key) {
return key.matches(".+[^\\\\]\\..+");
}

/**
* Split the key by flattening dots.
* Flattening dots are dot character '.' those are not preceded by slash '\'
Expand Down Expand Up @@ -220,18 +231,19 @@ private static String unescapeEscapedDots(String key) {
* @return true if at least one dot found
*/
private static boolean containsDot(String str) {
return str != null && str != "" && str.contains(".");
return str != null && !str.isEmpty() && str.contains(".");
}

/**
* Create a JsonParser for a given json node.
*
* @param jsonNode the json node
* @return the json parser
* @throws IOException
* @throws IOException if underlying reader fails to read the json string
*/
private static JsonParser newJsonParserForNode(JsonNode jsonNode) throws IOException {
JsonParser parser = new JsonFactory().createParser(jsonNode.toString());
parser.nextToken();
return parser;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public void canSerializeAdditionalProperties() throws Exception {
foo.additionalProperties.put("properties.bar", "barbar");

String serialized = new JacksonAdapter().serialize(foo);
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);
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\"}";
Assert.assertEquals(expected, serialized);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.microsoft.rest.serializer.JsonFlatten;

@JsonFlatten
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@odata\\.type", defaultImpl = AnimalWithTypeIdContainingDot.class)
@JsonTypeName("AnimalWithTypeIdContainingDot")
@JsonSubTypes({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.microsoft.rest.serializer.JsonFlatten;

@JsonFlatten
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@odata\\.type", defaultImpl = CatWithTypeIdContainingDot.class)
@JsonTypeName("#Favourite.Pet.CatWithTypeIdContainingDot")
public class CatWithTypeIdContainingDot extends AnimalWithTypeIdContainingDot {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.microsoft.rest.serializer.JsonFlatten;

@JsonFlatten
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@odata\\.type", defaultImpl = DogWithTypeIdContainingDot.class)
@JsonTypeName("#Favourite.Pet.DogWithTypeIdContainingDot")
public class DogWithTypeIdContainingDot extends AnimalWithTypeIdContainingDot {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.microsoft.rest.serializer.JsonFlatten;

@JsonFlatten
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@odata\\.type", defaultImpl = NonEmptyAnimalWithTypeIdContainingDot.class)
@JsonTypeName("NonEmptyAnimalWithTypeIdContainingDot")
@JsonSubTypes({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.microsoft.rest.serializer.JsonFlatten;

import java.util.List;

@JsonFlatten
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@odata\\.type", defaultImpl = RabbitWithTypeIdContainingDot.class)
@JsonTypeName("#Favourite.Pet.RabbitWithTypeIdContainingDot")
public class RabbitWithTypeIdContainingDot extends AnimalWithTypeIdContainingDot {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;

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

0 comments on commit 8a8327e

Please sign in to comment.