import {Type, IType, ITypeParams} from "./Type"; import {Model} from "../Model"; import {isObject, isPlainObject, isNaN} from "../utils"; import {CircularStructureToJSONError} from "../errors"; import EqualStack from "../EqualStack"; // tslint:disable-next-line: no-empty-interface export interface IAnyTypeParams extends ITypeParams { } export interface IAnyType extends IType { (params: IAnyTypeParams): IAnyType; TOutput: any; TInput: any; TJson: any; } export class AnyType extends Type { toJSON(value: any, stack: any) { return value2json( value, stack ); } clone(value: any, stack: EqualStack) { return clone(value, stack); } equal(selfValue: any, otherValue: any, stack: EqualStack) { return equal(selfValue, otherValue, stack); } } export function equal(selfValue: any, otherValue: any, stack: EqualStack) { if ( selfValue instanceof Date && otherValue instanceof Date ) { return +selfValue === +otherValue; } if ( selfValue instanceof RegExp && otherValue instanceof RegExp ) { return selfValue.toString() === otherValue.toString(); } if ( Array.isArray(selfValue) && Array.isArray(otherValue) ) { if ( selfValue.length !== otherValue.length ) { return false; } // stop circular recursion const stacked = stack.get(selfValue); if ( stacked ) { return stacked === otherValue; } stack.add(selfValue, otherValue); for (let i = 0, n = selfValue.length; i < n; i++) { const selfItem = selfValue[ i ]; const otherItem = otherValue[ i ]; const isEqualItem = equal( selfItem, otherItem, stack ); if ( !isEqualItem ) { return false; } } return true; } if ( isPlainObject(selfValue) && isPlainObject(otherValue) ) { // stop circular recursion const stacked = stack.get(selfValue); if ( stacked ) { return true; } stack.add(selfValue, otherValue); const selfObj = selfValue; const otherObj = otherValue; for (const key in selfObj) { const myValue = selfObj[ key ]; const himValue = otherObj[ key ]; const isEqual = equal( myValue, himValue, stack ); if ( !isEqual ) { return false; } } // check additional keys from otherObj for (const key in otherObj) { if ( key in selfObj) { continue; } // exists unknown property for selfObj return false; } return true; } if ( selfValue instanceof Model && otherValue instanceof Model ) { const stacked = stack.get(selfValue); if ( stacked ) { return true; } stack.add( selfValue, otherValue ); return selfValue.equal( otherValue, stack ); } if ( isNaN(selfValue) && isNaN(otherValue) ) { return true; } return selfValue === otherValue; } export function value2json(value: any, stack: any): any { if ( value instanceof Date ) { return value.toISOString(); } if ( value && typeof value.toJSON === "function" ) { if ( stack.includes(value) ) { throw new CircularStructureToJSONError({}); } stack.push(value); return value.toJSON([...stack]); } if ( Array.isArray(value) ) { if ( stack.includes(value) ) { throw new CircularStructureToJSONError({}); } stack.push(value); return value.map((item) => value2json( item, [...stack] ) ); } if ( isObject(value) ) { if ( stack.includes(value) ) { throw new CircularStructureToJSONError({}); } stack.push(value); const json: any = {}; for (const key in value) { const item = value[ key ]; json[ key ] = value2json( item, [...stack] ); } return json; } return value; } export function clone(value: any, stack: EqualStack): any { if ( value instanceof Date ) { return new Date( +value ); } if ( value instanceof Model ) { return value.clone(stack); } if ( Array.isArray(value) ) { return value.map((item) => clone( item, stack ) ); } if ( isObject(value) ) { const cloneObj: any = {}; for (const key in value) { const item = value[ key ]; cloneObj[ key ] = clone( item, stack ); } return cloneObj; } return value; }