Skip to content
Snippets Groups Projects
Select Git revision
  • ce1dd137678aaee721c92dd3cfdf88a52001f9af
  • master default protected
  • 1.31
  • 4.38.2
  • 4.38.1
  • 4.38.0
  • 4.37.2
  • 4.37.1
  • 4.37.0
  • 4.36.0
  • 4.35.0
  • 4.34.1
  • 4.34.0
  • 4.33.1
  • 4.33.0
  • 4.32.2
  • 4.32.1
  • 4.32.0
  • 4.31.0
  • 4.30.1
  • 4.30.0
  • 4.29.1
  • 4.29.0
23 results

server.mjs

Blame
  • diff.js 5.24 KiB
    'use strict';
    
    /**
     * @author schukai GmbH
     */
    
    import {assignToNamespace, Monster} from '../namespace.js';
    import {isArray, isObject} from "../types/is.js";
    
    /**
     * With the diff function you can perform the change of one object to another. The result shows the changes of the second object to the first object.
     *
     * The operator `add` means that something has been added to the second object. `delete` means that something has been deleted from the second object compared to the first object.
     *
     * You can call the method via the monster namespace `Monster.Data.Diff()`.
     *
     * ```
     * <script type="module">
     * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.16.1/dist/modules/data/diff.js';
     * console.log(Monster.Data.Diff(a, b))
     * </script>
     * ```
     *
     * Alternatively, you can also integrate this function individually.
     *
     * ```
     * <script type="module">
     * import {Diff} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.16.1/dist/modules/data/diff.js';
     * console.log(Diff(a, b))
     * </script>
     * ```
     *
     * @example
     *
     * import {Diff} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.16.1/dist/modules/data/diff.js';
     *
     * // given are two objects x and y.
     *
     * let x = {
     *     a: 1,
     *     b: "Hello!"
     * }
     *
     *  let y = {
     *     a: 2,
     *     c: true
     * }
     *
     * // These two objects can be compared with each other.
     *
     * console.log(Diff(x, y));
     *
     * // the result is then the following
     *
     * //
     * // [
     * // {
     * //        operator: 'update',
     * //        path: [ 'a' ],
     * //        first: { value: 1, type: 'number' },
     * //        second: { value: 2, type: 'number' }
     * //    },
     * // {
     * //        operator: 'delete',
     * //        path: [ 'b' ],
     * //        first: { value: 'Hello!', type: 'string' }
     * //    },
     * // {
     * //        operator: 'add',
     * //        path: [ 'c' ],
     * //        second: { value: true, type: 'boolean' }
     * //    }
     * // ]
     *
     * @param {*} first
     * @param {*} second
     * @return {array}
     * @since 1.6.0
     * @copyright schukai GmbH
     * @memberOf Monster.Data
     */
    function Diff(first, second) {
        return doDiff(first, second)
    }
    
    /**
     * @private
     * @param a
     * @param b
     * @param type
     * @return {Set<string>|Set<number>}
     */
    function getKeys(a, b, type) {
        if (isArray(type)) {
            const keys = a.length > b.length ? new Array(a.length) : new Array(b.length);
            keys.fill(0);
            return new Set(keys.map((_, i) => i));
        }
    
        return new Set(Object.keys(a).concat(Object.keys(b)));
    }
    
    /**
     * @private
     * @param a
     * @param b
     * @param path
     * @param diff
     * @return {array}
     */
    function doDiff(a, b, path, diff) {
    
        let typeA = typeof a
        let typeB = typeof b
    
        const currPath = path || [];
        const currDiff = diff || [];
    
        if (typeA === typeB && typeA === 'object') { // array is object too
    
            getKeys(a, b, typeA).forEach((v) => {
    
                if (!(Object.prototype.hasOwnProperty.call(a, v))) {
                    currDiff.push(buildResult(a[v], b[v], 'add', currPath.concat(v)));
                } else if (!(Object.prototype.hasOwnProperty.call(b, v))) {
                    currDiff.push(buildResult(a[v], b[v], 'delete', currPath.concat(v)));
                } else {
                    doDiff(a[v], b[v], currPath.concat(v), currDiff);
                }
            });
    
        } else {
    
            const o = getOperator(a, b, typeA, typeB);
            if (o !== undefined) {
                currDiff.push(buildResult(a, b, o, path));
            }
    
        }
    
        return currDiff;
    
    }
    
    /**
     *
     * @param {*} a
     * @param {*} b
     * @param {string} operator
     * @param {array} path
     * @return {{path: array, operator: string}}
     * @private
     */
    function buildResult(a, b, operator, path) {
    
        const result = {
            operator,
            path,
        };
    
        if (operator !== 'add') {
            result.first = {
                value: a,
                type: typeof a
            };
    
            if (isObject(a)) {
                const name = Object.getPrototypeOf(a)?.constructor?.name;
                if (name !== undefined) {
                    result.first.instance = name;
                }
            }
        }
    
        if (operator === 'add' || operator === 'update') {
            result.second = {
                value: b,
                type: typeof b
            };
    
            if (isObject(b)) {
                const name = Object.getPrototypeOf(b)?.constructor?.name;
                if (name !== undefined) {
                    result.second.instance = name;
                }
            }
    
        }
    
        return result;
    }
    
    /**
     * @private
     * @param {*} a
     * @param {*} b
     * @return {boolean}
     */
    function isNotEqual(a, b) {
    
        if (typeof a !== typeof b) {
            return true;
        }
    
        if (a instanceof Date && b instanceof Date) {
            return a.getTime() !== b.getTime();
        }
    
        return a !== b;
    }
    
    /**
     * @private
     * @param {*} a
     * @param {*} b
     * @return {string|undefined}
     */
    function getOperator(a, b) {
    
        /**
         * @type {string|undefined}
         */
        let operator;
    
        /**
         * @type {string}
         */
        let typeA = typeof a;
    
        /**
         * @type {string}
         */
        let typeB = typeof b;
    
        if (typeA === 'undefined' && typeB !== 'undefined') {
            operator = 'add';
        } else if (typeA !== 'undefined' && typeB === 'undefined') {
            operator = 'delete';
        } else if (isNotEqual(a, b)) {
            operator = 'update';
        }
    
        return operator;
    
    }
    
    assignToNamespace('Monster.Data', Diff);
    export {Monster, Diff}