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