master 7de73f1e06f7 cached
17 files
44.7 KB
14.0k tokens
27 symbols
1 requests
Download .txt
Repository: beenotung/best-effort-json-parser
Branch: master
Commit: 7de73f1e06f7
Files: 17
Total size: 44.7 KB

Directory structure:
gitextract_ujoa3yj2/

├── .editorconfig
├── .gitignore
├── .idea/
│   ├── .gitignore
│   ├── best-effort-json-parser.iml
│   └── modules.xml
├── .nycrc.json
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── example/
│   ├── index.ts
│   └── package.json
├── package.json
├── src/
│   ├── parse.spec.ts
│   └── parse.ts
├── tsconfig.json
└── tslint.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs
# http://editorconfig.org

root = true

[*]
indent_style = space
indent_size = 2

# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[*.xml]
indent_style = space
indent_size = 4

[*.json]
indent_style = space
indent_size = 2


================================================
FILE: .gitignore
================================================

node_modules/
package-lock.json
pnpm-lock.yaml
pnpm-debug.log
dist/
.cache/
*.tgz
.env
.nyc_output
coverage/


================================================
FILE: .idea/.gitignore
================================================
*
!modules.xml
!*.iml
!dictionaries/
!.gitignore



================================================
FILE: .idea/best-effort-json-parser.iml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
  <component name="NewModuleRootManager" inherit-compiler-output="true">
    <exclude-output />
    <content url="file://$MODULE_DIR$">
      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
      <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
      <excludeFolder url="file://$MODULE_DIR$/dist" />
      <excludeFolder url="file://$MODULE_DIR$/data" />
      <excludeFolder url="file://$MODULE_DIR$/res" />
    </content>
    <orderEntry type="inheritedJdk" />
    <orderEntry type="sourceFolder" forTests="false" />
  </component>
</module>


================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/.idea/best-effort-json-parser.iml" filepath="$PROJECT_DIR$/.idea/.idea/best-effort-json-parser.iml" />
    </modules>
  </component>
</project>


================================================
FILE: .nycrc.json
================================================
{
  "include": "src/**/*.ts",
  "exclude": "src/**/*.{test,spec}.ts"
}


================================================
FILE: .prettierignore
================================================
*.macro.ts
src/assets/*
src/components.d.ts
src/polyfill-array.ts


================================================
FILE: .prettierrc
================================================
{
  "singleQuote": true,
  "jsxSingleQuote": false,
  "quoteProps": "consistent",
  "overrides": [
    {
      "files": [
        "*.scss",
        "*.css"
      ],
      "options": {
        "singleQuote": false
      }
    }
  ],
  "semi": false,
  "arrowParens": "avoid",
  "trailingComma": "all"
}


================================================
FILE: LICENSE
================================================
BSD 2-Clause License

Copyright (c) [2020], [Beeno Tung (Tung Cheung Leong)]
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: README.md
================================================
# best-effort-json-parser

Parse incomplete JSON text in best-effort manner with support for comments. Useful for partial JSON responses, broken network packages, or LLM responses exceeding tokens. It can also read configuration files with comments.

[![npm Package Version](https://img.shields.io/npm/v/best-effort-json-parser)](https://www.npmjs.com/package/best-effort-json-parser)
[![Minified Package Size](https://img.shields.io/bundlephobia/min/best-effort-json-parser)](https://bundlephobia.com/package/best-effort-json-parser)
[![Minified and Gzipped Package Size](https://img.shields.io/bundlephobia/minzip/best-effort-json-parser)](https://bundlephobia.com/package/best-effort-json-parser)
[![npm Package Downloads](https://img.shields.io/npm/dm/best-effort-json-parser)](https://www.npmtrends.com/best-effort-json-parser)

## Features

- Typescript support
- Isomorphic package: works in Node.js and browsers
- Comment support: `// inline`, `/* multi-line */`, and `<!-- HTML-style -->` comments

## Installation

```bash
npm install best-effort-json-parser
```

You can also install `best-effort-json-parser` with [pnpm](https://pnpm.io/), [yarn](https://yarnpkg.com/), or [slnpm](https://github.com/beenotung/slnpm)

## Usage Example

### Parsing incomplete JSON text

If the string, object, or array is not complete, the parser will return the partial data.

```typescript
import { parse } from 'best-effort-json-parser'

let data = parse(`[1, 2, {"a": "apple`)
console.log(data) // [1, 2, { a: 'apple' }]
```

### Parsing json with comments

Multiple types of comments are supported:

```typescript
import { parse } from 'best-effort-json-parser'

let config = parse(`{
  "database": {
    "host": "localhost", // database server
    "port": 5432, /* default port */
    "ssl": true
  },
  "features": ["auth", "api"] <!-- injected by LLM -->
}`)
```

Comments inside strings are preserved and not treated as comments:

```typescript
let data = parse(`{
  "inline_comment": "// this is not a comment",
  "block_comment": "/* neither is this */",
  "html_comment": \`<!-- \${variable} -->\`,
  "value": 42
}`)
```

**Note:** The parser also supports template literals with backticks (\`) for strings, in addition to single and double quotes.

## Error Logging

By default, the parser logs errors to `console.error`. You can control error logging behavior:

```typescript
import {
  disableErrorLogging,
  enableErrorLogging,
  setErrorLogger,
} from 'best-effort-json-parser'

// Disable error logging completely
disableErrorLogging()

// Re-enable error logging (default behavior)
enableErrorLogging()

// Set a custom error logger
setErrorLogger((message, data) => {
  // Your custom logging logic here
  console.log('Custom error:', message, data)

  // Common destinations for error data:
  // - Database storage for analysis
  // - File system logging
  // - Third-party services (Sentry, LogRocket, etc.)
  // - Monitoring and alerting systems
})
```

## Typescript Signature

```typescript
// Main parse function
function parse(s: string | undefined | null): any

// Parse namespace with additional properties
namespace parse {
  lastParseReminding: string | undefined
  onExtraToken: (text: string, data: any, reminding: string) => void | undefined
}

// Error logging functions
function setErrorLogger(logger: (message: string, data?: any) => void): void
function disableErrorLogging(): void
function enableErrorLogging(): void
```

More examples see [parse.spec.ts](./src/parse.spec.ts)

## License

This is free and open-source software (FOSS) with
[BSD-2-Clause License](./LICENSE)


================================================
FILE: example/index.ts
================================================
import { parse } from 'best-effort-json-parser'

// Incomplete string, object, array
let data = parse(`[1,2,{"a":"apple`)
console.log(data) // [1, 2, { a: 'apple' }]

// Example with comments
let dataWithComments = parse(`{
  "users": [
    {
      "name": "Alice", // admin user
      "role": "admin"
    },
    {
      "name": "Bob", /* regular user
      with multi-line comment */
      "role": "user"
    }
  ],
  "version": 1.2.3
}`)
console.log(dataWithComments)
/*
{
  users: [
    { name: 'Alice', role: 'admin' },
    { name: 'Bob', role: 'user' },
  ],
  version: '1.2.3'
}
*/


================================================
FILE: example/package.json
================================================
{
  "name": "example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "ts-node index.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "best-effort-json-parser": "file:.."
  },
  "devDependencies": {
    "ts-node": "^9.1.1",
    "typescript": "^5.9.3"
  }
}


================================================
FILE: package.json
================================================
{
  "name": "best-effort-json-parser",
  "version": "1.4.0",
  "description": "Parse incomplete json text in best-effort manner",
  "keywords": [
    "json",
    "parser",
    "auto-fix",
    "auto-repair",
    "best-effort"
  ],
  "author": "Beeno Tung <aabbcc1241@yahoo.com.hk> (https://beeno-tung.surge.sh)",
  "license": "BSD-2-Clause",
  "main": "dist/parse.js",
  "types": "dist/parse.d.ts",
  "scripts": {
    "test": "run-s format tsc mocha",
    "clean": "rimraf dist",
    "format": "prettier --write \"src/**/*.ts\"",
    "postformat": "tslint -p . --fix && format-json-cli",
    "build": "run-s clean tsc",
    "tsc": "tsc -p .",
    "mocha": "ts-mocha \"src/**/*.spec.ts\"",
    "coverage": "nyc npm run mocha -- --reporter=progress",
    "report:update": "nyc --reporter=lcov npm run mocha -- --reporter=progress",
    "report:open": "open-cli coverage/lcov-report/index.html",
    "report": "run-s report:update report:open",
    "prepublishOnly": "run-s test build"
  },
  "directories": {
    "example": "example",
    "test": "test"
  },
  "files": [
    "dist",
    "src"
  ],
  "devDependencies": {
    "@types/chai": "^4.2.14",
    "@types/mocha": "^8.2.0",
    "@types/node": "*",
    "@types/sinon": "^9.0.9",
    "chai": "^4.2.0",
    "format-json-cli": "^1.0.1",
    "mocha": "^8.2.1",
    "npm-run-all": "^4.1.5",
    "nyc": "^15.1.0",
    "open-cli": "^6.0.1",
    "prettier": "^2.2.1",
    "rimraf": "^3.0.2",
    "sinon": "^9.2.2",
    "ts-mocha": "^8.0.0",
    "ts-node": "^9.1.1",
    "tslint": "^6.1.3",
    "tslint-config-prettier": "^1.18.0",
    "tslint-eslint-rules": "^5.4.0",
    "tslint-etc": "^1.13.9",
    "typescript": "^4.8.3"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/beenotung/best-effort-json-parser.git"
  },
  "bugs": {
    "url": "https://github.com/beenotung/best-effort-json-parser/issues"
  },
  "homepage": "https://github.com/beenotung/best-effort-json-parser#readme"
}


================================================
FILE: src/parse.spec.ts
================================================
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'])
      })
      it('should parse string with backticks', function () {
        expect(parse(`\`"Alice's"\``)).equals(`"Alice's"`)
        expect(
          parse(`[
            \`double quote: "\`,
            \`single quote: '\`,
            ${'`backtick: \\``'}
          ]`),
        ).deep.equals([`double quote: "`, `single quote: '`, 'backtick: `'])
      })
    })
    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: '' })
    })
  })

  context('comment in json', () => {
    it('should ignore inline comment', function () {
      // test with //
      let text = `{
        "a": 1, // comment
        "b": 2
      }`
      expect(parse(text)).deep.equals({ a: 1, b: 2 })
    })
    it('should ignore multi-line comment', function () {
      // test with /* */
      let text = `{
        "a": 1, /* line 1
        line 2
        line 3 */
        "b": 2
      }`
      expect(parse(text)).deep.equals({ a: 1, b: 2 })
    })
    it('should not strip comments inside strings', function () {
      let text = `{
        "comment": "// this is not a comment",
        "block": "/* neither is this */",
        "value": 42
      }`
      expect(parse(text)).deep.equals({
        comment: '// this is not a comment',
        block: '/* neither is this */',
        value: 42,
      })
    })
    it('should handle comments at beginning and end', function () {
      let text = `// start comment
      {
        "a": 1,
        "b": 2
      } // end comment`
      expect(parse(text)).deep.equals({ a: 1, b: 2 })
    })
    it('should handle mixed comment types', function () {
      let text = `{
        "a": 1, // inline comment
        /* multi-line
           comment */
        "b": 2
      }`
      expect(parse(text)).deep.equals({ a: 1, b: 2 })
    })
    it('should handle empty comments', function () {
      let text = `{
        "a": 1, //
        "b": 2, /**/
        "c": 3
      }`
      expect(parse(text)).deep.equals({ a: 1, b: 2, c: 3 })
    })
    it('should handle comments with special characters', function () {
      let text = `{
        "a": 1, // comment with "quotes" and 'single quotes'
        "b": 2 /* comment with { } [ ] */
      }`
      expect(parse(text)).deep.equals({ a: 1, b: 2 })
    })
    it('should handle html style of comments', function () {
      let text = `[
        "line 1", <!-- comment -->
        "line 2"
      ]`
      expect(parse(text)).deep.equals(['line 1', 'line 2'])
    })
  })
})


================================================
FILE: src/parse.ts
================================================
type Error = unknown

let logError = console.error

// for testing (spy/mock)
export function setErrorLogger(
  logger: (message: string, data?: any) => void,
): void {
  logError = logger
}

