diff --git a/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java b/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java index c7925bbf..7945a766 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java +++ b/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java @@ -274,7 +274,7 @@ public class FirestoreSerialize { return null; } } else { - Log.e(TAG, "parseTypeMap", new RuntimeException("Cannot convert object of type " + type)); + Log.e(TAG, "parseTypeMap: Cannot convert object of type " + type); return null; } } diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestore.m b/ios/RNFirebase/firestore/RNFirebaseFirestore.m index ed056a1c..f002f490 100644 --- a/ios/RNFirebase/firestore/RNFirebaseFirestore.m +++ b/ios/RNFirebase/firestore/RNFirebaseFirestore.m @@ -58,7 +58,7 @@ RCT_EXPORT_METHOD(documentBatch:(NSString *) appName for (NSDictionary *write in writes) { NSString *type = write[@"type"]; NSString *path = write[@"path"]; - NSDictionary *data = write[@"data"]; + NSDictionary *data = [RNFirebaseFirestoreDocumentReference parseJSMap:firestore jsMap:write[@"data"]]; FIRDocumentReference *ref = [firestore documentWithPath:path]; diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h index 238388f6..98f70b3f 100644 --- a/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h @@ -27,6 +27,7 @@ - (void)update:(NSDictionary *)data resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; - (BOOL)hasListeners; + (NSDictionary *)snapshotToDictionary:(FIRDocumentSnapshot *)documentSnapshot; ++(NSDictionary *)parseJSMap:(FIRFirestore *) firestore jsMap:(NSDictionary *) jsMap; @end #else diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m index 0a458731..e27b3349 100644 --- a/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m @@ -89,12 +89,13 @@ static NSMutableDictionary *_listeners; options:(NSDictionary *) options resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject { + NSDictionary *dictionary = [RNFirebaseFirestoreDocumentReference parseJSMap:[RNFirebaseFirestore getFirestoreForApp:_app] jsMap:data]; if (options && options[@"merge"]) { - [_ref setData:data options:[FIRSetOptions merge] completion:^(NSError * _Nullable error) { + [_ref setData:dictionary options:[FIRSetOptions merge] completion:^(NSError * _Nullable error) { [RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject]; }]; } else { - [_ref setData:data completion:^(NSError * _Nullable error) { + [_ref setData:dictionary completion:^(NSError * _Nullable error) { [RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject]; }]; } @@ -103,7 +104,8 @@ static NSMutableDictionary *_listeners; - (void)update:(NSDictionary *) data resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject { - [_ref updateData:data completion:^(NSError * _Nullable error) { + NSDictionary *dictionary = [RNFirebaseFirestoreDocumentReference parseJSMap:[RNFirebaseFirestore getFirestoreForApp:_app] jsMap:data]; + [_ref updateData:dictionary completion:^(NSError * _Nullable error) { [RNFirebaseFirestoreDocumentReference handleWriteResponse:error resolver:resolve rejecter:reject]; }]; } @@ -126,7 +128,7 @@ static NSMutableDictionary *_listeners; NSMutableDictionary *snapshot = [[NSMutableDictionary alloc] init]; [snapshot setValue:documentSnapshot.reference.path forKey:@"path"]; if (documentSnapshot.exists) { - [snapshot setValue:documentSnapshot.data forKey:@"data"]; + [snapshot setValue:[RNFirebaseFirestoreDocumentReference buildNativeMap:documentSnapshot.data] forKey:@"data"]; } if (documentSnapshot.metadata) { NSMutableDictionary *metadata = [[NSMutableDictionary alloc] init]; @@ -159,6 +161,121 @@ static NSMutableDictionary *_listeners; [_emitter sendEventWithName:FIRESTORE_DOCUMENT_SYNC_EVENT body:event]; } + ++ (NSDictionary *)buildNativeMap:(NSDictionary *)nativeMap { + NSMutableDictionary *map = [[NSMutableDictionary alloc] init]; + [nativeMap enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { + NSDictionary *typeMap = [RNFirebaseFirestoreDocumentReference buildTypeMap:obj]; + map[key] = typeMap; + }]; + + return map; +} + ++ (NSArray *)buildNativeArray:(NSArray *)nativeArray { + NSMutableArray *array = [[NSMutableArray alloc] init]; + [nativeArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + NSDictionary *typeMap = [RNFirebaseFirestoreDocumentReference buildTypeMap:obj]; + [array addObject:typeMap]; + }]; + + return array; +} + ++ (NSDictionary *)buildTypeMap:(id) value { + NSMutableDictionary *typeMap = [[NSMutableDictionary alloc] init]; + if (!value) { + typeMap[@"type"] = @"null"; + } else if ([value isKindOfClass:[NSString class]]) { + typeMap[@"type"] = @"string"; + typeMap[@"value"] = value; + } else if ([value isKindOfClass:[NSDictionary class]]) { + typeMap[@"type"] = @"object"; + typeMap[@"value"] = [RNFirebaseFirestoreDocumentReference buildNativeMap:value]; + } else if ([value isKindOfClass:[NSArray class]]) { + typeMap[@"type"] = @"array"; + typeMap[@"value"] = [RNFirebaseFirestoreDocumentReference buildNativeArray:value]; + } else if ([value isKindOfClass:[FIRDocumentReference class]]) { + typeMap[@"type"] = @"reference"; + FIRDocumentReference *ref = (FIRDocumentReference *)value; + typeMap[@"value"] = [ref path]; + } else if ([value isKindOfClass:[FIRGeoPoint class]]) { + typeMap[@"type"] = @"geopoint"; + FIRGeoPoint *point = (FIRGeoPoint *)value; + NSMutableDictionary *geopoint = [[NSMutableDictionary alloc] init]; + geopoint[@"latitude"] = @([point latitude]); + geopoint[@"longitude"] = @([point longitude]); + typeMap[@"value"] = geopoint; + } else if ([value isKindOfClass:[NSDate class]]) { + typeMap[@"type"] = @"date"; + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; + typeMap[@"value"] = [dateFormatter stringFromDate:(NSDate *)value]; + } else if ([value isKindOfClass:[NSNumber class]]) { + NSNumber *number = (NSNumber *)value; + if (number == (void*)kCFBooleanFalse || number == (void*)kCFBooleanTrue) { + typeMap[@"type"] = @"boolean"; + } else { + typeMap[@"type"] = @"number"; + } + typeMap[@"value"] = value; + } else { + // TODO: Log an error + typeMap[@"type"] = @"null"; + } + + return typeMap; +} + ++(NSDictionary *)parseJSMap:(FIRFirestore *) firestore + jsMap:(NSDictionary *) jsMap { + NSMutableDictionary* map = [[NSMutableDictionary alloc] init]; + if (jsMap) { + [jsMap enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { + map[key] = [RNFirebaseFirestoreDocumentReference parseJSTypeMap:firestore jsTypeMap:obj]; + }]; + } + return map; +} + ++(NSArray *)parseJSArray:(FIRFirestore *) firestore + jsArray:(NSArray *) jsArray { + NSMutableArray* array = [[NSMutableArray alloc] init]; + if (jsArray) { + [jsArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + [array addObject:[RNFirebaseFirestoreDocumentReference parseJSTypeMap:firestore jsTypeMap:obj]]; + }]; + } + return array; +} + ++(id)parseJSTypeMap:(FIRFirestore *) firestore + jsTypeMap:(NSDictionary *) jsTypeMap { + NSString *type = jsTypeMap[@"type"]; + id value = jsTypeMap[@"value"]; + if ([type isEqualToString:@"array"]) { + return [RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:value]; + } else if ([type isEqualToString:@"object"]) { + return [RNFirebaseFirestoreDocumentReference parseJSMap:firestore jsMap:value]; + } else if ([type isEqualToString:@"reference"]) { + return [firestore documentWithPath:value]; + } else if ([type isEqualToString:@"geopoint"]) { + NSDictionary* geopoint = (NSDictionary*)value; + NSNumber *latitude = geopoint[@"latitude"]; + NSNumber *longitude = geopoint[@"longitude"]; + return [[FIRGeoPoint alloc] initWithLatitude:[latitude doubleValue] longitude:[longitude doubleValue]]; + } else if ([type isEqualToString:@"date"]) { + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; + return [dateFormatter dateFromString:value]; + } else if ([type isEqualToString:@"boolean"] || [type isEqualToString:@"number"] || [type isEqualToString:@"string"] || [type isEqualToString:@"null"]) { + return value; + } else { + // TODO: Log error + return nil; + } +} + #endif @end diff --git a/tests/src/tests/firestore/documentReferenceTests.js b/tests/src/tests/firestore/documentReferenceTests.js index 5232148a..bc4a39e0 100644 --- a/tests/src/tests/firestore/documentReferenceTests.js +++ b/tests/src/tests/firestore/documentReferenceTests.js @@ -412,6 +412,18 @@ function documentReferenceTests({ describe, it, context, firebase }) { }); }); + context('types', () => { + it('should handle Boolean field', async () => { + const docRef = firebase.native.firestore().doc('document-tests/reference'); + await docRef.set({ + field: true, + }); + + const doc = await docRef.get(); + should.equal(doc.data().field, true); + }); + }); + context('types', () => { it('should handle Date field', async () => { const docRef = firebase.native.firestore().doc('document-tests/reference');