UNPKG

8.96 kBPlain TextView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3/*-----------------------------------------------------------------------------
4| Copyright (c) 2014-2017, PhosphorJS Contributors
5|
6| Distributed under the terms of the BSD 3-Clause License.
7|
8| The full license is in the file LICENSE, distributed with this software.
9|----------------------------------------------------------------------------*/
10
11/**
12 * A type alias for a JSON primitive.
13 */
14export type JSONPrimitive = boolean | number | string | null;
15
16/**
17 * A type alias for a JSON value.
18 */
19export type JSONValue = JSONPrimitive | JSONObject | JSONArray;
20
21/**
22 * A type definition for a JSON object.
23 */
24export interface JSONObject {
25 [key: string]: JSONValue;
26}
27
28/**
29 * A type definition for a JSON array.
30 */
31export interface JSONArray extends Array<JSONValue> {}
32
33/**
34 * A type definition for a readonly JSON object.
35 */
36export interface ReadonlyJSONObject {
37 readonly [key: string]: ReadonlyJSONValue;
38}
39
40/**
41 * A type definition for a readonly JSON array.
42 */
43export interface ReadonlyJSONArray extends ReadonlyArray<ReadonlyJSONValue> {}
44
45/**
46 * A type alias for a readonly JSON value.
47 */
48export type ReadonlyJSONValue =
49 | JSONPrimitive
50 | ReadonlyJSONObject
51 | ReadonlyJSONArray;
52
53/**
54 * A type alias for a partial JSON value.
55 *
56 * Note: Partial here means that JSON object attributes can be `undefined`.
57 */
58export type PartialJSONValue =
59 | JSONPrimitive
60 | PartialJSONObject
61 | PartialJSONArray;
62
63/**
64 * A type definition for a partial JSON object.
65 *
66 * Note: Partial here means that the JSON object attributes can be `undefined`.
67 */
68export interface PartialJSONObject {
69 [key: string]: PartialJSONValue | undefined;
70}
71
72/**
73 * A type definition for a partial JSON array.
74 *
75 * Note: Partial here means that JSON object attributes can be `undefined`.
76 */
77export interface PartialJSONArray extends Array<PartialJSONValue> {}
78
79/**
80 * A type definition for a readonly partial JSON object.
81 *
82 * Note: Partial here means that JSON object attributes can be `undefined`.
83 */
84export interface ReadonlyPartialJSONObject {
85 readonly [key: string]: ReadonlyPartialJSONValue | undefined;
86}
87
88/**
89 * A type definition for a readonly partial JSON array.
90 *
91 * Note: Partial here means that JSON object attributes can be `undefined`.
92 */
93export interface ReadonlyPartialJSONArray
94 extends ReadonlyArray<ReadonlyPartialJSONValue> {}
95
96/**
97 * A type alias for a readonly partial JSON value.
98 *
99 * Note: Partial here means that JSON object attributes can be `undefined`.
100 */
101export type ReadonlyPartialJSONValue =
102 | JSONPrimitive
103 | ReadonlyPartialJSONObject
104 | ReadonlyPartialJSONArray;
105
106/**
107 * The namespace for JSON-specific functions.
108 */
109export namespace JSONExt {
110 /**
111 * A shared frozen empty JSONObject
112 */
113 export const emptyObject = Object.freeze({}) as ReadonlyJSONObject;
114
115 /**
116 * A shared frozen empty JSONArray
117 */
118 export const emptyArray = Object.freeze([]) as ReadonlyJSONArray;
119
120 /**
121 * Test whether a JSON value is a primitive.
122 *
123 * @param value - The JSON value of interest.
124 *
125 * @returns `true` if the value is a primitive,`false` otherwise.
126 */
127 export function isPrimitive(
128 value: ReadonlyPartialJSONValue
129 ): value is JSONPrimitive {
130 return (
131 value === null ||
132 typeof value === 'boolean' ||
133 typeof value === 'number' ||
134 typeof value === 'string'
135 );
136 }
137
138 /**
139 * Test whether a JSON value is an array.
140 *
141 * @param value - The JSON value of interest.
142 *
143 * @returns `true` if the value is a an array, `false` otherwise.
144 */
145 export function isArray(value: JSONValue): value is JSONArray;
146 export function isArray(value: ReadonlyJSONValue): value is ReadonlyJSONArray;
147 export function isArray(value: PartialJSONValue): value is PartialJSONArray;
148 export function isArray(
149 value: ReadonlyPartialJSONValue
150 ): value is ReadonlyPartialJSONArray;
151 export function isArray(value: ReadonlyPartialJSONValue): boolean {
152 return Array.isArray(value);
153 }
154
155 /**
156 * Test whether a JSON value is an object.
157 *
158 * @param value - The JSON value of interest.
159 *
160 * @returns `true` if the value is a an object, `false` otherwise.
161 */
162 export function isObject(value: JSONValue): value is JSONObject;
163 export function isObject(
164 value: ReadonlyJSONValue
165 ): value is ReadonlyJSONObject;
166 export function isObject(value: PartialJSONValue): value is PartialJSONObject;
167 export function isObject(
168 value: ReadonlyPartialJSONValue
169 ): value is ReadonlyPartialJSONObject;
170 export function isObject(value: ReadonlyPartialJSONValue): boolean {
171 return !isPrimitive(value) && !isArray(value);
172 }
173
174 /**
175 * Compare two JSON values for deep equality.
176 *
177 * @param first - The first JSON value of interest.
178 *
179 * @param second - The second JSON value of interest.
180 *
181 * @returns `true` if the values are equivalent, `false` otherwise.
182 */
183 export function deepEqual(
184 first: ReadonlyPartialJSONValue,
185 second: ReadonlyPartialJSONValue
186 ): boolean {
187 // Check referential and primitive equality first.
188 if (first === second) {
189 return true;
190 }
191
192 // If one is a primitive, the `===` check ruled out the other.
193 if (isPrimitive(first) || isPrimitive(second)) {
194 return false;
195 }
196
197 // Test whether they are arrays.
198 let a1 = isArray(first);
199 let a2 = isArray(second);
200
201 // Bail if the types are different.
202 if (a1 !== a2) {
203 return false;
204 }
205
206 // If they are both arrays, compare them.
207 if (a1 && a2) {
208 return deepArrayEqual(
209 first as ReadonlyPartialJSONArray,
210 second as ReadonlyPartialJSONArray
211 );
212 }
213
214 // At this point, they must both be objects.
215 return deepObjectEqual(
216 first as ReadonlyPartialJSONObject,
217 second as ReadonlyPartialJSONObject
218 );
219 }
220
221 /**
222 * Create a deep copy of a JSON value.
223 *
224 * @param value - The JSON value to copy.
225 *
226 * @returns A deep copy of the given JSON value.
227 */
228 export function deepCopy<T extends ReadonlyPartialJSONValue>(value: T): T {
229 // Do nothing for primitive values.
230 if (isPrimitive(value)) {
231 return value;
232 }
233
234 // Deep copy an array.
235 if (isArray(value)) {
236 return deepArrayCopy(value);
237 }
238
239 // Deep copy an object.
240 return deepObjectCopy(value);
241 }
242
243 /**
244 * Compare two JSON arrays for deep equality.
245 */
246 function deepArrayEqual(
247 first: ReadonlyPartialJSONArray,
248 second: ReadonlyPartialJSONArray
249 ): boolean {
250 // Check referential equality first.
251 if (first === second) {
252 return true;
253 }
254
255 // Test the arrays for equal length.
256 if (first.length !== second.length) {
257 return false;
258 }
259
260 // Compare the values for equality.
261 for (let i = 0, n = first.length; i < n; ++i) {
262 if (!deepEqual(first[i], second[i])) {
263 return false;
264 }
265 }
266
267 // At this point, the arrays are equal.
268 return true;
269 }
270
271 /**
272 * Compare two JSON objects for deep equality.
273 */
274 function deepObjectEqual(
275 first: ReadonlyPartialJSONObject,
276 second: ReadonlyPartialJSONObject
277 ): boolean {
278 // Check referential equality first.
279 if (first === second) {
280 return true;
281 }
282
283 // Check for the first object's keys in the second object.
284 for (let key in first) {
285 if (first[key] !== undefined && !(key in second)) {
286 return false;
287 }
288 }
289
290 // Check for the second object's keys in the first object.
291 for (let key in second) {
292 if (second[key] !== undefined && !(key in first)) {
293 return false;
294 }
295 }
296
297 // Compare the values for equality.
298 for (let key in first) {
299 // Get the values.
300 let firstValue = first[key];
301 let secondValue = second[key];
302
303 // If both are undefined, ignore the key.
304 if (firstValue === undefined && secondValue === undefined) {
305 continue;
306 }
307
308 // If only one value is undefined, the objects are not equal.
309 if (firstValue === undefined || secondValue === undefined) {
310 return false;
311 }
312
313 // Compare the values.
314 if (!deepEqual(firstValue, secondValue)) {
315 return false;
316 }
317 }
318
319 // At this point, the objects are equal.
320 return true;
321 }
322
323 /**
324 * Create a deep copy of a JSON array.
325 */
326 function deepArrayCopy(value: any): any {
327 let result = new Array<any>(value.length);
328 for (let i = 0, n = value.length; i < n; ++i) {
329 result[i] = deepCopy(value[i]);
330 }
331 return result;
332 }
333
334 /**
335 * Create a deep copy of a JSON object.
336 */
337 function deepObjectCopy(value: any): any {
338 let result: any = {};
339 for (let key in value) {
340 // Ignore undefined values.
341 let subvalue = value[key];
342 if (subvalue === undefined) {
343 continue;
344 }
345 result[key] = deepCopy(subvalue);
346 }
347 return result;
348 }
349}