export function disableErrorLogging(): void {
  logError = () => {
    /* do not output to console */
  }
}

export function enableErrorLogging(): void {
  logError = console.error
}

export function stripComments(text: string): string {
  const buffer: string[] = []

  let in_string = false
  let string_char = ''
  let string_escaped = false

  let in_inline_comment = false

  let in_block_comment = false
  let saw_star = false

  let in_html_comment = false
  let saw_hyphen = 0

  for (const char of text) {
    // handle string content
    if (in_string) {
      // handle escaped sequence payload
      if (string_escaped) {
        string_escaped = false
        buffer.push(char)
        continue
      }

      // handle start of escape sequence
      if (char === '\\') {
        string_escaped = true
        buffer.push(char)
        continue
      }

      // handle end of string
      if (char === string_char) {
        in_string = false
        buffer.push(char)
        continue
      }

      // otherwise take the content of string
      buffer.push(char)
      continue
    }

    // handle inline comment content
    if (in_inline_comment) {
      // handle end of inline comment
      if (char === '\n') {
        in_inline_comment = false
        continue
      }

      // otherwise ignore the content of comment
      continue
    }

    const buffer_length = buffer.length
    const last_char = buffer_length === 0 ? '' : buffer[buffer_length - 1]

    // handle block comment content
    if (in_block_comment) {
      // handle end of block comment
      if (char === '*') {
        saw_star = true
        continue
      }
      if (saw_star && char === '/') {
        in_block_comment = false
        continue
      }

      // otherwise ignore the content of comment
      saw_star = false
      continue
    }

    // handle html comment content
    if (in_html_comment) {
      // handle end of html comment
      if (char === '-') {
        saw_hyphen++
        continue
      }
      if (saw_hyphen >= 2 && char === '>') {
        in_html_comment = false
        continue
      }

      // otherwise ignore the content of comment
      saw_hyphen = 0
      continue
    }

    // handle start of inline comment
    if (last_char === '/' && char === '/') {
      buffer.pop()
      in_inline_comment = true
      continue
    }

    // handle start of block comment
    if (last_char === '/' && char === '*') {
      buffer.pop()
      in_block_comment = true
      saw_star = false
      continue
    }

    // handle start of string
    if (char === '"' || char === "'" || char === '`') {
      in_string = true
      string_char = char
      string_escaped = false
      buffer.push(char)
      continue
    }

    // handle start of html comment "<!--"
    if (
      buffer_length >= 4 &&
      char === '-' &&
      buffer[buffer_length - 1] === '-' &&
      buffer[buffer_length - 2] === '!' &&
      buffer[buffer_length - 3] === '<'
    ) {
      in_html_comment = true
      buffer.pop()
      buffer.pop()
      buffer.pop()
      continue
    }

    // otherwise take the content of the text
    buffer.push(char)
  }
  return buffer.join('')
}

export function parse(s: string | undefined | null): any {
  if (s === undefined) {
    return undefined
  }
  if (s === null) {
    return null
  }
  if (s === '') {
    return ''
  }
  // strip comments first
  s = stripComments(s)
  // remove incomplete escaped characters at the end of the string
  s = s.replace(/\\+$/, match =>
    match.length % 2 === 0 ? match : match.slice(0, -1),
  )
  try {
    return JSON.parse(s)
  } catch (e) {
    const [data, reminding] =
      s.trimLeft()[0] === ':'
        ? parseAny(s, e)
        : parseAny(s, e, parseStringWithoutQuote)
    parse.lastParseReminding = reminding
    if (parse.onExtraToken && reminding.length > 0) {
      const trimmedReminding = reminding.trimRight()
      parse.lastParseReminding = trimmedReminding
      if (trimmedReminding.length > 0) {
        parse.onExtraToken(s, data, trimmedReminding)
      }
    }
    return data
  }
}

export namespace parse {
  export let lastParseReminding: string | undefined
  export let onExtraToken: (
    text: string,
    data: any,
    reminding: string,
  ) => void | undefined = (text, data, reminding) => {
    logError('parsed json with extra tokens:', {
      text,
      data,
      reminding,
    })
  }
}

