[ReactNative] Implement merge functionality for AsyncStorage
This commit is contained in:
parent
9fe7128493
commit
7ffa7bd1f1
|
@ -16,12 +16,19 @@ var {
|
|||
View,
|
||||
} = React;
|
||||
|
||||
var deepDiffer = require('deepDiffer');
|
||||
|
||||
var DEBUG = false;
|
||||
|
||||
var KEY_1 = 'key_1';
|
||||
var VAL_1 = 'val_1';
|
||||
var KEY_2 = 'key_2';
|
||||
var VAL_2 = 'val_2';
|
||||
var KEY_MERGE = 'key_merge';
|
||||
var VAL_MERGE_1 = {'foo': 1, 'bar': {'hoo': 1, 'boo': 1}, 'moo': {'a': 3}};
|
||||
var VAL_MERGE_2 = {'bar': {'hoo': 2}, 'baz': 2, 'moo': {'a': 3}};
|
||||
var VAL_MERGE_EXPECT =
|
||||
{'foo': 1, 'bar': {'hoo': 2, 'boo': 1}, 'baz': 2, 'moo': {'a': 3}};
|
||||
|
||||
// setup in componentDidMount
|
||||
var done;
|
||||
|
@ -40,8 +47,9 @@ function expectTrue(condition, message) {
|
|||
|
||||
function expectEqual(lhs, rhs, testname) {
|
||||
expectTrue(
|
||||
lhs === rhs,
|
||||
'Error in test ' + testname + ': expected ' + rhs + ', got ' + lhs
|
||||
!deepDiffer(lhs, rhs),
|
||||
'Error in test ' + testname + ': expected\n' + JSON.stringify(rhs) +
|
||||
'\ngot\n' + JSON.stringify(lhs)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -93,25 +101,25 @@ function testRemoveItem() {
|
|||
'Missing KEY_1 or KEY_2 in ' + '(' + result + ')'
|
||||
);
|
||||
updateMessage('testRemoveItem - add two items');
|
||||
AsyncStorage.removeItem(KEY_1, (err) => {
|
||||
expectAsyncNoError(err);
|
||||
AsyncStorage.removeItem(KEY_1, (err2) => {
|
||||
expectAsyncNoError(err2);
|
||||
updateMessage('delete successful ');
|
||||
AsyncStorage.getItem(KEY_1, (err, result) => {
|
||||
expectAsyncNoError(err);
|
||||
AsyncStorage.getItem(KEY_1, (err3, result2) => {
|
||||
expectAsyncNoError(err3);
|
||||
expectEqual(
|
||||
result,
|
||||
result2,
|
||||
null,
|
||||
'testRemoveItem: key_1 present after delete'
|
||||
);
|
||||
updateMessage('key properly removed ');
|
||||
AsyncStorage.getAllKeys((err, result2) => {
|
||||
expectAsyncNoError(err);
|
||||
AsyncStorage.getAllKeys((err4, result3) => {
|
||||
expectAsyncNoError(err4);
|
||||
expectTrue(
|
||||
result2.indexOf(KEY_1) === -1,
|
||||
'Unexpected: KEY_1 present in ' + result2
|
||||
result3.indexOf(KEY_1) === -1,
|
||||
'Unexpected: KEY_1 present in ' + result3
|
||||
);
|
||||
updateMessage('proper length returned.\nDone!');
|
||||
done();
|
||||
updateMessage('proper length returned.');
|
||||
runTestCase('should merge values', testMerge);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -120,6 +128,21 @@ function testRemoveItem() {
|
|||
});
|
||||
}
|
||||
|
||||
function testMerge() {
|
||||
AsyncStorage.setItem(KEY_MERGE, JSON.stringify(VAL_MERGE_1), (err1) => {
|
||||
expectAsyncNoError(err1);
|
||||
AsyncStorage.mergeItem(KEY_MERGE, JSON.stringify(VAL_MERGE_2), (err2) => {
|
||||
expectAsyncNoError(err2);
|
||||
AsyncStorage.getItem(KEY_MERGE, (err3, result) => {
|
||||
expectAsyncNoError(err3);
|
||||
expectEqual(JSON.parse(result), VAL_MERGE_EXPECT, 'testMerge');
|
||||
updateMessage('objects deeply merged\nDone!');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var AsyncStorageTest = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
// Utility functions for JSON object <-> string serialization/deserialization
|
||||
RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error);
|
||||
RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error);
|
||||
RCT_EXTERN id RCTJSONParseMutable(NSString *jsonString, NSError **error);
|
||||
RCT_EXTERN id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJSONReadingOptions options);
|
||||
|
||||
// Strip non JSON-safe values from an object graph
|
||||
RCT_EXTERN id RCTJSONClean(id object);
|
||||
|
|
|
@ -24,7 +24,7 @@ NSString *RCTJSONStringify(id jsonObject, NSError **error)
|
|||
return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil;
|
||||
}
|
||||
|
||||
id RCTJSONParse(NSString *jsonString, NSError **error)
|
||||
id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJSONReadingOptions options)
|
||||
{
|
||||
if (!jsonString) {
|
||||
return nil;
|
||||
|
@ -39,7 +39,15 @@ id RCTJSONParse(NSString *jsonString, NSError **error)
|
|||
return nil;
|
||||
}
|
||||
}
|
||||
return [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:error];
|
||||
return [NSJSONSerialization JSONObjectWithData:jsonData options:options error:error];
|
||||
}
|
||||
|
||||
id RCTJSONParse(NSString *jsonString, NSError **error) {
|
||||
return RCTJSONParseWithOptions(jsonString, error, NSJSONReadingAllowFragments);
|
||||
}
|
||||
|
||||
id RCTJSONParseMutable(NSString *jsonString, NSError **error) {
|
||||
return RCTJSONParseWithOptions(jsonString, error, NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves);
|
||||
}
|
||||
|
||||
id RCTJSONClean(id object)
|
||||
|
|
|
@ -61,6 +61,34 @@ static id RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut
|
|||
return nil;
|
||||
}
|
||||
|
||||
// Only merges objects - all other types are just clobbered (including arrays)
|
||||
static void RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *source)
|
||||
{
|
||||
for (NSString *key in source) {
|
||||
id sourceValue = source[key];
|
||||
if ([sourceValue isKindOfClass:[NSDictionary class]]) {
|
||||
id destinationValue = destination[key];
|
||||
NSMutableDictionary *nestedDestination;
|
||||
if ([destinationValue classForCoder] == [NSMutableDictionary class]) {
|
||||
nestedDestination = destinationValue;
|
||||
} else {
|
||||
if ([destinationValue isKindOfClass:[NSDictionary class]]) {
|
||||
// Ideally we wouldn't eagerly copy here...
|
||||
nestedDestination = [destinationValue mutableCopy];
|
||||
} else {
|
||||
destination[key] = [sourceValue copy];
|
||||
}
|
||||
}
|
||||
if (nestedDestination) {
|
||||
RCTMergeRecursive(nestedDestination, sourceValue);
|
||||
destination[key] = nestedDestination;
|
||||
}
|
||||
} else {
|
||||
destination[key] = sourceValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - RCTAsyncLocalStorage
|
||||
|
||||
@implementation RCTAsyncLocalStorage
|
||||
|
@ -135,13 +163,19 @@ RCT_EXPORT_MODULE()
|
|||
if (errorOut) {
|
||||
return errorOut;
|
||||
}
|
||||
id value = [self _getValueForKey:key errorOut:&errorOut];
|
||||
[result addObject:@[key, value ?: [NSNull null]]]; // Insert null if missing or failure.
|
||||
return errorOut;
|
||||
}
|
||||
|
||||
- (NSString *)_getValueForKey:(NSString *)key errorOut:(NSDictionary **)errorOut
|
||||
{
|
||||
id value = _manifest[key]; // nil means missing, null means there is a data file, anything else is an inline value.
|
||||
if (value == [NSNull null]) {
|
||||
NSString *filePath = [self _filePathForKey:key];
|
||||
value = RCTReadFile(filePath, key, &errorOut);
|
||||
value = RCTReadFile(filePath, key, errorOut);
|
||||
}
|
||||
[result addObject:@[key, value ?: [NSNull null]]]; // Insert null if missing or failure.
|
||||
return errorOut;
|
||||
return value;
|
||||
}
|
||||
|
||||
- (id)_writeEntry:(NSArray *)entry
|
||||
|
@ -198,7 +232,6 @@ RCT_EXPORT_METHOD(multiGet:(NSArray *)keys
|
|||
id keyError = [self _appendItemForKey:key toArray:result];
|
||||
RCTAppendError(keyError, &errors);
|
||||
}
|
||||
[self _writeManifest:&errors];
|
||||
callback(@[errors ?: [NSNull null], result]);
|
||||
}
|
||||
|
||||
|
@ -221,6 +254,38 @@ RCT_EXPORT_METHOD(multiSet:(NSArray *)kvPairs
|
|||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(multiMerge:(NSArray *)kvPairs
|
||||
callback:(RCTResponseSenderBlock)callback)
|
||||
{
|
||||
id errorOut = [self _ensureSetup];
|
||||
if (errorOut) {
|
||||
callback(@[@[errorOut]]);
|
||||
return;
|
||||
}
|
||||
NSMutableArray *errors;
|
||||
for (__strong NSArray *entry in kvPairs) {
|
||||
id keyError;
|
||||
NSString *value = [self _getValueForKey:entry[0] errorOut:&keyError];
|
||||
if (keyError) {
|
||||
RCTAppendError(keyError, &errors);
|
||||
} else {
|
||||
if (value) {
|
||||
NSMutableDictionary *mergedVal = [RCTJSONParseMutable(value, &keyError) mutableCopy];
|
||||
RCTMergeRecursive(mergedVal, RCTJSONParse(entry[1], &keyError));
|
||||
entry = @[entry[0], RCTJSONStringify(mergedVal, &keyError)];
|
||||
}
|
||||
if (!keyError) {
|
||||
keyError = [self _writeEntry:entry];
|
||||
}
|
||||
RCTAppendError(keyError, &errors);
|
||||
}
|
||||
}
|
||||
[self _writeManifest:&errors];
|
||||
if (callback) {
|
||||
callback(@[errors ?: [NSNull null]]);
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys
|
||||
callback:(RCTResponseSenderBlock)callback)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue