import { isFunction , isObject , keysOf , hasOwn , convertMapToObject , isArguments , handleError , checkError , } from "./utilities" ; import canonicalStringify from "./stringify" ; import { fromJSONValueHelper } from "./utils/fromJSONValueHelper" ; import { adjustTypesFromJSONValue } from "./utils/adjustTypesFromJSONValue" ; import { toJSONValueHelper } from "./utils/toJSONValueHelper" ; import { adjustTypesToJSONValue } from "./utils/adjustTypesToJSONValue" ; import { builtinConverters } from "./utils/builtinConverters" ; import { ObjectId } from "./objectid" ; export class EJSON { static customTypes = new Map ( ) ; summary locus param { String } name param { Function } factory static addType ( name , factory ) { if ( EJSON . customTypes . has ( name ) ) { throw new Error ( `Type ${ name } already present` ) ; } EJSON . customTypes . set ( name , factory ) ; } static _isCustomType ( obj ) { return ( obj && isFunction ( obj . toJSONValue ) && isFunction ( obj . typeName ) && EJSON . customTypes . has ( obj . typeName ( ) ) ) ; } static _getTypes ( isOriginal = false ) { return isOriginal ? EJSON . customTypes : convertMapToObject ( EJSON . customTypes ) ; } static _getConverters = ( ) => builtinConverters ; static _adjustTypesToJSONValue = adjustTypesToJSONValue ; summary locus param { EJSON } val static toJSONValue ( item ) { const changed = toJSONValueHelper ( item ) ; if ( changed !== undefined ) { return changed ; } let newItem = item ; if ( isObject ( item ) ) { newItem = EJSON . clone ( item ) ; adjustTypesToJSONValue ( newItem ) ; } return newItem ; } static _adjustTypesFromJSONValue = adjustTypesFromJSONValue ; summary locus param { JSONCompatible } val static fromJSONValue ( item ) { let changed = fromJSONValueHelper ( item ) ; if ( changed === item && isObject ( item ) ) { changed = EJSON . clone ( item ) ; adjustTypesFromJSONValue ( changed ) ; } return changed ; } summary locus param { EJSON } val param options param { Boolean | Integer | String } options . indent param { Boolean } options . canonical static stringify ( item , options ? ) { try { let serialized ; const json = EJSON . toJSONValue ( item ) ; if ( options && ( options . canonical || options . indent ) ) { serialized = canonicalStringify ( json , options ) ; } else { serialized = JSON . stringify ( json ) ; } return serialized ; } catch ( error ) { const isMaxStack = checkError . maxStack ( error . message ) ; if ( isMaxStack ) { throw new Error ( "Converting circular structure to JSON" ) ; } throw error ; } } summary locus param { String } str static parse ( item ) { if ( typeof item !== "string" ) { throw new Error ( "EJSON.parse argument should be a string" ) ; } return EJSON . fromJSONValue ( JSON . parse ( item ) ) ; } summary param { Object } x locus static isBinary ( obj ) { return ! ! ( ( typeof Uint8Array !== "undefined" && obj instanceof Uint8Array ) || ( obj && obj . $Uint8ArrayPolyfill ) ) ; } summary locus param { EJSON } a param { EJSON } b param options param { Boolean } options . keyOrderSensitive static equals ( a , b , options ? ) { let i ; const keyOrderSensitive = ! ! ( options && options . keyOrderSensitive ) ; if ( a === b ) { return true ; } if ( Number . isNaN ( a ) && Number . isNaN ( b ) ) { return true ; } if ( ! a || ! b ) { return false ; } if ( ! ( isObject ( a ) && isObject ( b ) ) ) { return false ; } if ( a instanceof Date && b instanceof Date ) { return a . valueOf ( ) === b . valueOf ( ) ; } if ( EJSON . isBinary ( a ) && EJSON . isBinary ( b ) ) { if ( a . length !== b . length ) { return false ; } for ( i = 0 ; i < a . length ; i ++ ) { if ( a [ i ] !== b [ i ] ) { return false ; } } return true ; } if ( isFunction ( a . equals ) ) { return a . equals ( b , options ) ; } if ( isFunction ( b . equals ) ) { return b . equals ( a , options ) ; } if ( a instanceof Array ) { if ( ! ( b instanceof Array ) ) { return false ; } if ( a . length !== b . length ) { return false ; } for ( i = 0 ; i < a . length ; i ++ ) { if ( ! EJSON . equals ( a [ i ] , b [ i ] , options ) ) { return false ; } } return true ; } let sum = 0 ; if ( EJSON . _isCustomType ( a ) ) sum ++ ; if ( EJSON . _isCustomType ( b ) ) sum ++ ; switch ( sum ) { case 1 : return false ; case 2 : return EJSON . equals ( EJSON . toJSONValue ( a ) , EJSON . toJSONValue ( b ) ) ; default : } let ret ; const aKeys = keysOf ( a ) ; const bKeys = keysOf ( b ) ; if ( keyOrderSensitive ) { i = 0 ; ret = aKeys . every ( ( key ) => { if ( i >= bKeys . length ) { return false ; } if ( key !== bKeys [ i ] ) { return false ; } if ( ! EJSON . equals ( a [ key ] , b [ bKeys [ i ] ] , options ) ) { return false ; } i ++ ; return true ; } ) ; } else { i = 0 ; ret = aKeys . every ( ( key ) => { if ( ! hasOwn ( b , key ) ) { return false ; } if ( ! EJSON . equals ( a [ key ] , b [ key ] , options ) ) { return false ; } i ++ ; return true ; } ) ; } return ret && i === bKeys . length ; } summary locus param { EJSON } val static clone ( v ) { let ret ; if ( ! isObject ( v ) ) { return v ; } if ( v === null ) { return null ; } if ( v instanceof Date ) { return new Date ( v . getTime ( ) ) ; } if ( v instanceof RegExp ) { return v ; } if ( ObjectId . isValid ( v ) ) { return v ; } if ( EJSON . isBinary ( v ) ) { ret = EJSON . newBinary ( v . length ) ; for ( let i = 0 ; i < v . length ; i ++ ) { ret [ i ] = v [ i ] ; } return ret ; } if ( Array . isArray ( v ) ) { return v . map ( EJSON . clone ) ; } if ( isArguments ( v ) ) { return Array . from ( v ) . map ( EJSON . clone ) ; } if ( isFunction ( v . clone ) ) { return v . clone ( ) ; } if ( EJSON . _isCustomType ( v ) ) { return EJSON . fromJSONValue ( EJSON . clone ( EJSON . toJSONValue ( v ) ) ) ; } ret = { } ; keysOf ( v ) . forEach ( ( key ) => { ret [ key ] = EJSON . clone ( v [ key ] ) ; } ) ; return ret ; } summary locus param { Number } size static newBinary ( len ) { if ( typeof Uint8Array === "undefined" || typeof ArrayBuffer === "undefined" ) { const ret = [ ] ; for ( let i = 0 ; i < len ; i ++ ) { ret . push ( 0 ) ; } ret . $Uint8ArrayPolyfill = true ; return ret ; } return new Uint8Array ( new ArrayBuffer ( len ) ) ; } }