1const COMMA = ','; 2const COLON = ':'; 3const LEFT_SQUARE_BRACKET = '['; 4const RIGHT_SQUARE_BRACKET = ']'; 5const LEFT_CURLY_BRACKET = '{'; 6const RIGHT_CURLY_BRACKET = '}'; 7 8// Recursively encodes the supplied object according to the canonical JSON form 9// as specified at http://wiki.laptop.org/go/Canonical_JSON. It's a restricted 10// dialect of JSON in which keys are lexically sorted, floats are not allowed, 11// and only double quotes and backslashes are escaped. 12function canonicalize(object) { 13 const buffer = []; 14 if (typeof object === 'string') { 15 buffer.push(canonicalizeString(object)); 16 } else if (typeof object === 'boolean') { 17 buffer.push(JSON.stringify(object)); 18 } else if (Number.isInteger(object)) { 19 buffer.push(JSON.stringify(object)); 20 } else if (object === null) { 21 buffer.push(JSON.stringify(object)); 22 } else if (Array.isArray(object)) { 23 buffer.push(LEFT_SQUARE_BRACKET); 24 let first = true; 25 object.forEach((element) => { 26 if (!first) { 27 buffer.push(COMMA); 28 } 29 first = false; 30 buffer.push(canonicalize(element)); 31 }); 32 buffer.push(RIGHT_SQUARE_BRACKET); 33 } else if (typeof object === 'object') { 34 buffer.push(LEFT_CURLY_BRACKET); 35 let first = true; 36 Object.keys(object) 37 .sort() 38 .forEach((property) => { 39 if (!first) { 40 buffer.push(COMMA); 41 } 42 first = false; 43 buffer.push(canonicalizeString(property)); 44 buffer.push(COLON); 45 buffer.push(canonicalize(object[property])); 46 }); 47 buffer.push(RIGHT_CURLY_BRACKET); 48 } else { 49 throw new TypeError('cannot encode ' + object.toString()); 50 } 51 52 return buffer.join(''); 53} 54 55// String canonicalization consists of escaping backslash (\) and double 56// quote (") characters and wrapping the resulting string in double quotes. 57function canonicalizeString(string) { 58 const escapedString = string.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); 59 return '"' + escapedString + '"'; 60} 61 62module.exports = { 63 canonicalize, 64}; 65