Skip to content

Commit dc897e0

Browse files
authored
fix: add support to deserialize to custom Lists and Maps (#337)
1 parent 44c806c commit dc897e0

File tree

4 files changed

+155
-2
lines changed

4 files changed

+155
-2
lines changed

google-cloud-firestore/src/main/java/com/google/cloud/firestore/CustomClassMapper.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,21 @@ private static <T> T deserializeToParameterizedType(
266266
Type genericType = type.getActualTypeArguments()[0];
267267
if (o instanceof List) {
268268
List<Object> list = (List<Object>) o;
269-
List<Object> result = new ArrayList<>(list.size());
269+
List<Object> result;
270+
try {
271+
result =
272+
(rawType == List.class)
273+
? new ArrayList<>(list.size())
274+
: (List<Object>) rawType.getDeclaredConstructor().newInstance();
275+
} catch (InstantiationException
276+
| IllegalAccessException
277+
| NoSuchMethodException
278+
| InvocationTargetException e) {
279+
throw deserializeError(
280+
context.errorPath,
281+
String.format(
282+
"Unable to deserialize to %s: %s", rawType.getSimpleName(), e.toString()));
283+
}
270284
for (int i = 0; i < list.size(); i++) {
271285
result.add(
272286
deserializeToType(
@@ -287,7 +301,21 @@ private static <T> T deserializeToParameterizedType(
287301
"Only Maps with string keys are supported, but found Map with key type " + keyType);
288302
}
289303
Map<String, Object> map = expectMap(o, context);
290-
HashMap<String, Object> result = new HashMap<>();
304+
HashMap<String, Object> result;
305+
try {
306+
result =
307+
(rawType == Map.class)
308+
? new HashMap<String, Object>()
309+
: (HashMap<String, Object>) rawType.getDeclaredConstructor().newInstance();
310+
} catch (InstantiationException
311+
| IllegalAccessException
312+
| NoSuchMethodException
313+
| InvocationTargetException e) {
314+
throw deserializeError(
315+
context.errorPath,
316+
String.format(
317+
"Unable to deserialize to %s: %s", rawType.getSimpleName(), e.toString()));
318+
}
291319
for (Map.Entry<String, Object> entry : map.entrySet()) {
292320
result.put(
293321
entry.getKey(),

google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import static com.google.cloud.firestore.LocalFirestoreHelper.DOCUMENT_NAME;
2525
import static com.google.cloud.firestore.LocalFirestoreHelper.DOCUMENT_PATH;
2626
import static com.google.cloud.firestore.LocalFirestoreHelper.FIELD_TRANSFORM_COMMIT_RESPONSE;
27+
import static com.google.cloud.firestore.LocalFirestoreHelper.FOO_LIST;
28+
import static com.google.cloud.firestore.LocalFirestoreHelper.FOO_MAP;
2729
import static com.google.cloud.firestore.LocalFirestoreHelper.GEO_POINT;
2830
import static com.google.cloud.firestore.LocalFirestoreHelper.NESTED_CLASS_OBJECT;
2931
import static com.google.cloud.firestore.LocalFirestoreHelper.SERVER_TIMESTAMP_PROTO;
@@ -69,10 +71,13 @@
6971
import com.google.cloud.firestore.LocalFirestoreHelper.InvalidPOJO;
7072
import com.google.cloud.firestore.spi.v1.FirestoreRpc;
7173
import com.google.common.collect.ImmutableList;
74+
import com.google.common.collect.ImmutableMap;
75+
import com.google.firestore.v1.ArrayValue;
7276
import com.google.firestore.v1.BatchGetDocumentsRequest;
7377
import com.google.firestore.v1.BatchGetDocumentsResponse;
7478
import com.google.firestore.v1.CommitRequest;
7579
import com.google.firestore.v1.CommitResponse;
80+
import com.google.firestore.v1.MapValue;
7681
import com.google.firestore.v1.Value;
7782
import java.math.BigInteger;
7883
import java.util.ArrayList;
@@ -1073,4 +1078,66 @@ public void deleteNestedFieldUsingFieldPath() throws Exception {
10731078
Collections.<String, Value>emptyMap(), Collections.singletonList("`a.b`.`c.d`")));
10741079
assertEquals(expectedCommit, commitCapture.getValue());
10751080
}
1081+
1082+
@Test
1083+
public void deserializeCustomList() throws ExecutionException, InterruptedException {
1084+
ImmutableMap CUSTOM_LIST_PROTO =
1085+
ImmutableMap.<String, Value>builder()
1086+
.put(
1087+
"fooList",
1088+
Value.newBuilder()
1089+
.setArrayValue(
1090+
ArrayValue.newBuilder()
1091+
.addValues(
1092+
Value.newBuilder()
1093+
.setMapValue(
1094+
MapValue.newBuilder().putAllFields(SINGLE_FIELD_PROTO))
1095+
.build()))
1096+
.build())
1097+
.build();
1098+
doAnswer(getAllResponse(CUSTOM_LIST_PROTO))
1099+
.when(firestoreMock)
1100+
.streamRequest(
1101+
getAllCapture.capture(),
1102+
streamObserverCapture.capture(),
1103+
Matchers.<ServerStreamingCallable>any());
1104+
DocumentSnapshot snapshot = documentReference.get().get();
1105+
LocalFirestoreHelper.CustomList customList =
1106+
snapshot.toObject(LocalFirestoreHelper.CustomList.class);
1107+
1108+
assertEquals(FOO_LIST, customList.fooList);
1109+
assertEquals(SINGLE_FIELD_OBJECT, customList.fooList.get(0));
1110+
}
1111+
1112+
@Test
1113+
public void deserializeCustomMap() throws ExecutionException, InterruptedException {
1114+
ImmutableMap CUSTOM_MAP_PROTO =
1115+
ImmutableMap.<String, Value>builder()
1116+
.put(
1117+
"fooMap",
1118+
Value.newBuilder()
1119+
.setMapValue(
1120+
MapValue.newBuilder()
1121+
.putFields(
1122+
"customMap",
1123+
Value.newBuilder()
1124+
.setMapValue(
1125+
MapValue.newBuilder().putAllFields(SINGLE_FIELD_PROTO))
1126+
.build())
1127+
.build())
1128+
.build())
1129+
.build();
1130+
doAnswer(getAllResponse(CUSTOM_MAP_PROTO))
1131+
.when(firestoreMock)
1132+
.streamRequest(
1133+
getAllCapture.capture(),
1134+
streamObserverCapture.capture(),
1135+
Matchers.<ServerStreamingCallable>any());
1136+
DocumentSnapshot snapshot = documentReference.get().get();
1137+
LocalFirestoreHelper.CustomMap customMap =
1138+
snapshot.toObject(LocalFirestoreHelper.CustomMap.class);
1139+
1140+
assertEquals(FOO_MAP, customMap.fooMap);
1141+
assertEquals(SINGLE_FIELD_OBJECT, customMap.fooMap.get("customMap"));
1142+
}
10761143
}

google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ public final class LocalFirestoreHelper {
143143
public static final Timestamp TIMESTAMP;
144144
public static final GeoPoint GEO_POINT;
145145
public static final Blob BLOB;
146+
public static final FooList<SingleField> FOO_LIST = new FooList<>();
147+
public static final FooMap<String, SingleField> FOO_MAP = new FooMap<>();
146148

147149
public static final Precondition UPDATE_PRECONDITION;
148150

@@ -165,6 +167,30 @@ public boolean equals(Object o) {
165167
}
166168
}
167169

170+
public static class FooList<E> extends ArrayList<SingleField> {
171+
public FooList() {
172+
super();
173+
}
174+
}
175+
176+
public static class CustomList {
177+
public CustomList() {}
178+
179+
public FooList<SingleField> fooList;
180+
}
181+
182+
public static class FooMap<K, V> extends HashMap<K, V> {
183+
public FooMap() {
184+
super();
185+
}
186+
}
187+
188+
public static class CustomMap {
189+
public CustomMap() {}
190+
191+
public FooMap<String, SingleField> fooMap;
192+
}
193+
168194
public static class NestedClass {
169195
public SingleField first = new SingleField();
170196
public AllSupportedTypes second = new AllSupportedTypes();
@@ -773,6 +799,8 @@ public boolean equals(Object o) {
773799
SINGLE_FIELD_MAP = map("foo", (Object) "bar");
774800
SINGLE_FILED_MAP_WITH_DOT = map("c.d", (Object) "bar");
775801
SINGLE_FIELD_OBJECT = new SingleField();
802+
FOO_LIST.add(SINGLE_FIELD_OBJECT);
803+
FOO_MAP.put("customMap", SINGLE_FIELD_OBJECT);
776804
SINGLE_FIELD_PROTO = map("foo", Value.newBuilder().setStringValue("bar").build());
777805
UPDATED_POJO_PROTO =
778806
map(

google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package com.google.cloud.firestore.it;
1818

19+
import static com.google.cloud.firestore.LocalFirestoreHelper.FOO_LIST;
20+
import static com.google.cloud.firestore.LocalFirestoreHelper.FOO_MAP;
1921
import static com.google.cloud.firestore.LocalFirestoreHelper.UPDATE_SINGLE_FIELD_OBJECT;
2022
import static com.google.cloud.firestore.LocalFirestoreHelper.map;
2123
import static com.google.common.truth.Truth.assertThat;
@@ -1402,6 +1404,34 @@ public Void updateCallback(Transaction transaction) throws Exception {
14021404
}
14031405
}
14041406

1407+
@Test
1408+
public void deserializeCustomList() throws Exception {
1409+
LocalFirestoreHelper.CustomList customList = new LocalFirestoreHelper.CustomList();
1410+
customList.fooList = FOO_LIST;
1411+
DocumentReference documentReference = randomColl.document("doc1");
1412+
documentReference.set(customList).get();
1413+
DocumentSnapshot documentSnapshots = documentReference.get().get();
1414+
LocalFirestoreHelper.CustomList targetCustomList =
1415+
documentSnapshots.toObject(LocalFirestoreHelper.CustomList.class);
1416+
1417+
assertEquals(FOO_LIST, targetCustomList.fooList);
1418+
assertEquals(SINGLE_FIELD_OBJECT, targetCustomList.fooList.get(0));
1419+
}
1420+
1421+
@Test
1422+
public void deserializeCustomMap() throws Exception {
1423+
LocalFirestoreHelper.CustomMap customMap = new LocalFirestoreHelper.CustomMap();
1424+
customMap.fooMap = FOO_MAP;
1425+
DocumentReference documentReference = randomColl.document("doc1");
1426+
documentReference.set(customMap).get();
1427+
DocumentSnapshot documentSnapshots = documentReference.get().get();
1428+
LocalFirestoreHelper.CustomMap targetCustomMap =
1429+
documentSnapshots.toObject(LocalFirestoreHelper.CustomMap.class);
1430+
1431+
assertEquals(FOO_MAP, targetCustomMap.fooMap);
1432+
assertEquals(SINGLE_FIELD_OBJECT, targetCustomMap.fooMap.get("customMap"));
1433+
}
1434+
14051435
/** Wrapper around ApiStreamObserver that returns the results in a list. */
14061436
private static class StreamConsumer<T> implements ApiStreamObserver<T> {
14071437
SettableApiFuture<List<T>> done = SettableApiFuture.create();

0 commit comments

Comments
 (0)