function parseAny(
  s: string,
  e: Error,
  fallback?: Parser<any>,
): ParseResult<any> {
  const parser = parsers[s[0]] || fallback
  if (!parser) {
    logError(`no parser registered for ${JSON.stringify(s[0])}:`, { s })
    throw e
  }
  return parser(s, e)
}

function parseStringCasual(
  s: string,
  e: Error,
  delimiters?: string[],
): ParseResult<string> {
  if (s[0] === '"') {
    return parseString(s)
  }
  if (s[0] === "'") {
    return parseSingleQuoteString(s)
  }
  if (s[0] === '`') {
    return parseBacktickString(s)
  }
  return parseStringWithoutQuote(s, e, delimiters)
}

type Code = string
type Parser<T> = (s: Code, e: Error) => ParseResult<T>
type ParseResult<T> = [T, Code]

const parsers: Record<string, Parser<any>> = {}

function skipSpace(s: string): string {
  return s.trimLeft()
}

parsers[' '] = parseSpace
parsers['\r'] = parseSpace
parsers['\n'] = parseSpace
parsers['\t'] = parseSpace

function parseSpace(s: string, e: Error) {
  s = skipSpace(s)
  return parseAny(s, e)
}

parsers['['] = parseArray

function parseArray(s: string, e: Error): ParseResult<any[]> {
  s = s.substr(1) // skip starting '['
  const acc: any[] = []
  s = skipSpace(s)
  for (; s.length > 0; ) {
    if (s[0] === ']') {
      s = s.substr(1) // skip ending ']'
      break
    }
    const res = parseAny(s, e, (s, e) =>
      parseStringWithoutQuote(s, e, [',', ']']),
    )
    acc.push(res[0])
    s = res[1]
    s = skipSpace(s)
    if (s[0] === ',') {
      s = s.substring(1)
      s = skipSpace(s)
    }
  }
  return [acc, s]
}

for (const c of '0123456789.-'.slice()) {
  parsers[c] = parseNumber
}

function parseNumber(s: string): ParseResult<number | string> {
  for (let i = 0; i < s.length; i++) {
    const c = s[i]
    if (parsers[c] === parseNumber) {
      continue
    }
    const num = s.substring(0, i)
    s = s.substring(i)
    return [numToStr(num), s]
  }
  return [numToStr(s), '']
}

function numToStr(s: string) {
  if (s === '-') {
    return -0
  }
  const num = +s
  if (Number.isNaN(num)) {
    return s
  }
  return num
}

parsers['"'] = parseString

function parseString(s: string): ParseResult<string> {
  for (let i = 1; i < s.length; i++) {
    const c = s[i]
    if (c === '\\') {
      i++
      continue
    }
    if (c === '"') {
      const str = fixEscapedCharacters(s.substring(0, i + 1))
      s = s.substring(i + 1)
      return [JSON.parse(str), s]
    }
  }
  return [JSON.parse(fixEscapedCharacters(s) + '"'), '']
}

function fixEscapedCharacters(s: string): string {
  return s.replace(/\n/g, '\\n').replace(/\t/g, '\\t').replace(/\r/g, '\\r')
}

parsers["'"] = parseSingleQuoteString

function parseSingleQuoteString(s: string): ParseResult<string> {
  for (let i = 1; i < s.length; i++) {
    const c = s[i]
    if (c === '\\') {
      i++
      continue
    }
    if (c === "'") {
      const str = fixEscapedCharacters(s.substring(0, i + 1))
      s = s.substring(i + 1)
      return [JSON.parse('"' + str.slice(1, -1) + '"'), s]
    }
  }
  return [JSON.parse('"' + fixEscapedCharacters(s.slice(1)) + '"'), '']
}

parsers['`'] = parseBacktickString

function parseBacktickString(s: string): ParseResult<string> {
  const buffer: string[] = []
  let is_escaped = false
  let escape_count = 0
  for (let i = 1; i < s.length; i++) {
    const c = s[i]
    if (is_escaped) {
      buffer.push(c)
      is_escaped = false
      continue
    }
    if (c === '\\') {
      is_escaped = true
      escape_count++
      continue
    }
    if (c === '`') {
      const str = buffer.join('')
      s = s.substring(str.length + escape_count + 2)
      return [str, s]
    }
    buffer.push(c)
  }
  return [buffer.join(''), s.slice(1)]
}

function parseStringWithoutQuote(
  s: string,
  e: Error,
  delimiters: string[] = [' '],
): ParseResult<string> {
  const index = Math.min(
    ...delimiters.map(delimiter => {
      const index = s.indexOf(delimiter)
      return index === -1 ? s.length : index
    }),
  )
  const value = s.substring(0, index).trim()
  const rest = s.substring(index)
  return [value, rest]
}

parsers['{'] = parseObject

function parseObject(s: string, e: Error): ParseResult<object> {
  s = s.substr(1) // skip starting '{'
  const acc: any = {}
  s = skipSpace(s)
  for (; s.length > 0; ) {
    if (s[0] === '}') {
      s = s.substr(1) // skip ending '}'
      break
    }

    const keyRes = parseStringCasual(s, e, [':', '}'])
    const key = keyRes[0]
    s = keyRes[1]

    s = skipSpace(s)
    if (s[0] !== ':') {
      acc[key] = undefined
      break
    }
    s = s.substr(1) // skip ':'
    s = skipSpace(s)

    if (s.length === 0) {
      acc[key] = undefined
      break
    }
    const valueRes = parseAny(s, e)
    acc[key] = valueRes[0]
    s = valueRes[1]
    s = skipSpace(s)

    if (s[0] === ',') {
      s = s.substr(1)
      s = skipSpace(s)
    }
  }
  return [acc, s]
}

parsers['t'] = parseTrue

function parseTrue(s: string, e: Error): ParseResult<true> {
  return parseToken(s, `true`, true, e)
}

parsers['f'] = parseFalse

function parseFalse(s: string, e: Error): ParseResult<false> {
  return parseToken(s, `false`, false, e)
}

parsers['n'] = parseNull

function parseNull(s: string, e: Error): ParseResult<null> {
  return parseToken(s, `null`, null, e)
}

function parseToken<T>(
  s: string,
  tokenStr: string,
  tokenVal: T,
  e: Error,
): ParseResult<T> {
  for (let i = tokenStr.length; i >= 1; i--) {
    if (s.startsWith(tokenStr.slice(0, i))) {
      return [tokenVal, s.slice(i)]
    }
  }
  /* istanbul ignore next */
  {
    const prefix = JSON.stringify(s.slice(0, tokenStr.length))
    logError(`unknown token starting with ${prefix}:`, { s })
    throw e
  }
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "allowJs": false,
    "sourceMap": true,
    "declaration": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "strict": true,
    "skipLibCheck": true,
    "downlevelIteration": false,
    "importHelpers": false,
    "lib": [
      "dom",
      "es2018"
    ],
    "module": "commonjs",
    "moduleResolution": "node",
    "target": "es2015",
    "outDir": "dist"
  },
  "include": [
    "src/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "src/**/*.spec.ts"
  ],
  "compileOnSave": false,
  "atom": {
    "rewriteTsconfig": false
  }
}


