1717import com .fasterxml .jackson .databind .JsonMappingException ;
1818import com .fasterxml .jackson .databind .JsonNode ;
1919import com .fasterxml .jackson .databind .ObjectMapper ;
20- import com .fasterxml .jackson .databind .deser .BeanDeserializer ;
2120import com .fasterxml .jackson .databind .deser .BeanDeserializerModifier ;
2221import com .fasterxml .jackson .databind .deser .ResolvableDeserializer ;
2322import com .fasterxml .jackson .databind .deser .std .StdDeserializer ;
3534 * will be mapped to a top level "name" property in the POJO model.
3635 */
3736public 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+ }
0 commit comments