UNPKG

best-effort-json-parser

Version:
670 lines (636 loc) 19.4 kB
import sinon from 'sinon' import { expect } from 'chai' import { parse, setErrorLogger } from './parse' let onExtraTokenSpy: sinon.SinonSpy let originalOnExtraToken: typeof parse.onExtraToken let muteLog = true beforeEach(() => { if (muteLog) { onExtraTokenSpy = sinon.fake() originalOnExtraToken = parse.onExtraToken parse.onExtraToken = onExtraTokenSpy } else { onExtraTokenSpy = sinon.spy(parse, 'onExtraToken') } }) afterEach(() => { if (muteLog) { parse.onExtraToken = originalOnExtraToken } else { sinon.restore() } }) describe('parser TestSuit', function () { context('number', () => { it('should parse positive integer', function () { expect(parse(`42`)).equals(42) }) it('should parse negative integer', function () { expect(parse(`-42`)).equals(-42) }) it('should parse positive float', function () { expect(parse(`12.34`)).equals(12.34) }) it('should parse negative float', function () { expect(parse(`-12.34`)).equals(-12.34) }) it('should parse incomplete positive float', function () { expect(parse(`12.`)).equals(12) }) it('should parse incomplete negative float', function () { expect(parse(`-12.`)).equals(-12) }) it('should parse incomplete negative integer', function () { expect(parse(`-`)).equals(-0) }) it('should preserve invalid number', function () { expect(parse(`1.2.3.4`)).equals('1.2.3.4') }) }) context('string', () => { it('should parse string', function () { expect(parse(`"I am text"`)).equals('I am text') expect(parse(`"I'm text"`)).equals("I'm text") expect(parse(`"I\\"m text"`)).equals('I"m text') }) it('should parse incomplete string', function () { expect(parse(`"I am text`)).equals('I am text') expect(parse(`"I'm text`)).equals("I'm text") expect(parse(`"I\\"m text`)).equals('I"m text') }) }) context('boolean', () => { it('should parse boolean', function () { expect(parse(`true`)).equals(true) expect(parse(`false`)).equals(false) }) function testIncomplete(str: string, val: boolean) { for (let i = str.length; i >= 1; i--) { expect(parse(str.slice(0, i))).equals(val) } } it('should parse incomplete true', function () { testIncomplete(`true`, true) }) it('should parse incomplete false', function () { testIncomplete(`false`, false) }) }) context('array', () => { it('should parse empty array', function () { expect(parse(`[]`)).deep.equals([]) }) it('should parse number array', function () { expect(parse(`[1,2,3]`)).deep.equals([1, 2, 3]) }) it('should parse incomplete array', function () { expect(parse(`[1,2,3`)).deep.equals([1, 2, 3]) expect(parse(`[1,2,`)).deep.equals([1, 2]) expect(parse(`[1,2`)).deep.equals([1, 2]) expect(parse(`[1,`)).deep.equals([1]) expect(parse(`[1`)).deep.equals([1]) expect(parse(`[`)).deep.equals([]) }) }) context('object', () => { it('should parse simple object', function () { let o = { a: 'apple', b: 'banana' } expect(parse(JSON.stringify(o))).deep.equals(o) expect(parse(JSON.stringify(o, null, 2))).deep.equals(o) expect(parse(`{"a":"apple","b":"banana"}`)).deep.equals({ a: 'apple', b: 'banana', }) expect(parse(`{"a": "apple","b": "banana"}`)).deep.equals({ a: 'apple', b: 'banana', }) expect(parse(`{"a": "apple", "b": "banana"}`)).deep.equals({ a: 'apple', b: 'banana', }) expect(parse(`{"a" : "apple", "b" : "banana"}`)).deep.equals({ a: 'apple', b: 'banana', }) expect(parse(`{ "a" : "apple", "b" : "banana" }`)).deep.equals({ a: 'apple', b: 'banana', }) expect(parse(`{ "a" : "apple" , "b" : "banana" }`)).deep.equals({ a: 'apple', b: 'banana', }) }) it('should parse incomplete simple object', function () { expect(parse(`{"a":"apple","b":"banana"`)).deep.equals({ a: 'apple', b: 'banana', }) expect(parse(`{"a":"apple","b":"banana`)).deep.equals({ a: 'apple', b: 'banana', }) expect(parse(`{"a":"apple","b":"b`)).deep.equals({ a: 'apple', b: 'b' }) expect(parse(`{"a":"apple","b":"`)).deep.equals({ a: 'apple', b: '' }) expect(parse(`{"a":"apple","b":`)).deep.equals({ a: 'apple', b: undefined, }) expect(parse(`{"a":"apple","b"`)).deep.equals({ a: 'apple', b: undefined, }) expect(parse(`{"a":"apple","b`)).deep.equals({ a: 'apple', b: undefined }) expect(parse(`{"a":"apple","`)).deep.equals({ 'a': 'apple', '': undefined, }) expect(parse(`{"a":"apple",`)).deep.equals({ a: 'apple' }) expect(parse(`{"a":"apple"`)).deep.equals({ a: 'apple' }) expect(parse(`{"a":"apple`)).deep.equals({ a: 'apple' }) expect(parse(`{"a":"a`)).deep.equals({ a: 'a' }) expect(parse(`{"a":"`)).deep.equals({ a: '' }) expect(parse(`{"a":`)).deep.equals({ a: undefined }) expect(parse(`{"a"`)).deep.equals({ a: undefined }) expect(parse(`{"a`)).deep.equals({ a: undefined }) expect(parse(`{"`)).deep.equals({ '': undefined }) expect(parse(`{`)).deep.equals({}) }) }) context('complex object', () => { it('should parse complete complex object', function () { expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34 } } }`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, }, }, }) }) it('should parse incomplete complex object', function () { expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34 } }`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, }, }, }) expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34 }`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, }, }, }) expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, }, }, }) expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12, }, }, }) expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float":`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: undefined, }, }, }) expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42,`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, }, }, }) expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": {`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: {}, }, }) expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj":`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: undefined, }, }) expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }],`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], }, }) expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "flo`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, flo: undefined }], }, }) expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], {`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], {}], }, }) expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 1`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, arr: [42, 12.34, [42, 1]], }, }) expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float": 12.34, "arr": [`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: 12.34, arr: [], }, }) expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "float": 12.34 }], "obj": { "int": 42, "float"`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, float: 12.34 }], obj: { int: 42, float: undefined, }, }) expect( parse(`{ "int": 42, "float": 12.34, "arr": [42, 12.34, [42, 12.34], { "int": 42, "flo`), ).deep.equals({ int: 42, float: 12.34, arr: [42, 12.34, [42, 12.34], { int: 42, flo: undefined }], }) }) }) context('invalid inputs', () => { it('should throw error on invalid (not incomplete) json text', function () { // spy the error logger let spy = sinon.fake() setErrorLogger(spy) expect(() => parse(`:atom`)).to.throws() expect(spy.called).be.true expect(spy.firstCall.firstArg).is.string('no parser registered for ":"') // restore the error logger setErrorLogger(console.error) }) it('should complaint on extra tokens', function () { expect(parse(`[1] 2`)).deep.equals([1]) expect(onExtraTokenSpy.called).be.true expect(parse.lastParseReminding).equals(' 2') }) }) context('extra space', () => { it('should parse complete json with extra space', function () { expect(parse(` [1] `)).deep.equals([1]) }) it('should parse incomplete json with extra space', function () { expect(parse(` [1 `)).deep.equals([1]) }) }) context('invalid but understandable json', () => { context('string newline', () => { it('should parse escaped newline', function () { expect(parse(`"line1\\nline2"`)).equals('line1\nline2') expect( parse(/* javascript */ ` { "essay": "global health.\n\nDuring my tenure ..." } `), ).deep.equals({ essay: 'global health.\n\nDuring my tenure ...', }) }) it('should parse non-escaped newline', function () { expect(parse(`"line1\nline2"`)).equals('line1\nline2') }) it('should parse non-escaped newline inside string value', function () { expect(parse(`{"key":"line1\\nline2`)).deep.equals({ key: 'line1\nline2', }) expect(parse(`{"key":"line1\nline2`)).deep.equals({ key: 'line1\nline2', }) expect(parse(`{"key":"line1\n`)).deep.equals({ key: 'line1\n' }) expect(parse(`{\n\t"key":"line1\n`)).deep.equals({ key: 'line1\n' }) expect(parse(`{\n\t"key":"line1\nline2"\n}`)).deep.equals({ key: 'line1\nline2', }) }) }) context('string non-escaped characters', function () { it('should parse \\t', function () { expect(parse(`"text\t"`)).equals(`text\t`) expect(parse(`"text\\t"`)).equals(`text\t`) }) it('should parse \\r', function () { expect(parse(`"text\r"`)).equals(`text\r`) expect(parse(`"text\\r"`)).equals(`text\r`) }) }) context('string quote', () => { it('should parse string with double quote', function () { expect(parse(`"str"`)).equals('str') }) it('should parse string with single quote', function () { expect(parse(`'str'`)).equals('str') }) it('should parse string without double quote', function () { expect(parse(`str`)).equals('str') }) it('should parse array of string without double quote', function () { expect(parse(`[a,b,c]`)).deep.equals(['a', 'b', 'c']) }) }) context('object key', () => { it('should parse object key with double quote', function () { expect(parse(`{ "int" : 42 }`)).deep.equals({ int: 42 }) }) it('should parse object key with single quote', function () { expect(parse(`{ 'int' : 42 }`)).deep.equals({ int: 42 }) }) it('should parse object key without double quote', function () { expect(parse(`{ int : 42 }`)).deep.equals({ int: 42 }) }) }) }) context('falsy values', () => { it('should parse empty string', function () { expect(parse('')).equals('') }) it('should parse undefined', function () { expect(parse(undefined)).to.be.undefined }) it('should parse null', function () { expect(parse(null)).to.be.null }) }) context('incomplete escaped characters', function () { it('should ignore an incomplete escaped character (such as control character \\n)', function () { expect(parse(`"the newline\n`)).equals('the newline\n') expect(parse(`"the newline\\n`)).equals('the newline\n') expect(parse(`"the newline\\`)).equals('the newline') expect(parse(`"the newline\n\\`)).equals('the newline\n') expect(parse(`"the newline\\n\\`)).equals('the newline\n') expect(parse(`"the newline\\\\`)).equals('the newline\\') }) it('should ignore incomplete escape character in object value', function () { expect(parse('{"a":"\n"')).deep.equals({ a: '\n' }) expect(parse('{"a":"\n')).deep.equals({ a: '\n' }) expect(parse('{"a":"\\n"')).deep.equals({ a: '\n' }) expect(parse('{"a":"\\')).deep.equals({ a: '' }) }) }) })