Angular-RU/angular-ru-sdk

View on GitHub
libs/cdk/object/src/utils.ts

Summary

Maintainability
C
1 day
Test Coverage
import { PlainObject, PlainObjectOf } from '@angular-ru/cdk/typings';
import { checkValueIsEmpty, isTrue } from '@angular-ru/cdk/utils';

import {
    ComparableKeys,
    ExtraObjectOptions,
    ObjectExtraOptions,
    ObjectReduceOptions
} from './internal/extra-object-options';
import { isObject } from './is-object';

export function unwrap<T>(target: T, options: ObjectExtraOptions): T {
    return Object.keys(target)
        .sort()
        .reduce(
            (accumulator: T, key: string): T =>
                deepObjectReduce<T>({
                    key,
                    accumulator,
                    targetValue: (target as any)?.[key],
                    options
                }),
            {} as T
        );
}

export function flatten<T = string>(object: PlainObject, excludeKeys: string[] = []): PlainObjectOf<T> {
    const depthGraph: PlainObjectOf<T> = {};

    for (const key in object) {
        if (object.hasOwnProperty(key) && !excludeKeys.includes(key)) {
            mutate<T>(object, depthGraph, key);
        }
    }

    return depthGraph;
}

export function mutate<T>(object: PlainObject, depthGraph: PlainObjectOf<T>, key: string): void {
    const isObjectLike: boolean = typeof object[key] === 'object' && object[key] !== null!;

    if (isObjectLike) {
        const flatObject: PlainObject = flatten(object[key]);

        for (const path in flatObject) {
            if (flatObject.hasOwnProperty(path)) {
                depthGraph[`${key}.${path}`] = flatObject[path];
            }
        }
    } else {
        depthGraph[key] = object[key];
    }
}

export function deepObjectReduce<T>({ accumulator, key, targetValue, options }: ObjectReduceOptions<T>): T {
    let value: T | string = targetValue;

    if (isTrue(options.weekType)) {
        const isComplexType: boolean = typeof targetValue === 'object' && targetValue !== null!;

        value = isComplexType ? targetValue : String(checkValueIsEmpty(targetValue) ? null : targetValue);
    }

    (accumulator as PlainObject)[key] = getComparableValue(options, value);

    return accumulator;
}

export function comparable<T>(target: T | T[] | string, options: ObjectExtraOptions = {}): ComparableKeys<T> {
    let result: ComparableKeys<T> | string = target;

    if (!isObject(result)) {
        return result as string;
    }

    if (Array.isArray(target)) {
        result = target.map((value: T): T => getComparableValue(options, value) as T);
    } else {
        result = unwrap<T>(target as T, options);
    }

    return result as T;
}

export function objectToString<T>(object: T, options: Partial<ExtraObjectOptions> = {}): string {
    return JSON.stringify(comparable(object, options));
}

export function equals<T, K>(a: T, b: K, options: ObjectExtraOptions = {}): boolean {
    return objectToString<T>(a, options) === objectToString<K>(b, options);
}

export function shallowTrimProperties<T>(object: PlainObjectOf<any>): T {
    return Object.entries(object).reduce((result: T, [key, value]: [string, any]): T => {
        // note: don't use isString for preserve circular dependencies
        (result as any)[key] = typeof value === 'string' ? value.trim() : value;

        return result;
    }, {} as T);
}

export function strictEquals<T, K>(a: T, b: K): boolean {
    const options: ObjectExtraOptions = { weekType: true, deep: true };

    return objectToString<T>(a, options) === objectToString<K>(b, options);
}

export function getComparableValue<T>(options: ObjectExtraOptions, value: T): ComparableKeys<T> {
    return isTrue(options.deep) ? comparable(value, options) : value;
}