// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * A type alias for a JSON primitive. */ export type JSONPrimitive = boolean | number | string | null; /** * A type alias for a JSON value. */ export type JSONValue = JSONPrimitive | JSONObject | JSONArray; /** * A type definition for a JSON object. */ export interface JSONObject { [key: string]: JSONValue; } /** * A type definition for a JSON array. */ export interface JSONArray extends Array {} /** * A type definition for a readonly JSON object. */ export interface ReadonlyJSONObject { readonly [key: string]: ReadonlyJSONValue; } /** * A type definition for a readonly JSON array. */ export interface ReadonlyJSONArray extends ReadonlyArray {} /** * A type alias for a readonly JSON value. */ export type ReadonlyJSONValue = | JSONPrimitive | ReadonlyJSONObject | ReadonlyJSONArray; /** * A type alias for a partial JSON value. * * Note: Partial here means that JSON object attributes can be `undefined`. */ export type PartialJSONValue = | JSONPrimitive | PartialJSONObject | PartialJSONArray; /** * A type definition for a partial JSON object. * * Note: Partial here means that the JSON object attributes can be `undefined`. */ export interface PartialJSONObject { [key: string]: PartialJSONValue | undefined; } /** * A type definition for a partial JSON array. * * Note: Partial here means that JSON object attributes can be `undefined`. */ export interface PartialJSONArray extends Array {} /** * A type definition for a readonly partial JSON object. * * Note: Partial here means that JSON object attributes can be `undefined`. */ export interface ReadonlyPartialJSONObject { readonly [key: string]: ReadonlyPartialJSONValue | undefined; } /** * A type definition for a readonly partial JSON array. * * Note: Partial here means that JSON object attributes can be `undefined`. */ export interface ReadonlyPartialJSONArray extends ReadonlyArray {} /** * A type alias for a readonly partial JSON value. * * Note: Partial here means that JSON object attributes can be `undefined`. */ export type ReadonlyPartialJSONValue = | JSONPrimitive | ReadonlyPartialJSONObject | ReadonlyPartialJSONArray; /** * The namespace for JSON-specific functions. */ export namespace JSONExt { /** * A shared frozen empty JSONObject */ export const emptyObject = Object.freeze({}) as ReadonlyJSONObject; /** * A shared frozen empty JSONArray */ export const emptyArray = Object.freeze([]) as ReadonlyJSONArray; /** * Test whether a JSON value is a primitive. * * @param value - The JSON value of interest. * * @returns `true` if the value is a primitive,`false` otherwise. */ export function isPrimitive( value: ReadonlyPartialJSONValue ): value is JSONPrimitive { return ( value === null || typeof value === 'boolean' || typeof value === 'number' || typeof value === 'string' ); } /** * Test whether a JSON value is an array. * * @param value - The JSON value of interest. * * @returns `true` if the value is a an array, `false` otherwise. */ export function isArray(value: JSONValue): value is JSONArray; export function isArray(value: ReadonlyJSONValue): value is ReadonlyJSONArray; export function isArray(value: PartialJSONValue): value is PartialJSONArray; export function isArray( value: ReadonlyPartialJSONValue ): value is ReadonlyPartialJSONArray; export function isArray(value: ReadonlyPartialJSONValue): boolean { return Array.isArray(value); } /** * Test whether a JSON value is an object. * * @param value - The JSON value of interest. * * @returns `true` if the value is a an object, `false` otherwise. */ export function isObject(value: JSONValue): value is JSONObject; export function isObject( value: ReadonlyJSONValue ): value is ReadonlyJSONObject; export function isObject(value: PartialJSONValue): value is PartialJSONObject; export function isObject( value: ReadonlyPartialJSONValue ): value is ReadonlyPartialJSONObject; export function isObject(value: ReadonlyPartialJSONValue): boolean { return !isPrimitive(value) && !isArray(value); } /** * Compare two JSON values for deep equality. * * @param first - The first JSON value of interest. * * @param second - The second JSON value of interest. * * @returns `true` if the values are equivalent, `false` otherwise. */ export function deepEqual( first: ReadonlyPartialJSONValue, second: ReadonlyPartialJSONValue ): boolean { // Check referential and primitive equality first. if (first === second) { return true; } // If one is a primitive, the `===` check ruled out the other. if (isPrimitive(first) || isPrimitive(second)) { return false; } // Test whether they are arrays. let a1 = isArray(first); let a2 = isArray(second); // Bail if the types are different. if (a1 !== a2) { return false; } // If they are both arrays, compare them. if (a1 && a2) { return deepArrayEqual( first as ReadonlyPartialJSONArray, second as ReadonlyPartialJSONArray ); } // At this point, they must both be objects. return deepObjectEqual( first as ReadonlyPartialJSONObject, second as ReadonlyPartialJSONObject ); } /** * Create a deep copy of a JSON value. * * @param value - The JSON value to copy. * * @returns A deep copy of the given JSON value. */ export function deepCopy(value: T): T { // Do nothing for primitive values. if (isPrimitive(value)) { return value; } // Deep copy an array. if (isArray(value)) { return deepArrayCopy(value); } // Deep copy an object. return deepObjectCopy(value); } /** * Compare two JSON arrays for deep equality. */ function deepArrayEqual( first: ReadonlyPartialJSONArray, second: ReadonlyPartialJSONArray ): boolean { // Check referential equality first. if (first === second) { return true; } // Test the arrays for equal length. if (first.length !== second.length) { return false; } // Compare the values for equality. for (let i = 0, n = first.length; i < n; ++i) { if (!deepEqual(first[i], second[i])) { return false; } } // At this point, the arrays are equal. return true; } /** * Compare two JSON objects for deep equality. */ function deepObjectEqual( first: ReadonlyPartialJSONObject, second: ReadonlyPartialJSONObject ): boolean { // Check referential equality first. if (first === second) { return true; } // Check for the first object's keys in the second object. for (let key in first) { if (first[key] !== undefined && !(key in second)) { return false; } } // Check for the second object's keys in the first object. for (let key in second) { if (second[key] !== undefined && !(key in first)) { return false; } } // Compare the values for equality. for (let key in first) { // Get the values. let firstValue = first[key]; let secondValue = second[key]; // If both are undefined, ignore the key. if (firstValue === undefined && secondValue === undefined) { continue; } // If only one value is undefined, the objects are not equal. if (firstValue === undefined || secondValue === undefined) { return false; } // Compare the values. if (!deepEqual(firstValue, secondValue)) { return false; } } // At this point, the objects are equal. return true; } /** * Create a deep copy of a JSON array. */ function deepArrayCopy(value: any): any { let result = new Array(value.length); for (let i = 0, n = value.length; i < n; ++i) { result[i] = deepCopy(value[i]); } return result; } /** * Create a deep copy of a JSON object. */ function deepObjectCopy(value: any): any { let result: any = {}; for (let key in value) { // Ignore undefined values. let subvalue = value[key]; if (subvalue === undefined) { continue; } result[key] = deepCopy(subvalue); } return result; } }