================================================
FILE: tslint.json
================================================
{
  "extends": [
    "tslint:recommended",
    "tslint-eslint-rules",
    "tslint-etc",
    "tslint-config-prettier"
  ],
  "rules": {
    "no-unused-declaration": true,
    "array-type": [
      true,
      "array-simple"
    ],
    "interface-over-type-literal": false,
    "no-reference": true,
    "only-arrow-functions": false,
    "max-classes-per-file": false,
    "no-namespace": false,
    "align": [
      false,
      "arguments",
      "elements",
      "members",
      "parameters",
      "statements"
    ],
    "arrow-parens": [
      true,
      "ban-single-arg-parens"
    ],
    "jsdoc-format": false,
    "space-before-function-paren": false,
    "no-invalid-this": true,
    "callable-types": true,
    "class-name": true,
    "comment-format": [
      true,
      "check-space"
    ],
    "curly": true,
    "eofline": true,
    "forin": true,
    "import-blacklist": [
      true
    ],
    "import-spacing": true,
    "indent": [
      true,
      "spaces"
    ],
    "label-position": true,
    "max-line-length": [
      true,
      140
    ],
    "member-access": false,
    "member-ordering": [
      true,
      {
        "order": "instance-sandwich"
      }
    ],
    "no-arg": true,
    "no-bitwise": true,
    "no-console": [
      true,
      "debug",
      "info",
      "time",
      "timeEnd",
      "trace"
    ],
    "no-construct": true,
    "no-debugger": true,
    "no-duplicate-variable": true,
    "no-empty": true,
    "no-empty-interface": true,
    "no-eval": true,
    "no-inferrable-types": false,
    "no-shadowed-variable": false,
    "no-string-literal": false,
    "no-string-throw": true,
    "no-switch-case-fall-through": true,
    "no-trailing-whitespace": true,
    "no-unused-expression": [
      true,
      "allow-fast-null-checks"
    ],
    "no-var-keyword": true,
    "object-literal-sort-keys": false,
    "one-line": [
      true,
      "check-open-brace",
      "check-catch",
      "check-else",
      "check-whitespace"
    ],
    "prefer-const": true,
    "quotemark": [
      false,
      "single",
      "avoid-escape"
    ],
    "radix": true,
    "semicolon": [
      false,
      "never",
      "ignore-interfaces"
    ],
    "triple-equals": true,
    "typedef-whitespace": [
      true,
      {
        "call-signature": "nospace",
        "index-signature": "nospace",
        "parameter": "nospace",
        "property-declaration": "nospace",
        "variable-declaration": "nospace"
      }
    ],
    "unified-signatures": false,
    "variable-name": false,
    "whitespace": [
      true,
      "check-branch",
      "check-decl",
      "check-operator",
      "check-separator",
      "check-type"
    ],
    "interface-name": false,
    "ordered-imports": true,
    "trailing-comma": [
      true,
      {
        "multiline": {
          "arrays": "always",
          "exports": "always",
          "functions": "ignore",
          "imports": "always",
          "objects": "always",
          "typeLiterals": "never"
        },
        "singleline": "never"
      }
    ]
  }
}
Download .txt
gitextract_ujoa3yj2/

├── .editorconfig
├── .gitignore
├── .idea/
│   ├── .gitignore
│   ├── best-effort-json-parser.iml
│   └── modules.xml
├── .nycrc.json
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── example/
│   ├── index.ts
│   └── package.json
├── package.json
├── src/
│   ├── parse.spec.ts
│   └── parse.ts
├── tsconfig.json
└── tslint.json
Download .txt
SYMBOL INDEX (27 symbols across 2 files)

