Skip to content

Commit

Permalink
Properly handle null values coming from JS (facebook#49250)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebook#49250

The TurboModule System decided to ignore the Null values when they are coming to JS. However, in iOS, null value can be mapped to `[NSNull null];` and this value is a valid value that can be used on the native side.

In the old architecture, when the user were sending a null value from JS to a native module, the Native side was receiving the value.

In the New Architecture, the value was stripped away.

This change allow us to handle the `null` value properly in the interop layer, to restore the usage of legacy modules in the New Arch.

I also tried with a more radical approach, but several tests were crashing because some modules do not know how to handle `NSNull`.

See discussion happening here: invertase/react-native-firebase#8144 (comment)

## Changelog:
[iOS][Changed] - Properly handle `null` values coming from NativeModules.

Reviewed By: sammy-SC

Differential Revision: D69301396

fbshipit-source-id: be275185e2643092f6c3dc2481fe9381bbcf69e9
  • Loading branch information
cipolleschi authored and facebook-github-bot committed Feb 10, 2025
1 parent e74246b commit d423679
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ T RCTConvertTo(SEL selector, id json)
SEL selector = selectorForType(argumentType);

if ([RCTConvert respondsToSelector:selector]) {
id objCArg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
id objCArg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);

if (objCArgType == @encode(char)) {
char arg = RCTConvertTo<char>(selector, objCArg);
Expand Down Expand Up @@ -500,7 +500,7 @@ T RCTConvertTo(SEL selector, id json)
}

RCTResponseSenderBlock arg =
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);
if (arg) {
[retainedObjectsForInvocation addObject:arg];
}
Expand All @@ -515,7 +515,7 @@ T RCTConvertTo(SEL selector, id json)
}

RCTResponseSenderBlock senderBlock =
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);
RCTResponseErrorBlock arg = ^(NSError *error) {
senderBlock(@[ RCTJSErrorFromNSError(error) ]);
};
Expand Down Expand Up @@ -545,7 +545,7 @@ T RCTConvertTo(SEL selector, id json)
runtime, errorPrefix + "JavaScript argument must be a plain object. Got " + getType(runtime, jsiArg));
}

id arg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
id arg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);

RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
RCTManagedPointer *box = convert([RCTCxxConvert class], selector, arg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ using EventEmitterCallback = std::function<void(const std::string &, id)>;
namespace TurboModuleConvertUtils {
jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value);
id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<CallInvoker> jsInvoker);
id convertJSIValueToObjCObject(
jsi::Runtime &runtime,
const jsi::Value &value,
std::shared_ptr<CallInvoker> jsInvoker,
BOOL useNSNull);
} // namespace TurboModuleConvertUtils

template <>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,29 +111,35 @@ static int32_t getUniqueId()
return [NSString stringWithUTF8String:value.utf8(runtime).c_str()];
}

static NSArray *
convertJSIArrayToNSArray(jsi::Runtime &runtime, const jsi::Array &value, std::shared_ptr<CallInvoker> jsInvoker)
static NSArray *convertJSIArrayToNSArray(
jsi::Runtime &runtime,
const jsi::Array &value,
std::shared_ptr<CallInvoker> jsInvoker,
BOOL useNSNull)
{
size_t size = value.size(runtime);
NSMutableArray *result = [NSMutableArray new];
for (size_t i = 0; i < size; i++) {
// Insert kCFNull when it's `undefined` value to preserve the indices.
id convertedObject = convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker);
id convertedObject = convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker, useNSNull);
[result addObject:convertedObject ? convertedObject : (id)kCFNull];
}
return [result copy];
}

static NSDictionary *
convertJSIObjectToNSDictionary(jsi::Runtime &runtime, const jsi::Object &value, std::shared_ptr<CallInvoker> jsInvoker)
static NSDictionary *convertJSIObjectToNSDictionary(
jsi::Runtime &runtime,
const jsi::Object &value,
std::shared_ptr<CallInvoker> jsInvoker,
BOOL useNSNull)
{
jsi::Array propertyNames = value.getPropertyNames(runtime);
size_t size = propertyNames.size(runtime);
NSMutableDictionary *result = [NSMutableDictionary new];
for (size_t i = 0; i < size; i++) {
jsi::String name = propertyNames.getValueAtIndex(runtime, i).getString(runtime);
NSString *k = convertJSIStringToNSString(runtime, name);
id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker);
id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker, useNSNull);
if (v) {
result[k] = v;
}
Expand Down Expand Up @@ -161,9 +167,21 @@ static int32_t getUniqueId()

id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<CallInvoker> jsInvoker)
{
if (value.isUndefined() || value.isNull()) {
return convertJSIValueToObjCObject(runtime, value, jsInvoker, NO);
}

id convertJSIValueToObjCObject(
jsi::Runtime &runtime,
const jsi::Value &value,
std::shared_ptr<CallInvoker> jsInvoker,
BOOL useNSNull)
{
if (value.isUndefined() || (value.isNull() && !useNSNull)) {
return nil;
}
if (value.isNull() && useNSNull) {
return [NSNull null];
}
if (value.isBool()) {
return @(value.getBool());
}
Expand All @@ -176,12 +194,12 @@ id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, s
if (value.isObject()) {
jsi::Object o = value.getObject(runtime);
if (o.isArray(runtime)) {
return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker);
return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker, useNSNull);
}
if (o.isFunction(runtime)) {
return convertJSIFunctionToCallback(runtime, o.getFunction(runtime), jsInvoker);
}
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker);
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker, useNSNull);
}

throw std::runtime_error("Unsupported jsi::Value kind");
Expand Down

0 comments on commit d423679

Please sign in to comment.