mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
4.1 KiB
159 lines
4.1 KiB
import { Big } from 'big.js';
|
|
import { cloneDeep, isArray, isObject } from 'lodash';
|
|
|
|
export function hasNotDefinedValuesInObject(aObject: Object): boolean {
|
|
for (const key in aObject) {
|
|
if (aObject[key] === null || aObject[key] === undefined) {
|
|
return true;
|
|
} else if (isObject(aObject[key])) {
|
|
return hasNotDefinedValuesInObject(aObject[key]);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
export function nullifyValuesInObject<T>(aObject: T, keys: string[]): T {
|
|
const object = cloneDeep(aObject);
|
|
|
|
if (object) {
|
|
keys.forEach((key) => {
|
|
object[key] = null;
|
|
});
|
|
}
|
|
|
|
return object;
|
|
}
|
|
|
|
export function nullifyValuesInObjects<T>(aObjects: T[], keys: string[]): T[] {
|
|
return aObjects.map((object) => {
|
|
return nullifyValuesInObject(object, keys);
|
|
});
|
|
}
|
|
|
|
// Helper: Custom fast clone (faster than structuredClone for our use case)
|
|
function fastClone(obj: any, seen = new WeakMap()): any {
|
|
// Handle primitives and null
|
|
if (obj === null || typeof obj !== 'object') {
|
|
return obj;
|
|
}
|
|
|
|
// Don't clone Big.js instances
|
|
if (obj instanceof Big) {
|
|
return obj;
|
|
}
|
|
|
|
// Handle circular references
|
|
if (seen.has(obj)) {
|
|
return seen.get(obj);
|
|
}
|
|
|
|
// Handle arrays
|
|
if (isArray(obj)) {
|
|
const arr: any[] = [];
|
|
seen.set(obj, arr);
|
|
for (let i = 0; i < obj.length; i++) {
|
|
arr[i] = fastClone(obj[i], seen);
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
// Handle objects
|
|
const cloned: any = {};
|
|
seen.set(obj, cloned);
|
|
const keys = Object.keys(obj);
|
|
for (let i = 0; i < keys.length; i++) {
|
|
const key = keys[i];
|
|
cloned[key] = fastClone(obj[key], seen);
|
|
}
|
|
return cloned;
|
|
}
|
|
|
|
export function redactAttributes({
|
|
isFirstRun = true,
|
|
object,
|
|
options
|
|
}: {
|
|
isFirstRun?: boolean;
|
|
object: any;
|
|
options: { attribute: string; valueMap: { [key: string]: any } }[];
|
|
}): any {
|
|
if (!object || !options?.length) {
|
|
return object;
|
|
}
|
|
|
|
// Optimization 1: Use custom fast clone instead of structuredClone
|
|
const redactedObject = isFirstRun ? fastClone(object) : object;
|
|
|
|
// Optimization 2: Pre-process options and separate wildcards from conditional mappings
|
|
const wildcardAttrs = new Map<string, any>();
|
|
const conditionalAttrs = new Map<string, { [key: string]: any }>();
|
|
|
|
for (const opt of options) {
|
|
if ('*' in opt.valueMap) {
|
|
wildcardAttrs.set(opt.attribute, opt.valueMap['*']);
|
|
} else {
|
|
conditionalAttrs.set(opt.attribute, opt.valueMap);
|
|
}
|
|
}
|
|
|
|
// Optimization 3: Use iterative traversal with pointer-based queue
|
|
const workQueue: any[] = [redactedObject];
|
|
let queueIndex = 0;
|
|
|
|
while (queueIndex < workQueue.length) {
|
|
const current = workQueue[queueIndex++];
|
|
|
|
// Skip null/undefined
|
|
if (current == null) {
|
|
continue;
|
|
}
|
|
|
|
// Process wildcard attributes first (most common case)
|
|
for (const [attribute, replacementValue] of wildcardAttrs) {
|
|
if (attribute in current) {
|
|
current[attribute] = replacementValue;
|
|
}
|
|
}
|
|
|
|
// Process conditional attributes
|
|
for (const [attribute, valueMap] of conditionalAttrs) {
|
|
if (attribute in current) {
|
|
const currentValue = current[attribute];
|
|
if (currentValue in valueMap) {
|
|
current[attribute] = valueMap[currentValue];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Optimization 4: Use Object.keys() instead of for...in (faster, no inherited props)
|
|
const keys = Object.keys(current);
|
|
for (let i = 0; i < keys.length; i++) {
|
|
const key = keys[i];
|
|
const value = current[key];
|
|
|
|
// Optimization 5: Cache type check
|
|
const valueType = typeof value;
|
|
if (valueType !== 'object' || value === null) {
|
|
continue;
|
|
}
|
|
|
|
if (isArray(value)) {
|
|
// Optimization 6: Batch array processing
|
|
if (value.length > 0) {
|
|
for (let j = 0; j < value.length; j++) {
|
|
const item = value[j];
|
|
if (item != null && typeof item === 'object') {
|
|
workQueue.push(item);
|
|
}
|
|
}
|
|
}
|
|
} else if (!(value instanceof Big)) {
|
|
// Push object to queue (Big.js instances excluded)
|
|
workQueue.push(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
return redactedObject;
|
|
}
|
|
|