1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5var EXPORTED_SYMBOLS = ["CanonicalJSON"];
6
7var CanonicalJSON = {
8  /**
9   * Return the canonical JSON form of the passed source, sorting all the object
10   * keys recursively. Note that this method will cause an infinite loop if
11   * cycles exist in the source (bug 1265357).
12   *
13   * @param source
14   *        The elements to be serialized.
15   *
16   * The output will have all unicode chars escaped with the unicode codepoint
17   * as lowercase hexadecimal.
18   *
19   * @usage
20   *        CanonicalJSON.stringify(listOfRecords);
21   **/
22  stringify: function stringify(source, jsescFn) {
23    if (typeof jsescFn != "function") {
24      const { jsesc } = ChromeUtils.import(
25        "resource://gre/modules/third_party/jsesc/jsesc.js"
26      );
27      jsescFn = jsesc;
28    }
29    if (Array.isArray(source)) {
30      const jsonArray = source.map(x => (typeof x === "undefined" ? null : x));
31      return (
32        "[" + jsonArray.map(item => stringify(item, jsescFn)).join(",") + "]"
33      );
34    }
35
36    if (typeof source === "number") {
37      if (source === 0) {
38        return Object.is(source, -0) ? "-0" : "0";
39      }
40    }
41
42    // Leverage jsesc library, mainly for unicode escaping.
43    const toJSON = input => jsescFn(input, { lowercaseHex: true, json: true });
44
45    if (typeof source !== "object" || source === null) {
46      return toJSON(source);
47    }
48
49    // Dealing with objects, ordering keys.
50    const sortedKeys = Object.keys(source).sort();
51    const lastIndex = sortedKeys.length - 1;
52    return (
53      sortedKeys.reduce((serial, key, index) => {
54        const value = source[key];
55        // JSON.stringify drops keys with an undefined value.
56        if (typeof value === "undefined") {
57          return serial;
58        }
59        const jsonValue = value && value.toJSON ? value.toJSON() : value;
60        const suffix = index !== lastIndex ? "," : "";
61        const escapedKey = toJSON(key);
62        return (
63          serial + escapedKey + ":" + stringify(jsonValue, jsescFn) + suffix
64        );
65      }, "{") + "}"
66    );
67  },
68};
69