@firecms/core
Version:
Awesome Firebase/Firestore-based headless open-source CMS
193 lines (176 loc) • 6.76 kB
text/typescript
import hash from "object-hash";
import { GeoPoint } from "../types";
export const pick: <T>(obj: T, ...args: any[]) => T = (obj: any, ...args: any[]) => ({
...args.reduce((res, key) => ({
...res,
[key]: obj[key]
}), {})
});
export function isObject(item: any) {
return item && typeof item === "object" && !Array.isArray(item);
}
export function mergeDeep<T extends Record<any, any>, U extends Record<any, any>>(target: T, source: U, ignoreUndefined: boolean = false): T & U {
const targetIsObject = isObject(target);
const output = targetIsObject ? { ...target } : target;
if (targetIsObject && isObject(source)) {
Object.keys(source).forEach(key => {
const sourceElement = source[key];
// Skip undefined values when ignoreUndefined is true
if (ignoreUndefined && sourceElement === undefined) {
return;
}
if (sourceElement instanceof Date) {
// Assign a new Date instance with the same time value
Object.assign(output, { [key]: new Date(sourceElement.getTime()) });
} else if (isObject(sourceElement)) {
if (!(key in target))
Object.assign(output, { [key]: sourceElement });
else
(output as any)[key] = mergeDeep((target as any)[key], sourceElement);
} else {
Object.assign(output, { [key]: sourceElement });
}
});
}
return output as T;
}
export function getValueInPath(o: object | undefined, path: string): any {
if (!o) return undefined;
if (typeof o === "object") {
if (path in o) {
return (o as any)[path];
}
if (path.includes(".") || path.includes("[")) {
let pathSegments = path.split(/[.[]/);
if (path.includes("[")) {
pathSegments = pathSegments.map(segment => segment.replace("]", ""));
}
const firstSegment = pathSegments[0];
const isArrayAndIndexExists = Array.isArray((o as any)[firstSegment]) && !isNaN(parseInt(pathSegments[1]));
const nextObject = isArrayAndIndexExists
? (o as any)[firstSegment][parseInt(pathSegments[1])]
: (o as any)[firstSegment];
const nextPath = pathSegments.slice(isArrayAndIndexExists ? 2 : 1).join(".");
if (nextPath === "")
return nextObject;
return getValueInPath(nextObject, nextPath)
}
}
return undefined;
}
export function removeInPath(o: object, path: string): object | undefined {
let currentObject = { ...o };
const parts = path.split(".");
const last = parts.pop();
for (const part of parts) {
currentObject = (currentObject as any)[part]
}
if (last)
delete (currentObject as any)[last];
return currentObject;
}
export function removeFunctions(o: object | undefined): any {
if (o === undefined) return undefined;
if (o === null) return null;
if (typeof o === "object") {
return Object.entries(o)
.filter(([_, value]) => typeof value !== "function")
.map(([key, value]) => {
if (Array.isArray(value)) {
return { [key]: value.map(v => removeFunctions(v)) };
} else if (typeof value === "object") {
return { [key]: removeFunctions(value) };
} else return { [key]: value };
})
.reduce((a, b) => ({ ...a, ...b }), {});
}
return o;
}
export function getHashValue<T>(v: T) {
if (!v) return null;
if (typeof v === "object") {
if ("id" in v)
return (v as any).id;
else if (v instanceof Date)
return v.toLocaleString();
else if (v instanceof GeoPoint)
return hash(v as Record<string, unknown>);
}
return hash(v, { ignoreUnknown: true });
}
export function removeUndefined(value: any, removeEmptyStrings?: boolean): any {
if (typeof value === "function") {
return value;
}
if (Array.isArray(value)) {
return value.map((v: any) => removeUndefined(v, removeEmptyStrings));
}
if (typeof value === "object") {
const res: object = {};
if (value === null)
return value;
Object.keys(value).forEach((key) => {
if (!isEmptyObject(value)) {
const childRes = removeUndefined(value[key], removeEmptyStrings);
const isString = typeof childRes === "string";
const shouldKeepIfString = !removeEmptyStrings || (removeEmptyStrings && !isString) || (removeEmptyStrings && isString && childRes !== "");
if (childRes !== undefined && !isEmptyObject(childRes) && shouldKeepIfString)
(res as any)[key] = childRes;
}
});
return res;
}
return value;
}
export function removeNulls(value: any): any {
if (typeof value === "function") {
return value;
}
if (Array.isArray(value)) {
return value.map((v: any) => removeNulls(v));
}
if (typeof value === "object") {
const res: object = {};
if (value === null)
return value;
Object.keys(value).forEach((key) => {
if (value[key] !== null)
(res as any)[key] = removeNulls(value[key]);
});
return res;
}
return value;
}
export function isEmptyObject(obj: object) {
return obj &&
Object.getPrototypeOf(obj) === Object.prototype &&
Object.keys(obj).length === 0
}
export function removePropsIfExisting(source: any, comparison: any) {
const isObject = (val: any) => typeof val === "object" && val !== null;
const isArray = (val: any) => Array.isArray(val);
if (!isObject(source) || !isObject(comparison)) {
return source;
}
const res = isArray(source) ? [...source] : { ...source };
if (isArray(res)) {
for (let i = res.length - 1; i >= 0; i--) {
if (res[i] === comparison[i]) {
res.splice(i, 1);
} else if (isObject(res[i]) && isObject(comparison[i])) {
res[i] = removePropsIfExisting(res[i], comparison[i]);
}
}
} else {
Object.keys(comparison).forEach(key => {
if (key in res) {
if (isObject(res[key]) && isObject(comparison[key])) {
res[key] = removePropsIfExisting(res[key], comparison[key]);
} else if (res[key] === comparison[key]) {
delete res[key];
}
}
});
}
return res;
}