Blame toolkit/modules/CanonicalJSON.jsm

Packit f0b94e
/* This Source Code Form is subject to the terms of the Mozilla Public
Packit f0b94e
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
Packit f0b94e
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
Packit f0b94e
Packit f0b94e
var EXPORTED_SYMBOLS = ["CanonicalJSON"];
Packit f0b94e
Packit f0b94e
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
Packit f0b94e
ChromeUtils.defineModuleGetter(this, "jsesc",
Packit f0b94e
                               "resource://gre/modules/third_party/jsesc/jsesc.js");
Packit f0b94e
Packit f0b94e
var CanonicalJSON = {
Packit f0b94e
  /**
Packit f0b94e
   * Return the canonical JSON form of the passed source, sorting all the object
Packit f0b94e
   * keys recursively. Note that this method will cause an infinite loop if
Packit f0b94e
   * cycles exist in the source (bug 1265357).
Packit f0b94e
   *
Packit f0b94e
   * @param source
Packit f0b94e
   *        The elements to be serialized.
Packit f0b94e
   *
Packit f0b94e
   * The output will have all unicode chars escaped with the unicode codepoint
Packit f0b94e
   * as lowercase hexadecimal.
Packit f0b94e
   *
Packit f0b94e
   * @usage
Packit f0b94e
   *        CanonicalJSON.stringify(listOfRecords);
Packit f0b94e
   **/
Packit f0b94e
  stringify: function stringify(source) {
Packit f0b94e
    if (Array.isArray(source)) {
Packit f0b94e
      const jsonArray = source.map(x => typeof x === "undefined" ? null : x);
Packit f0b94e
      return `[${jsonArray.map(stringify).join(",")}]`;
Packit f0b94e
    }
Packit f0b94e
Packit f0b94e
    if (typeof source === "number") {
Packit f0b94e
      if (source === 0) {
Packit f0b94e
        return (Object.is(source, -0)) ? "-0" : "0";
Packit f0b94e
      }
Packit f0b94e
    }
Packit f0b94e
Packit f0b94e
    // Leverage jsesc library, mainly for unicode escaping.
Packit f0b94e
    const toJSON = (input) => jsesc(input, {lowercaseHex: true, json: true});
Packit f0b94e
Packit f0b94e
    if (typeof source !== "object" || source === null) {
Packit f0b94e
      return toJSON(source);
Packit f0b94e
    }
Packit f0b94e
Packit f0b94e
    // Dealing with objects, ordering keys.
Packit f0b94e
    const sortedKeys = Object.keys(source).sort();
Packit f0b94e
    const lastIndex = sortedKeys.length - 1;
Packit f0b94e
    return sortedKeys.reduce((serial, key, index) => {
Packit f0b94e
      const value = source[key];
Packit f0b94e
      // JSON.stringify drops keys with an undefined value.
Packit f0b94e
      if (typeof value === "undefined") {
Packit f0b94e
        return serial;
Packit f0b94e
      }
Packit f0b94e
      const jsonValue = value && value.toJSON ? value.toJSON() : value;
Packit f0b94e
      const suffix = index !== lastIndex ? "," : "";
Packit f0b94e
      const escapedKey = toJSON(key);
Packit f0b94e
      return serial + `${escapedKey}:${stringify(jsonValue)}${suffix}`;
Packit f0b94e
    }, "{") + "}";
Packit f0b94e
  },
Packit f0b94e
};