FILE: src/parse.spec.ts
  function testIncomplete (line 75) | function testIncomplete(str: string, val: boolean) {

FILE: src/parse.ts
  type Error (line 1) | type Error = unknown
  function setErrorLogger (line 6) | function setErrorLogger(
  function disableErrorLogging (line 12) | function disableErrorLogging(): void {
  function enableErrorLogging (line 18) | function enableErrorLogging(): void {
  function stripComments (line 22) | function stripComments(text: string): string {
  function parse (line 160) | function parse(s: string | undefined | null): any {
  function parseAny (line 210) | function parseAny(
  function parseStringCasual (line 223) | function parseStringCasual(
  type Code (line 240) | type Code = string
  type Parser (line 241) | type Parser<T> = (s: Code, e: Error) => ParseResult<T>
  type ParseResult (line 242) | type ParseResult<T> = [T, Code]
  function skipSpace (line 246) | function skipSpace(s: string): string {
  function parseSpace (line 255) | function parseSpace(s: string, e: Error) {
  function parseArray (line 262) | function parseArray(s: string, e: Error): ParseResult<any[]> {
  function parseNumber (line 289) | function parseNumber(s: string): ParseResult<number | string> {
  function numToStr (line 302) | function numToStr(s: string) {
  function parseString (line 315) | function parseString(s: string): ParseResult<string> {
  function fixEscapedCharacters (line 331) | function fixEscapedCharacters(s: string): string {
  function parseSingleQuoteString (line 337) | function parseSingleQuoteString(s: string): ParseResult<string> {
  function parseBacktickString (line 355) | function parseBacktickString(s: string): ParseResult<string> {
  function parseStringWithoutQuote (line 381) | function parseStringWithoutQuote(
  function parseObject (line 399) | function parseObject(s: string, e: Error): ParseResult<object> {
  function parseTrue (line 440) | function parseTrue(s: string, e: Error): ParseResult<true> {
  function parseFalse (line 446) | function parseFalse(s: string, e: Error): ParseResult<false> {
  function parseNull (line 452) | function parseNull(s: string, e: Error): ParseResult<null> {
  function parseToken (line 456) | function parseToken<T>(
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (50K chars).
[
  {
    "path": ".editorconfig",
    "chars": 464,
    "preview": "# EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs\n# http:/"
  },
  {
    "path": ".gitignore",
    "chars": 110,
    "preview": "\nnode_modules/\npackage-lock.json\npnpm-lock.yaml\npnpm-debug.log\ndist/\n.cache/\n*.tgz\n.env\n.nyc_output\ncoverage/\n"
  },
  {
    "path": ".idea/.gitignore",
    "chars": 50,
    "preview": "*\n!modules.xml\n!*.iml\n!dictionaries/\n!.gitignore\n\n"
  },
  {
    "path": ".idea/best-effort-json-parser.iml",
    "chars": 662,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"JAVA_MODULE\" version=\"4\">\n  <component name=\"NewModuleRootManager\" "
  },
  {
    "path": ".idea/modules.xml",
    "chars": 305,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n   "
  },
  {
    "path": ".nycrc.json",
    "chars": 71,
    "preview": "{\n  \"include\": \"src/**/*.ts\",\n  \"exclude\": \"src/**/*.{test,spec}.ts\"\n}\n"
  },
  {
    "path": ".prettierignore",
    "chars": 66,
    "preview": "*.macro.ts\nsrc/assets/*\nsrc/components.d.ts\nsrc/polyfill-array.ts\n"
  },
  {
    "path": ".prettierrc",
    "chars": 302,
    "preview": "{\n  \"singleQuote\": true,\n  \"jsxSingleQuote\": false,\n  \"quoteProps\": \"consistent\",\n  \"overrides\": [\n    {\n      \"files\": "
  },
  {
    "path": "LICENSE",
    "chars": 1344,
    "preview": "BSD 2-Clause License\n\nCopyright (c) [2020], [Beeno Tung (Tung Cheung Leong)]\nAll rights reserved.\n\nRedistribution and us"
  },
  {
    "path": "README.md",
    "chars": 3608,
    "preview": "# best-effort-json-parser\n\nParse incomplete JSON text in best-effort manner with support for comments. Useful for partia"
  },
  {
    "path": "example/index.ts",
    "chars": 588,
    "preview": "import { parse } from 'best-effort-json-parser'\n\n// Incomplete string, object, array\nlet data = parse(`[1,2,{\"a\":\"apple`"
  },
  {
    "path": "example/package.json",
    "chars": 340,
    "preview": "{\n  \"name\": \"example\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"ts-n"
  },
  {
    "path": "package.json",
    "chars": 1959,
    "preview": "{\n  \"name\": \"best-effort-json-parser\",\n  \"version\": \"1.4.0\",\n  \"description\": \"Parse incomplete json text in best-effort"
  },
  {
    "path": "src/parse.spec.ts",
    "chars": 21803,
    "preview": "import sinon from 'sinon'\nimport { expect } from 'chai'\nimport { parse, setErrorLogger } from './parse'\n\nlet onExtraToke"
  },
  {
    "path": "src/parse.ts",
    "chars": 10316,
    "preview": "type Error = unknown\n\nlet logError = console.error\n\n// for testing (spy/mock)\nexport function setErrorLogger(\n  logger: "
  },
  {
    "path": "tsconfig.json",
    "chars": 674,
    "preview": "{\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"allowJs\": false,\n  "
  },
  {
    "path": "tslint.json",
    "chars": 3065,
    "preview": "{\n  \"extends\": [\n    \"tslint:recommended\",\n    \"tslint-eslint-rules\",\n    \"tslint-etc\",\n    \"tslint-config-prettier\"\n  ]"
  }
]

About this extraction

This page contains the full source code of the beenotung/best-effort-json-parser GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (44.7 KB), approximately 14.0k tokens, and a symbol index with 27 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!