Created
August 17, 2019 01:25
-
-
Save kalda341/096c57e4670c882864060e195194c714 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // As described here: http://gibson042.github.io/canonicaljson-spec/ | |
| // Probably fairly inefficient, but I really doubt it matters | |
| export function encodeCanonicalJSON(jsonNode) { | |
| // Escape character | quote | backslash | high surrogate not followed by low, or low surrogate not following | |
| // a high surrogate | |
| const replaceRegex = /[\u0000-\u001F]|"|\\|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/g; | |
| const toSlashEscape = { | |
| "\b": "\\b", | |
| "\t": "\\t", | |
| "\n": "\\n", | |
| "\f": "\\f", | |
| "\r": "\\r", | |
| "\"": "\\\"", | |
| "\\": "\\\\", | |
| }; | |
| const escapeCharacter = (character) => { | |
| const slashEscaped = toSlashEscape[character]; | |
| if (slashEscaped) { | |
| return slashEscaped; | |
| } else { | |
| // If the character is marked for replacement and we can't slash escape it, we'll encode it | |
| // with a six-character \u00xx uppercase hexadecimal escape sequence | |
| const charCode = character.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0"); | |
| return "\\u" + charCode; | |
| } | |
| } | |
| const exponentialRegex = /^(\d)(?:\.(\d*))?e([+-]\d+)$/; | |
| const formatExponential = (number) => { | |
| // Must be a floating point number | |
| const match = number.toExponential().match(exponentialRegex); | |
| const integer = match[1]; | |
| // May not include a decimal component, but we need it | |
| const decimal = match[2] || '0'; | |
| // Will remove + if present | |
| const exponent = parseInt(match[3]); | |
| return `${integer}.${decimal}E${exponent}`; | |
| }; | |
| const doEncode = (jsonNode, depth) => { | |
| if (depth > 100) { | |
| throw new Error("Max depth exceeded. Structure is probably recursive."); | |
| } else if (jsonNode === undefined) { | |
| throw new Error("Invalid type: undefined"); | |
| } else if (jsonNode === null) { | |
| return "null"; | |
| } else if (typeof jsonNode === "boolean") { | |
| return jsonNode.toString(); | |
| } else if (typeof jsonNode === "number" && Number.isInteger(jsonNode)) { | |
| return jsonNode.toString(); | |
| } else if (typeof jsonNode === "number") { | |
| return formatExponential(jsonNode); | |
| } else if (typeof jsonNode === "string") { | |
| return `"${jsonNode.replace(replaceRegex, escapeCharacter)}"`; | |
| } else if (typeof jsonNode === "object" && Array.isArray(jsonNode)) { | |
| const nodeContents = jsonNode.map((value) => doEncode(value, depth + 1)).join(","); | |
| return `[${nodeContents}]`; | |
| } else if (typeof jsonNode === "object") { | |
| // Order the members of all objects lexicographically by the UCS (Unicode Character Set) code points of their names | |
| // Do not include key value pairs where their value is undefined. | |
| const nodeContents = Object.keys(jsonNode).sort().filter( | |
| key => jsonNode[key] !== undefined | |
| ).map(key => | |
| [doEncode(key, depth + 1), doEncode(jsonNode[key], depth + 1)].join(":") | |
| ).join(","); | |
| return `{${nodeContents}}`; | |
| } else { | |
| throw new Error("Unknown type"); | |
| } | |
| }; | |
| return doEncode(jsonNode, 0); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment