@react-native-firebase/app
Version:
A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Admob, Analytics, Auth, Crash Reporting, Cloud Firestore, Database, Dynamic Links, Functions, Messaging (FCM), Remote Config, Sto
187 lines (169 loc) • 5.26 kB
text/typescript
/*
* Copyright (c) 2016-present Invertase Limited & Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this library except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
const NULL_SENTINEL = { __rnfbNull: true };
type Encodable = string | number | boolean | null | EncodableObject | EncodableArray;
type EncodableObject = { [key: string]: Encodable };
type EncodableArray = Encodable[];
type ArrayFrame = {
type: 'array';
original: unknown[];
encoded: unknown[];
index: number;
};
type ObjectFrame = {
type: 'object';
original: Record<string, unknown>;
encoded: Record<string, unknown>;
keys: string[];
index: number;
};
type StackFrame = ArrayFrame | ObjectFrame;
/**
* Replaces null values in object properties with sentinel objects for iOS TurboModule compatibility.
* Uses iterative stack-based traversal to avoid stack overflow on deeply nested structures.
*
* iOS TurboModules strip null values from object properties during serialization,
* so we replace them with sentinel objects that can survive the serialization
* and be detected/restored on the native side.
*
* Note: Null values in arrays are preserved by iOS TurboModules, so we don't
* encode them (but we still process nested objects within arrays).
*
* @param data - The data to encode
* @returns The encoded data with null object properties replaced by sentinels
*/
export function encodeNullValues(data: unknown): unknown {
if (data === null) {
// only null values within objects are encoded
return null;
}
if (typeof data !== 'object') {
return data;
}
// Helper to process a child element and add it to the encoded container
function processArrayChild(
child: unknown,
encoded: unknown[],
index: number,
stack: StackFrame[],
): void {
if (child === null) {
// Arrays preserve nulls as null
encoded[index] = null;
} else if (typeof child !== 'object') {
encoded[index] = child;
} else if (Array.isArray(child)) {
const childEncoded: unknown[] = new Array(child.length);
encoded[index] = childEncoded;
stack.push({
type: 'array',
original: child,
encoded: childEncoded,
index: 0,
});
} else {
const childEncoded: Record<string, unknown> = {};
encoded[index] = childEncoded;
stack.push({
type: 'object',
original: child as Record<string, unknown>,
encoded: childEncoded,
keys: Object.keys(child),
index: 0,
});
}
}
function processObjectChild(
child: unknown,
encoded: Record<string, unknown>,
key: string,
stack: StackFrame[],
): void {
if (child === null) {
// Objects convert null to sentinel
encoded[key] = NULL_SENTINEL;
} else if (typeof child !== 'object') {
encoded[key] = child;
} else if (Array.isArray(child)) {
const childEncoded: unknown[] = new Array(child.length);
encoded[key] = childEncoded;
stack.push({
type: 'array',
original: child,
encoded: childEncoded,
index: 0,
});
} else {
const childEncoded: Record<string, unknown> = {};
encoded[key] = childEncoded;
stack.push({
type: 'object',
original: child as Record<string, unknown>,
encoded: childEncoded,
keys: Object.keys(child),
index: 0,
});
}
}
// Prepare root encoded container
let rootEncoded: unknown[] | Record<string, unknown>;
const stack: StackFrame[] = [];
if (Array.isArray(data)) {
rootEncoded = new Array(data.length);
stack.push({
type: 'array',
original: data,
encoded: rootEncoded,
index: 0,
});
} else {
rootEncoded = {};
stack.push({
type: 'object',
original: data as Record<string, unknown>,
encoded: rootEncoded,
keys: Object.keys(data),
index: 0,
});
}
while (stack.length > 0) {
const frame = stack[stack.length - 1]!; // Non-null assertion safe due to while condition
if (frame.type === 'array') {
const { original, encoded } = frame;
if (frame.index >= original.length) {
// Done with this array
stack.pop();
continue;
}
const i = frame.index++;
const item = original[i];
processArrayChild(item, encoded, i, stack);
} else {
// frame.type === 'object'
const { original, encoded, keys } = frame;
if (frame.index >= keys.length) {
// Done with this object
stack.pop();
continue;
}
const key = keys[frame.index++]!; // Non-null assertion safe due to length check above
const value = original[key];
processObjectChild(value, encoded, key, stack);
}
}
return rootEncoded;
}