import { encodeCanonicalJSON } from "./canonical"; import { JSONValue, JSONObject } from "./types"; describe("canonical JSON serialization", () => { function canonicalJSON(jsonString: string): JSONValue { const json = JSON.parse(jsonString); return encodeCanonicalJSON(json); } // Arrays aren't handled by any of the sections in the canonicalJSON spec test("encodes arrays correctly", function() { expect(canonicalJSON('[8, "b", "a", 4]')).toBe('[8,"b","a",4]'); }); // Things to test from http://gibson042.github.io/canonicaljson-spec/ // 2. MUST NOT include insignificant (i.e., inter-token) whitespace (defined in section 2 of RFC 7159) test("does not include insignificant whitespace", function() { expect(canonicalJSON('{ "a": 0,"b" : "1" }')).toBe( '{"a":0,"b":"1"}' ); }); // 3. MUST order the members of all objects lexicographically by the UCS (Unicode Character Set) code points of their names // 1. preserving and utilizing the code points in U+D800 through U+DFFF (inclusive) for all lone surrogates test("keys are sorted lexically", function() { expect(canonicalJSON('{"b": 1, "a": 0}')).toBe('{"a":0,"b":1}'); }); // 4. MUST represent all integer numbers (those with a zero-valued fractional part) // 1. without a leading minus sign when the value is zero, and // 2. without a decimal point, and // 3. without an exponent, and // 4. without insignificant leading zeroes (as already required of all JSON numbers) test("integers are represented sensibly", function() { expect(canonicalJSON('{"a": 0}')).toBe('{"a":0}'); }); // 5. MUST represent all non-integer numbers in exponential notation // 1. including a nonzero single-digit significand integer part, and // 2. including a nonempty significand fractional part, and // 3. including no trailing zeroes in the significand fractional part (other than as part of a “.0” required to satisfy the preceding point), and // 4. including a capital “E”, and // 5. including no plus sign in the exponent, and // 6. including no insignificant leading zeroes in the exponent describe("non integer numbers are respresented properly with exponents", function() { expect(canonicalJSON('{"a": 1.01E1}')).toBe('{"a":1.01E1}'); }); // 6. MUST represent all strings (including object member names) in their minimal-length UTF-8 encoding // 1. avoiding escape sequences for characters except those otherwise inexpressible in JSON (U+0022 QUOTATION MARK, U+005C REVERSE SOLIDUS, and ASCII control characters U+0000 through U+001F) or UTF-8 (U+D800 through U+DFFF), and // 2. avoiding escape sequences for combining characters, variation selectors, and other code points that affect preceding characters, and // 3. using two-character escape sequences where possible for characters that require escaping: // - \b U+0008 BACKSPACE // - \t U+0009 CHARACTER TABULATION (“tab”) // - \n U+000A LINE FEED (“newline”) // - \f U+000C FORM FEED // - \r U+000D CARRIAGE RETURN // - \" U+0022 QUOTATION MARK // - \\ U+005C REVERSE SOLIDUS (“backslash”), and // 4. using six-character \u00xx uppercase hexadecimal escape sequences for control characters that require escaping but lack a two-character sequence, and // 5. using six-character \uDxxx uppercase hexadecimal escape sequences for lone surrogates describe("represents strings with their minimal length encoding", function() { const exampleText = '{"a":"!@#$%^&*()-_+={}\\b\\t\\n\\f\\r\\"\\\\"}'; expect(canonicalJSON(exampleText)).toBe(exampleText); }); describe("passes the canonicaljson-spec example", function() { const example = '{"-0":0,"-1":-1,"0.1":1.0E-1,"1":1,"10.1":1.01E1,"emoji":"😃","escape":"\\u001B","lone surrogate":"\\uDEAD","whitespace":" \\t\\n\\r"}'; expect(canonicalJSON(example)).toBe(example); }); describe("throws exception on recursive data structure", function() { const a: JSONObject = {}; a["a"] = { b: a }; expect(() => encodeCanonicalJSON(a)).toThrow(Error); }); });