Skip to content

Commit c201034

Browse files
committed
Sprout for fix which downcasts parametrized nested IJsonBackedObject objects during deserialization.
1 parent f8a44b0 commit c201034

File tree

4 files changed

+127
-52
lines changed

4 files changed

+127
-52
lines changed

src/main/java/com/microsoft/graph/serializer/CollectionResponseSerializer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ public static <T1> BaseCollectionResponse<T1> deserialize(@Nonnull final JsonEle
8686
for(JsonElement sourceElement : sourceArray) {
8787
if(sourceElement.isJsonObject()) {
8888
final JsonObject sourceObject = sourceElement.getAsJsonObject();
89-
Class<?> entityClass = serializer.getDerivedClass(sourceObject, baseEntityClass);
89+
final DerivedClassIdentifier derivedClassIdentifier = new DerivedClassIdentifier(logger);
90+
Class<?> entityClass = derivedClassIdentifier.getDerivedClass(sourceObject, baseEntityClass);
9091
if(entityClass == null) {
9192
if(baseEntityClass == null) {
9293
logger.logError("Could not find target class for object " + sourceObject.toString(), null);

src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,10 @@
2222

2323
package com.microsoft.graph.serializer;
2424

25-
import com.google.common.base.CaseFormat;
2625
import com.google.gson.Gson;
2726
import com.google.gson.JsonArray;
2827
import com.google.gson.JsonElement;
2928
import com.google.gson.JsonObject;
30-
3129
import com.microsoft.graph.logger.ILogger;
3230

3331
import java.io.IOException;
@@ -38,9 +36,8 @@
3836
import java.util.Iterator;
3937
import java.util.List;
4038
import java.util.Map;
41-
import java.util.Objects;
4239
import java.util.Map.Entry;
43-
40+
import java.util.Objects;
4441
import javax.annotation.Nonnull;
4542
import javax.annotation.Nullable;
4643

@@ -108,7 +105,8 @@ public <T> T deserializeObject(@Nonnull final JsonElement rawElement, @Nonnull f
108105
// If there is a derived class, try to get it and deserialize to it
109106
T jo = jsonObject;
110107
if (rawElement.isJsonObject()) {
111-
final Class<?> derivedClass = this.getDerivedClass(rawObject, clazz);
108+
final DerivedClassIdentifier derivedClassIdentifier = new DerivedClassIdentifier(logger);
109+
final Class<?> derivedClass = derivedClassIdentifier.getDerivedClass(rawObject, clazz);
112110
if (derivedClass != null)
113111
jo = (T) gson.fromJson(rawElement, derivedClass);
114112
}
@@ -310,46 +308,6 @@ private void addAdditionalDataFromManagerToJson(AdditionalDataManager additional
310308
}
311309
}
312310

313-
private final static String ODATA_TYPE_KEY = "@odata.type";
314-
/**
315-
* Get the derived class for the given JSON object
316-
* This covers scenarios in which the service may return one of several derived types
317-
* of a base object, which it defines using the odata.type parameter
318-
*
319-
* @param jsonObject the raw JSON object of the response
320-
* @param parentClass the parent class the derived class should inherit from
321-
* @return the derived class if found, or null if not applicable
322-
*/
323-
@Nullable
324-
public Class<?> getDerivedClass(@Nonnull final JsonObject jsonObject, @Nullable final Class<?> parentClass) {
325-
Objects.requireNonNull(jsonObject, "parameter jsonObject cannot be null");
326-
//Identify the odata.type information if provided
327-
if (jsonObject.get(ODATA_TYPE_KEY) != null) {
328-
/** #microsoft.graph.user or #microsoft.graph.callrecords.callrecord */
329-
final String odataType = jsonObject.get(ODATA_TYPE_KEY).getAsString();
330-
final int lastDotIndex = odataType.lastIndexOf(".");
331-
final String derivedType = (odataType.substring(0, lastDotIndex) +
332-
".models." +
333-
CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL,
334-
odataType.substring(lastDotIndex + 1)))
335-
.replace("#", "com.");
336-
try {
337-
Class<?> derivedClass = Class.forName(derivedType);
338-
//Check that the derived class inherits from the given parent class
339-
if (parentClass == null || parentClass.isAssignableFrom(derivedClass)) {
340-
return derivedClass;
341-
}
342-
return null;
343-
} catch (ClassNotFoundException e) {
344-
logger.logDebug("Unable to find a corresponding class for derived type " + derivedType + ". Falling back to parent class.");
345-
//If we cannot determine the derived type to cast to, return null
346-
//This may happen if the API and the SDK are out of sync
347-
return null;
348-
}
349-
}
350-
//If there is no defined OData type, return null
351-
return null;
352-
}
353311

354312
/**
355313
* Gets the logger in use
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.microsoft.graph.serializer;
2+
3+
import com.google.common.base.CaseFormat;
4+
import com.google.gson.JsonObject;
5+
import com.microsoft.graph.logger.ILogger;
6+
7+
import java.util.Objects;
8+
import javax.annotation.Nonnull;
9+
import javax.annotation.Nullable;
10+
11+
public class DerivedClassIdentifier {
12+
13+
private final static String ODATA_TYPE_KEY = "@odata.type";
14+
private final ILogger logger;
15+
16+
public DerivedClassIdentifier(ILogger logger) {
17+
this.logger = logger;
18+
}
19+
20+
/**
21+
* Get the derived class for the given JSON object
22+
* This covers scenarios in which the service may return one of several derived types
23+
* of a base object, which it defines using the odata.type parameter
24+
*
25+
* @param jsonObject the raw JSON object of the response
26+
* @param parentClass the parent class the derived class should inherit from
27+
* @return the derived class if found, or null if not applicable
28+
*/
29+
@Nullable
30+
public Class<?> getDerivedClass(@Nonnull final JsonObject jsonObject, @Nullable final Class<?> parentClass) {
31+
Objects.requireNonNull(jsonObject, "parameter jsonObject cannot be null");
32+
//Identify the odata.type information if provided
33+
if (jsonObject.get(ODATA_TYPE_KEY) != null) {
34+
/** #microsoft.graph.user or #microsoft.graph.callrecords.callrecord */
35+
final String odataType = jsonObject.get(ODATA_TYPE_KEY).getAsString();
36+
final int lastDotIndex = odataType.lastIndexOf(".");
37+
final String derivedType = (odataType.substring(0, lastDotIndex) +
38+
".models." +
39+
CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL,
40+
odataType.substring(lastDotIndex + 1)))
41+
.replace("#", "com.");
42+
try {
43+
Class<?> derivedClass = Class.forName(derivedType);
44+
//Check that the derived class inherits from the given parent class
45+
if (parentClass == null || parentClass.isAssignableFrom(derivedClass)) {
46+
return derivedClass;
47+
}
48+
return null;
49+
} catch (ClassNotFoundException e) {
50+
logger.logDebug("Unable to find a corresponding class for derived type " + derivedType + ". Falling back to parent class.");
51+
//If we cannot determine the derived type to cast to, return null
52+
//This may happen if the API and the SDK are out of sync
53+
return null;
54+
}
55+
}
56+
//If there is no defined OData type, return null
57+
return null;
58+
}
59+
}

src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,26 @@
2222

2323
package com.microsoft.graph.serializer;
2424

25-
import java.io.IOException;
26-
import java.util.HashMap;
27-
import java.util.Map;
28-
import java.util.Objects;
29-
3025
import com.google.common.base.CaseFormat;
3126
import com.google.gson.Gson;
27+
import com.google.gson.JsonElement;
3228
import com.google.gson.TypeAdapter;
3329
import com.google.gson.TypeAdapterFactory;
30+
import com.google.gson.internal.Streams;
3431
import com.google.gson.reflect.TypeToken;
3532
import com.google.gson.stream.JsonReader;
3633
import com.google.gson.stream.JsonToken;
3734
import com.google.gson.stream.JsonWriter;
35+
import com.microsoft.graph.http.BaseCollectionPage;
36+
import com.microsoft.graph.http.BaseCollectionResponse;
3837
import com.microsoft.graph.logger.ILogger;
3938

40-
import javax.annotation.Nullable;
39+
import java.io.IOException;
40+
import java.util.HashMap;
41+
import java.util.Map;
42+
import java.util.Objects;
4143
import javax.annotation.Nonnull;
44+
import javax.annotation.Nullable;
4245

4346
/**
4447
* Handles serialization/deserialization for special types (especially of
@@ -89,16 +92,70 @@ public FallbackTypeAdapterFactory(@Nonnull final ILogger logger) {
8992
public <T> TypeAdapter<T> create(@Nonnull final Gson gson, @Nonnull final TypeToken<T> type) {
9093
Objects.requireNonNull(type, "parameter type cannot be null");
9194
final Class<T> rawType = (Class<T>) type.getRawType();
95+
9296
if (rawType.isEnum()) {
9397
return new EnumTypeAdapter<T>(rawType, logger);
9498
} else if (rawType == Void.class) {
9599
return (TypeAdapter<T>) voidAdapter;
100+
} else if (IJsonBackedObject.class.isAssignableFrom(type.getRawType())) {
101+
102+
// Avoid overriding custom IJsonBackedObject type adapters defined in GsonFactory
103+
if (BaseCollectionResponse.class.isAssignableFrom(rawType) || BaseCollectionPage.class.isAssignableFrom(rawType)) {
104+
return null;
105+
}
106+
107+
final TypeAdapter<?> delegatedAdapter = gson.getDelegateAdapter(this, type);
108+
return (TypeAdapter<T>) new ODataTypeParametrizedIJsonBackedObjectAdapter(gson, delegatedAdapter, type);
96109
}
97110
else {
98111
return null;
99112
}
100113
}
101114

115+
/**
116+
* This adapter is responsible for deserialization of IJsonBackedObjects where service
117+
* returns one of several derived types of a base object, which is defined using the
118+
* odata.type parameter. If odata.type parameter is not found, the Gson default
119+
* (delegated) type adapter is used.
120+
*/
121+
private class ODataTypeParametrizedIJsonBackedObjectAdapter extends TypeAdapter<IJsonBackedObject> {
122+
123+
private final Gson gson;
124+
private final TypeAdapter<?> delegatedAdapter;
125+
private final TypeToken<?> type;
126+
127+
public ODataTypeParametrizedIJsonBackedObjectAdapter(@Nonnull Gson gson, @Nonnull TypeAdapter<?> delegatedAdapter, @Nonnull final TypeToken<?> type) {
128+
super();
129+
this.gson = gson;
130+
this.delegatedAdapter = delegatedAdapter;
131+
this.type = type;
132+
}
133+
134+
@Override
135+
public void write(JsonWriter out, IJsonBackedObject value)
136+
throws IOException
137+
{
138+
((TypeAdapter<IJsonBackedObject>)this.delegatedAdapter).write(out, value);
139+
}
140+
141+
@Override
142+
public IJsonBackedObject read(JsonReader in) {
143+
JsonElement jsonElement = Streams.parse(in);
144+
145+
if (jsonElement.isJsonObject()) {
146+
final DerivedClassIdentifier derivedClassIdentifier = new DerivedClassIdentifier(logger);
147+
final Class<?> derivedClass = derivedClassIdentifier.getDerivedClass(jsonElement.getAsJsonObject(), type.getRawType());
148+
149+
if (derivedClass != null) {
150+
final TypeAdapter<?> subTypeAdapter = gson.getDelegateAdapter(FallbackTypeAdapterFactory.this, TypeToken.get(derivedClass));
151+
return (IJsonBackedObject) subTypeAdapter.fromJsonTree(jsonElement);
152+
}
153+
}
154+
155+
return (IJsonBackedObject) delegatedAdapter.fromJsonTree(jsonElement);
156+
}
157+
}
158+
102159
private static final class EnumTypeAdapter<T> extends TypeAdapter<T> {
103160

104161
private final Map<String, T> enumValues;

0 commit comments

Comments
 (0)