Skip to content

Commit f50c074

Browse files
authored
Support deserialization of lists containing JsonSerializable types (Azure#44208)
* Support deserialization of lists containing JsonSerializable types * fix spotbugs issue * remove unused import
1 parent 4254d7a commit f50c074

File tree

2 files changed

+114
-3
lines changed

2 files changed

+114
-3
lines changed

sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/utils/JsonSerializer.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
import java.io.InputStream;
1717
import java.io.OutputStream;
1818
import java.lang.reflect.InvocationTargetException;
19+
import java.lang.reflect.ParameterizedType;
1920
import java.lang.reflect.Type;
21+
import java.util.List;
2022

2123
/**
2224
* Class providing basic JSON serialization and deserialization methods.
@@ -52,13 +54,29 @@ public JsonSerializer() {
5254
@Override
5355
public <T> T deserializeFromBytes(byte[] bytes, Type type) throws IOException {
5456
try (JsonReader jsonReader = JsonReader.fromBytes(bytes)) {
55-
if (type instanceof Class<?> && JsonSerializable.class.isAssignableFrom(TypeUtil.getRawClass(type))) {
57+
if (type instanceof ParameterizedType && List.class.isAssignableFrom(TypeUtil.getRawClass(type))) {
58+
ParameterizedType parameterizedType = (ParameterizedType) type;
59+
Type listElementType = parameterizedType.getActualTypeArguments()[0];
60+
if (listElementType instanceof Class<?>
61+
&& JsonSerializable.class.isAssignableFrom(TypeUtil.getRawClass(listElementType))) {
62+
List<?> list = jsonReader.readArray(arrayReader -> {
63+
Type actualTypeArgument = parameterizedType.getActualTypeArguments()[0];
64+
Class<?> clazz = (Class<?>) actualTypeArgument;
65+
try {
66+
return clazz.getMethod("fromJson", JsonReader.class).invoke(null, arrayReader);
67+
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
68+
throw LOGGER.logThrowableAsError(new RuntimeException(e));
69+
}
70+
});
71+
return (T) list;
72+
}
73+
} else if (type instanceof Class<?>
74+
&& JsonSerializable.class.isAssignableFrom(TypeUtil.getRawClass(type))) {
5675
Class<T> clazz = (Class<T>) type;
5776

5877
return (T) clazz.getMethod("fromJson", JsonReader.class).invoke(null, jsonReader);
59-
} else {
60-
return (T) jsonReader.readUntyped();
6178
}
79+
return (T) jsonReader.readUntyped();
6280
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
6381
throw LOGGER.logThrowableAsError(new RuntimeException(e));
6482
}

sdk/clientcore/core/src/test/java/io/clientcore/core/utils/serializers/JsonSerializerTests.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import io.clientcore.core.http.models.HttpMethod;
77
import io.clientcore.core.implementation.AccessibleByteArrayOutputStream;
8+
import io.clientcore.core.implementation.TypeUtil;
89
import io.clientcore.core.implementation.utils.JsonSerializer;
910
import io.clientcore.core.models.SimpleClass;
1011
import io.clientcore.core.serialization.json.JsonReader;
@@ -20,20 +21,25 @@
2021
import java.io.IOException;
2122
import java.io.InputStream;
2223
import java.lang.reflect.InvocationTargetException;
24+
import java.lang.reflect.ParameterizedType;
2325
import java.net.URI;
2426
import java.net.URL;
2527
import java.nio.charset.StandardCharsets;
2628
import java.time.OffsetDateTime;
2729
import java.time.ZoneOffset;
2830
import java.util.Collections;
2931
import java.util.HashMap;
32+
import java.util.LinkedHashMap;
33+
import java.util.List;
3034
import java.util.Map;
3135
import java.util.stream.Stream;
3236

3337
import static io.clientcore.core.utils.TestUtils.assertArraysEqual;
3438
import static org.junit.jupiter.api.Assertions.assertEquals;
39+
import static org.junit.jupiter.api.Assertions.assertNotNull;
3540
import static org.junit.jupiter.api.Assertions.assertNull;
3641
import static org.junit.jupiter.api.Assertions.assertThrows;
42+
import static org.junit.jupiter.api.Assertions.assertTrue;
3743

3844
public class JsonSerializerTests {
3945
private static final ObjectSerializer SERIALIZER = new JsonSerializer();
@@ -266,4 +272,91 @@ private static Stream<Arguments> unsupportedDeserializationSupplier() {
266272
Arguments.of(URI.class, IOException.class) // Thrown when the String cannot be parsed by core
267273
);
268274
}
275+
276+
@Test
277+
public void deserializeListOfJsonSerializableTypes() throws IOException {
278+
byte[] bytes = "[{\"property\":\"value1\"},{\"property\":\"value2\"}]".getBytes(StandardCharsets.UTF_8);
279+
280+
ParameterizedType type = TypeUtil.createParameterizedType(List.class, FooModel.class);
281+
282+
List<FooModel> models = SERIALIZER.deserializeFromBytes(bytes, type);
283+
assertNotNull(models);
284+
assertEquals(2, models.size());
285+
assertEquals("value1", models.get(0).getProperty());
286+
assertEquals("value2", models.get(1).getProperty());
287+
}
288+
289+
@SuppressWarnings("unchecked")
290+
@Test
291+
public void deserializeListOfNonJsonSerializableTypes() throws IOException {
292+
byte[] bytes = "[{\"property\":\"value1\"},{\"property\":\"value2\"}]".getBytes(StandardCharsets.UTF_8);
293+
294+
ParameterizedType type = TypeUtil.createParameterizedType(List.class, BarModel.class);
295+
296+
List<?> models = SERIALIZER.deserializeFromBytes(bytes, type);
297+
assertNotNull(models);
298+
assertEquals(2, models.size());
299+
assertTrue(models.get(0) instanceof LinkedHashMap);
300+
301+
if (models.get(0) instanceof LinkedHashMap) {
302+
LinkedHashMap<String, String> model = (LinkedHashMap<String, String>) models.get(0);
303+
assertEquals("value1", model.get("property"));
304+
}
305+
}
306+
307+
/**
308+
* A model that implements {@link JsonSerializable}.
309+
*/
310+
public static final class FooModel implements JsonSerializable<FooModel> {
311+
private final String property;
312+
313+
public FooModel(String property) {
314+
this.property = property;
315+
}
316+
317+
public String getProperty() {
318+
return this.property;
319+
}
320+
321+
@Override
322+
public JsonWriter toJson(JsonWriter jsonWriter) throws IOException {
323+
jsonWriter.writeStartObject();
324+
jsonWriter.writeStringField("property", this.property);
325+
return jsonWriter.writeEndObject();
326+
}
327+
328+
public static FooModel fromJson(JsonReader jsonReader) throws IOException {
329+
return jsonReader.readObject(reader -> {
330+
String property = null;
331+
while (reader.nextToken() != JsonToken.END_OBJECT) {
332+
String fieldName = reader.getFieldName();
333+
reader.nextToken();
334+
335+
if ("property".equals(fieldName)) {
336+
property = reader.getString();
337+
} else {
338+
reader.skipChildren();
339+
}
340+
}
341+
FooModel deserializedModel = new FooModel(property);
342+
343+
return deserializedModel;
344+
});
345+
}
346+
}
347+
348+
/**
349+
* A model that does not implement {@link JsonSerializable}.
350+
*/
351+
public static final class BarModel {
352+
private final String property;
353+
354+
public BarModel(String property) {
355+
this.property = property;
356+
}
357+
358+
public String getProperty() {
359+
return this.property;
360+
}
361+
}
269362
}

0 commit comments

Comments
 (0)