├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── codeql.yml ├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── SECURITY.md ├── benchmark ├── .gitignore ├── benchmark.ts ├── package.json ├── samples │ ├── history.json │ └── transactions.json └── tsconfig.json ├── bin └── release.sh ├── examples ├── node-esm-polyfill │ ├── package.json │ ├── src │ │ ├── do-parse.ts │ │ ├── index.ts │ │ ├── keypath.ts │ │ ├── native-reviver.ts │ │ ├── simple.ts │ │ └── source-text-access.ts │ └── tsconfig.json ├── node-esm-rules │ ├── package.json │ ├── src │ │ ├── common.ts │ │ ├── explicit.ts │ │ └── polyfilled.ts │ └── tsconfig.json └── node-esm │ ├── package.json │ ├── src │ ├── keypath.ts │ ├── native-reviver.ts │ ├── proto-throw.ts │ ├── simple.ts │ └── source-text-access.ts │ └── tsconfig.json ├── package.json ├── packages ├── json-parse-polyfill │ ├── .gitignore │ ├── api-extractor.json │ ├── dist │ │ ├── index-alpha.d.ts │ │ ├── index-beta.d.ts │ │ ├── index.cjs │ │ ├── index.cjs.map │ │ ├── index.cjs.meta.json │ │ ├── index.d.ts │ │ ├── index.mjs │ │ ├── index.mjs.map │ │ ├── index.mjs.meta.json │ │ └── tsdoc-metadata.json │ ├── esbuild.ts │ ├── json-parse-polyfill.api.md │ ├── package.json │ ├── src │ │ ├── global.d.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.types.json ├── json-parser-rules │ ├── .gitignore │ ├── .mocharc.json │ ├── api-extractor.json │ ├── bin │ │ └── release.sh │ ├── dist │ │ ├── index-alpha.d.ts │ │ ├── index-beta.d.ts │ │ ├── index.cjs │ │ ├── index.cjs.map │ │ ├── index.cjs.meta.json │ │ ├── index.d.ts │ │ ├── index.mjs │ │ ├── index.mjs.map │ │ ├── index.mjs.meta.json │ │ └── tsdoc-metadata.json │ ├── esbuild.ts │ ├── json-parser-rules.api.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── json-parser-rules.ts │ │ └── json-parser.test.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.test.json │ └── tsconfig.types.json └── json-parser │ ├── .gitignore │ ├── .mocharc.json │ ├── .nycrc.json │ ├── api-extractor.json │ ├── dist │ ├── index-alpha.d.ts │ ├── index-beta.d.ts │ ├── index.cjs │ ├── index.cjs.map │ ├── index.cjs.meta.json │ ├── index.d.ts │ ├── index.mjs │ ├── index.mjs.map │ ├── index.mjs.meta.json │ └── tsdoc-metadata.json │ ├── esbuild.ts │ ├── json-parser.api.md │ ├── package.json │ ├── src │ ├── index.ts │ ├── json-parser.test.ts │ └── json-parser.ts │ ├── test │ ├── samples.test.ts │ └── samples │ │ ├── blocks.json │ │ ├── btc-unconfirmed-txes.json │ │ ├── food.json │ │ ├── gh-events.json │ │ ├── gh-gists.json │ │ ├── history.json │ │ ├── super-json.json │ │ └── transactions.json │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.test.json │ └── tsconfig.types.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── readme ├── build.ts ├── package.json ├── src │ ├── index.md │ ├── license.md │ ├── usage-polyfill.md │ ├── usage-ponyfill.md │ └── usage-rules.md ├── tsconfig.json └── types │ └── untyped │ └── index.d.ts └── tsconfig.common.json /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.json linguist-language=JSON-with-Comments 2 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '32 3 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Use only 'java' to analyze code written in Java, Kotlin or both 38 | # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both 39 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 40 | 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v3 44 | 45 | # Initializes the CodeQL tools for scanning. 46 | - name: Initialize CodeQL 47 | uses: github/codeql-action/init@v2 48 | with: 49 | languages: ${{ matrix.language }} 50 | # If you wish to specify custom queries, you can do so here or in a config file. 51 | # By default, queries listed here will override any specified in a config file. 52 | # Prefix the list here with "+" to use these queries and those in the config file. 53 | 54 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 55 | # queries: security-extended,security-and-quality 56 | 57 | - run: | 58 | echo "Building the packages…" 59 | npm i -g pnpm && pnpm install -r && pnpm run -r build 60 | 61 | - name: Perform CodeQL Analysis 62 | uses: github/codeql-action/analyze@v2 63 | with: 64 | category: "/language:${{matrix.language}}" 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | temp/ 4 | 5 | /.idea/ 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing 3 | 4 | Before making any changes please create an issue to 5 | discuss them first, this will ensure that PR will be 6 | effectively merged and the work won't be duplicated. 7 | 8 | ## Prerequisites 9 | 10 | - [Node.js](https://nodejs.org/en/download/) v.14 11 | - [pnpm](https://pnpm.io/) package manager 12 | 13 | ## Development workflow 14 | 15 | - Fork and clone the repository, 16 | 17 | - Install all dependencies: 18 | ```sh 19 | pnpm install 20 | ``` 21 | 22 | - Build the package: 23 | ```sh 24 | pnpm build 25 | ``` 26 | 27 | - Make changes, 28 | 29 | - Create and submit a PR. 30 | 31 | 32 | ## Legal 33 | 34 | By submitting a PR you confirm that you are an author of all 35 | the contributed code, and you waive all the copyright rights 36 | to the TON FOUNDATION. 37 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | # Security Policy 3 | 4 | The code in this library is used to handle cryptocurrency, 5 | which is a security-sensitive field. Therefore, we take 6 | security very seriously and apply the following measures: 7 | 8 | - we try to keep third-party dependencies to an absolute 9 | minimum, 10 | 11 | - all generated code is committed to the VCS and is manually 12 | checked by the release manager before actually releasing 13 | them, 14 | 15 | - all code is getting [automatically checked](https://github.com/ton-js/json-parser/security/code-scanning) 16 | by the GitHub Code QL with every commit, 17 | 18 | - all commits are required to be signed with the PGP keys, 19 | 20 | - all accounts used to release the library are protected 21 | with MFA/OTP, 22 | 23 | - additionally, published package is checked by the npm for 24 | known vulnerabilities, 25 | 26 | - we are using Dependabot to make sure that all dependencies 27 | are secure as well. 28 | 29 | 30 | ## Supported Versions 31 | 32 | Please use the latest version of the library to ensure the 33 | best possible security and performance. 34 | 35 | 36 | ## Reporting a Vulnerability 37 | 38 | DO NOT PUBLISH VULNERABILITY INFORMATION IN THE OPEN SOURCES. 39 | 40 | If you have found a vulnerability in the library, please 41 | write to the [slava@fomin.io](mailto:slava@fomin.io) directly, 42 | so it could be discretely handled. 43 | -------------------------------------------------------------------------------- /benchmark/.gitignore: -------------------------------------------------------------------------------- 1 | /results/ 2 | -------------------------------------------------------------------------------- /benchmark/benchmark.ts: -------------------------------------------------------------------------------- 1 | 2 | import { readFile } from 'node:fs/promises'; 3 | import { dirname, join as pathJoin } from 'node:path'; 4 | import { fileURLToPath } from 'node:url'; 5 | 6 | import * as benny from 'benny'; 7 | 8 | import { parseJson } from '@ton.js/json-parser'; 9 | 10 | import manifest from '../packages/json-parser/package.json' assert { type: 'json' }; 11 | 12 | 13 | const __dirname = dirname( 14 | fileURLToPath(import.meta.url) 15 | ); 16 | 17 | const outputPath = pathJoin(__dirname, 'results'); 18 | 19 | 20 | createSuite( 21 | 'big-dataset', 22 | await loadDataset('history.json') 23 | ); 24 | 25 | createSuite( 26 | 'normal-dataset', 27 | await loadDataset('transactions.json') 28 | ); 29 | 30 | 31 | async function loadDataset(filename: string): Promise { 32 | const filePath = pathJoin(__dirname, 'samples/', filename); 33 | return readFile(filePath, 'utf-8'); 34 | } 35 | 36 | function createSuite(name: string, dataset: string) { 37 | 38 | benny.suite( 39 | 40 | name, 41 | 42 | benny.add('native', () => { 43 | JSON.parse(dataset); 44 | }), 45 | 46 | benny.add('parse-json', () => { 47 | parseJson(dataset); 48 | }), 49 | 50 | benny.cycle(), 51 | 52 | benny.complete(), 53 | 54 | benny.save({ 55 | folder: outputPath, 56 | file: name, 57 | format: 'json', 58 | version: manifest.version, 59 | }), 60 | 61 | benny.save({ 62 | folder: outputPath, 63 | file: name, 64 | format: 'chart.html', 65 | version: manifest.version, 66 | }), 67 | 68 | benny.save({ 69 | folder: outputPath, 70 | file: name, 71 | format: 'table.html', 72 | version: manifest.version, 73 | }), 74 | 75 | ); 76 | 77 | } 78 | -------------------------------------------------------------------------------- /benchmark/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "benchmark", 3 | "private": true, 4 | "license": "MIT", 5 | "type": "module", 6 | "scripts": { 7 | "build": "if ! [ -d ./results ]; then run-s benchmark; fi", 8 | "benchmark": "ts-node ./benchmark.ts" 9 | }, 10 | "devDependencies": { 11 | "@types/node": "^18.11.18", 12 | "benny": "^3.7.1", 13 | "npm-run-all": "^4.1.5", 14 | "ts-node": "^10.9.1", 15 | "typescript": "^4.9.4" 16 | }, 17 | "dependencies": { 18 | "@ton.js/json-parser": "workspace:*" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /benchmark/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../tsconfig.common.json", 4 | "compilerOptions": { 5 | "module": "ESNext", 6 | "resolveJsonModule": true 7 | }, 8 | "files": [ 9 | "benchmark.ts" 10 | ], 11 | "ts-node": { 12 | "esm": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /bin/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -o pipefail 5 | 6 | SCRIPT_PATH="$(dirname "$0")" 7 | ROOT_PATH="${SCRIPT_PATH}/.." 8 | 9 | PKG1_PATH="${ROOT_PATH}/packages/json-parser" 10 | MAN1_PATH="${PKG1_PATH}/package.json" 11 | 12 | PKG2_PATH="${ROOT_PATH}/packages/json-parse-polyfill" 13 | MAN2_PATH="${PKG2_PATH}/package.json" 14 | 15 | # Reading some data from the main package manifest 16 | NAME1=$(jq -r '.name' "${MAN1_PATH}") 17 | NAME2=$(jq -r '.name' "${MAN2_PATH}") 18 | TAG=$(jq -r '.publishConfig.tag' "${MAN1_PATH}") 19 | VERSION=$(jq -r '.version' "${MAN1_PATH}") 20 | 21 | 22 | echo "Starting to release the packages…" 23 | 24 | echo "Building all the projects in the monorepo first…" 25 | pnpm run -r build 26 | 27 | # Asking user for tag and version 28 | read -e -p "Tag: " -i "${TAG}" TAG 29 | read -e -p "Version: " -i "${VERSION}" VERSION 30 | 31 | echo "Updating packages manifests…" 32 | 33 | jq ".version = \"${VERSION}\"" "${MAN1_PATH}" > "${PKG1_PATH}/~package.json" 34 | mv "${PKG1_PATH}/~package.json" "${MAN1_PATH}" 35 | 36 | jq ".version = \"${VERSION}\"" "${MAN2_PATH}" > "${PKG2_PATH}/~package.json" 37 | mv "${PKG2_PATH}/~package.json" "${MAN2_PATH}" 38 | 39 | echo "Copying the README files…" 40 | cp "${ROOT_PATH}/README.md" "${PKG1_PATH}/" 41 | cp "${ROOT_PATH}/README.md" "${PKG2_PATH}/" 42 | 43 | # Making sure Git is "clean" 44 | if [ -n "$(git status --porcelain)" ]; then 45 | echo "Git repository content was updated, please commit all the changes and start again" 46 | exit 1 || return 1 47 | fi 48 | 49 | echo "Publishing the package: ${NAME1}…" 50 | (cd "${PKG1_PATH}" && npm publish --tag "${TAG}") 51 | 52 | echo "Publishing the package: ${NAME2}…" 53 | (cd "${PKG2_PATH}" && npm publish --tag "${TAG}") 54 | 55 | printf "\nRelease complete…\n\n" 56 | 57 | printf "Install the packages with:\n" 58 | echo "[${NAME1}]" 59 | echo "— ${NAME1}@${VERSION}" 60 | echo "— ${NAME1}@${TAG}" 61 | echo "" 62 | echo "[${NAME2}]" 63 | echo "— ${NAME2}@${VERSION}" 64 | echo "— ${NAME2}@${TAG}" 65 | -------------------------------------------------------------------------------- /examples/node-esm-polyfill/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-node-esm", 3 | "version": "0.0.0", 4 | "description": "Node.js ESM examples", 5 | "private": true, 6 | "license": "MIT", 7 | "type": "module", 8 | "scripts": { 9 | "build": "run-s clean build:compile", 10 | "build:compile": "tsc", 11 | "clean": "rimraf ./dist/", 12 | "__examples__": ":", 13 | "index": "ts-node src/index.ts", 14 | "keypath": "ts-node src/keypath.ts", 15 | "native-reviver": "ts-node src/native-reviver.ts", 16 | "simple": "ts-node src/simple.ts", 17 | "source-text-access": "ts-node src/source-text-access.ts" 18 | }, 19 | "engines": { 20 | "node": ">=14" 21 | }, 22 | "dependencies": { 23 | "@ton.js/json-parse-polyfill": "workspace:*" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^14.18.36", 27 | "npm-run-all": "^4.1.5", 28 | "rimraf": "^3.0.2", 29 | "ts-node": "^10.9.1", 30 | "typescript": "^4.9.4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/node-esm-polyfill/src/do-parse.ts: -------------------------------------------------------------------------------- 1 | 2 | export function doParse(source: string): Type { 3 | return JSON.parse(source, (key, value, context) => 4 | (key.endsWith('BN') ? BigInt(context.source) : value) 5 | ); 6 | } 7 | -------------------------------------------------------------------------------- /examples/node-esm-polyfill/src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import '@ton.js/json-parse-polyfill'; 3 | 4 | import { strict as assert } from 'node:assert'; 5 | 6 | import { doParse } from './do-parse.js'; 7 | 8 | 9 | /** 10 | * This examples tests global type declaration loading and 11 | * propagation to other TypeScript modules (do-parse.ts). 12 | */ 13 | 14 | 15 | interface DocumentType { 16 | valueBN: bigint; 17 | } 18 | 19 | 20 | const object = doParse('{ "valueBN": 12345678901234567890 }'); 21 | 22 | assert.equal(typeof object.valueBN, 'bigint'); 23 | 24 | console.log(`valueBN = ${object.valueBN}`); 25 | -------------------------------------------------------------------------------- /examples/node-esm-polyfill/src/keypath.ts: -------------------------------------------------------------------------------- 1 | 2 | import { strict as assert } from 'node:assert'; 3 | 4 | import '@ton.js/json-parse-polyfill'; 5 | import type { ReviverFunc } from '@ton.js/json-parse-polyfill'; 6 | 7 | 8 | interface DocumentType { 9 | foo: { 10 | bar: { 11 | value: bigint; 12 | }; 13 | }; 14 | } 15 | 16 | const content = '{ "foo": { "bar": { "value": 12345678901234567890 } } }'; 17 | 18 | const reviver: ReviverFunc = (key, value, context) => ( 19 | ((context.keys.join('.') === 'foo.bar.value') 20 | ? BigInt(context.source) 21 | : value 22 | ) 23 | ); 24 | 25 | const object = ( 26 | JSON.parse(content, reviver as any) 27 | ); 28 | 29 | assert.equal(typeof object.foo.bar.value, 'bigint'); 30 | 31 | console.log(`value = ${object.foo.bar.value}`); 32 | -------------------------------------------------------------------------------- /examples/node-esm-polyfill/src/native-reviver.ts: -------------------------------------------------------------------------------- 1 | 2 | import { strict as assert } from 'node:assert'; 3 | 4 | import '@ton.js/json-parse-polyfill'; 5 | 6 | 7 | interface DocumentType { 8 | birthDate: Date; 9 | } 10 | 11 | const content = '{ "birthDate": "1989-08-16T10:20:30.123Z" }'; 12 | 13 | const object = JSON.parse(content, (key, value) => ( 14 | (key.endsWith('Date') ? new Date(value) : value) 15 | )); 16 | 17 | assert(object.birthDate instanceof Date); 18 | 19 | console.log(`timestamp = ${object.birthDate.getTime()}`); 20 | -------------------------------------------------------------------------------- /examples/node-esm-polyfill/src/simple.ts: -------------------------------------------------------------------------------- 1 | 2 | import '@ton.js/json-parse-polyfill'; 3 | 4 | 5 | interface DocumentType { 6 | greeting: string; 7 | name: string; 8 | } 9 | 10 | const content = '{ "greeting": "Hello", "name": "John" }'; 11 | 12 | const object = JSON.parse(content) as DocumentType; 13 | 14 | console.log(`${object.greeting} ${object.name}!`); 15 | -------------------------------------------------------------------------------- /examples/node-esm-polyfill/src/source-text-access.ts: -------------------------------------------------------------------------------- 1 | 2 | import { strict as assert } from 'node:assert'; 3 | 4 | import '@ton.js/json-parse-polyfill'; 5 | import type { ReviverFunc } from '@ton.js/json-parse-polyfill'; 6 | 7 | 8 | interface DocumentType { 9 | valueBN: bigint; 10 | } 11 | 12 | 13 | const content = '{ "valueBN": 12345678901234567890 }'; 14 | 15 | const reviver: ReviverFunc = (key, value, context) => ( 16 | (key.endsWith('BN') ? BigInt(context.source) : value) 17 | ); 18 | 19 | const object = ( 20 | JSON.parse(content, reviver as any) 21 | ); 22 | 23 | assert.equal(typeof object.valueBN, 'bigint'); 24 | 25 | console.log(`valueBN = ${object.valueBN}`); 26 | -------------------------------------------------------------------------------- /examples/node-esm-polyfill/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.common.json", 4 | "compilerOptions": { 5 | "lib": [ 6 | "ESNext" 7 | ], 8 | "target": "ESNext", 9 | "outDir": "dist/" 10 | }, 11 | "include": [ 12 | "src/" 13 | ], 14 | "ts-node": { 15 | "esm": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/node-esm-rules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-node-esm-rules", 3 | "version": "0.0.0", 4 | "description": "Node.js ESM parsing by rules examples", 5 | "private": true, 6 | "license": "MIT", 7 | "type": "module", 8 | "scripts": { 9 | "build": "run-s clean build:compile", 10 | "build:compile": "tsc", 11 | "clean": "rimraf ./dist/", 12 | "__examples__": ":", 13 | "polyfilled": "ts-node src/polyfilled.ts", 14 | "explicit": "ts-node src/explicit.ts" 15 | }, 16 | "engines": { 17 | "node": ">=14" 18 | }, 19 | "dependencies": { 20 | "@ton.js/json-parse-polyfill": "workspace:*", 21 | "@ton.js/json-parser": "workspace:*", 22 | "@ton.js/json-parser-rules": "workspace:*" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^14.18.36", 26 | "npm-run-all": "^4.1.5", 27 | "rimraf": "^3.0.2", 28 | "ts-node": "^10.9.1", 29 | "typescript": "^4.9.4" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/node-esm-rules/src/common.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { Reviver, ReviverRule } from '@ton.js/json-parser-rules'; 3 | import { strict as assert } from 'assert'; 4 | 5 | 6 | //===============// 7 | // JSON DOCUMENT // 8 | //===============// 9 | 10 | export const content = (` 11 | { 12 | "foo": { 13 | "hex": { 14 | "value": "416C6C20796F75206E656564206973206C6F7665" 15 | }, 16 | "bar": { 17 | "baz": { 18 | "valueBig": 11145678901234567890, 19 | "hex": "466f72204169757221" 20 | } 21 | }, 22 | "feeBig": 22245678901234567890, 23 | "myArray": [0, 1, "2023-01-20T19:30:45.904Z", 3, { 24 | "hex": { 25 | "value": "616c6c20796f75722062617365206172652062656c6f6e6720746f207573" 26 | } 27 | }] 28 | } 29 | } 30 | `); 31 | 32 | 33 | //==================// 34 | // RESULT INTERFACE // 35 | //==================// 36 | 37 | export interface ParseResult { 38 | foo: { 39 | hex: HexBag; 40 | bar: { 41 | baz: { 42 | valueBig: bigint; 43 | hex: string; 44 | }; 45 | }; 46 | feeBig: bigint; 47 | myArray: [number, number, Date, number, { 48 | hex: HexBag; 49 | }]; 50 | }; 51 | } 52 | 53 | export interface HexBag { 54 | value: string; 55 | } 56 | 57 | 58 | //==========// 59 | // REVIVERS // 60 | //==========// 61 | 62 | const bigIntReviver: Reviver = ( 63 | context => BigInt(context.source) 64 | ); 65 | 66 | const dateReviver: Reviver = ( 67 | context => new Date(context.value) 68 | ); 69 | 70 | const hexReviver: Reviver = ( 71 | context => Buffer.from(context.value, 'hex').toString() 72 | ); 73 | 74 | 75 | //=======// 76 | // RULES // 77 | //=======// 78 | 79 | export const rules: ReviverRule[] = [{ 80 | pattern: '**.*Big', 81 | reviver: bigIntReviver, 82 | }, { 83 | pattern: [ 84 | '**.hex.value', 85 | 'foo.bar.baz.hex', 86 | ], 87 | reviver: hexReviver, 88 | }, { 89 | pattern: 'foo.myArray.2', 90 | reviver: dateReviver, 91 | }]; 92 | 93 | 94 | //=================// 95 | // TESTING RESULTS // 96 | //=================// 97 | 98 | export function testResults(object: ParseResult) { 99 | 100 | assert.equal(typeof object.foo.bar.baz.valueBig, 'bigint'); 101 | assert.equal(object.foo.bar.baz.valueBig, 11145678901234567890n); 102 | 103 | assert.equal(typeof object.foo.feeBig, 'bigint'); 104 | assert.equal(object.foo.feeBig, 22245678901234567890n); 105 | 106 | assert.equal( 107 | object.foo.hex.value, 108 | 'All you need is love' 109 | ); 110 | 111 | assert.equal( 112 | object.foo.bar.baz.hex, 113 | 'For Aiur!' 114 | ); 115 | 116 | assert.equal( 117 | object.foo.myArray[4].hex.value, 118 | 'all your base are belong to us' 119 | ); 120 | 121 | assert.equal( 122 | object.foo.myArray[2].toISOString(), 123 | '2023-01-20T19:30:45.904Z' 124 | ); 125 | 126 | } 127 | -------------------------------------------------------------------------------- /examples/node-esm-rules/src/explicit.ts: -------------------------------------------------------------------------------- 1 | 2 | import { parseJson } from '@ton.js/json-parser'; 3 | import { parseJsonByRules } from '@ton.js/json-parser-rules'; 4 | 5 | import type { ParseResult } from './common.js'; 6 | import { content, rules, testResults } from './common.js'; 7 | 8 | 9 | const result = parseJsonByRules(content, { 10 | rules, 11 | parser: parseJson, 12 | }); 13 | 14 | testResults(result); 15 | -------------------------------------------------------------------------------- /examples/node-esm-rules/src/polyfilled.ts: -------------------------------------------------------------------------------- 1 | 2 | import '@ton.js/json-parse-polyfill'; 3 | 4 | import { parseJsonByRules } from '@ton.js/json-parser-rules'; 5 | 6 | import type { ParseResult } from './common.js'; 7 | import { content, rules, testResults } from './common.js'; 8 | 9 | 10 | const result = parseJsonByRules(content, { 11 | rules, 12 | }); 13 | 14 | testResults(result); 15 | -------------------------------------------------------------------------------- /examples/node-esm-rules/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.common.json", 4 | "compilerOptions": { 5 | "lib": [ 6 | "ESNext" 7 | ], 8 | "target": "ESNext", 9 | "outDir": "dist/" 10 | }, 11 | "include": [ 12 | "src/" 13 | ], 14 | "ts-node": { 15 | "esm": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/node-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-node-esm", 3 | "version": "0.0.0", 4 | "description": "Node.js ESM examples", 5 | "private": true, 6 | "license": "MIT", 7 | "type": "module", 8 | "scripts": { 9 | "build": "run-s clean build:compile", 10 | "build:compile": "tsc", 11 | "clean": "rimraf ./dist/", 12 | "__examples__": ":", 13 | "keypath": "ts-node src/keypath.ts", 14 | "native-reviver": "ts-node src/native-reviver.ts", 15 | "proto-throw": "ts-node src/proto-throw.ts", 16 | "simple": "ts-node src/simple.ts", 17 | "source-text-access": "ts-node src/source-text-access.ts" 18 | }, 19 | "engines": { 20 | "node": ">=14" 21 | }, 22 | "dependencies": { 23 | "@ton.js/json-parser": "workspace:*" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^14.18.36", 27 | "npm-run-all": "^4.1.5", 28 | "rimraf": "^3.0.2", 29 | "ts-node": "^10.9.1", 30 | "typescript": "^4.9.4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/node-esm/src/keypath.ts: -------------------------------------------------------------------------------- 1 | 2 | import { strict as assert } from 'node:assert'; 3 | 4 | import { parseJson } from '@ton.js/json-parser'; 5 | 6 | 7 | interface DocumentType { 8 | foo: { 9 | bar: { 10 | value: bigint; 11 | }; 12 | }; 13 | } 14 | 15 | const content = '{ "foo": { "bar": { "value": 12345678901234567890 } } }'; 16 | 17 | const object = parseJson(content, (key, value, context) => ( 18 | (context.keys.join('.') === 'foo.bar.value' ? BigInt(context.source) : value) 19 | )); 20 | 21 | assert.equal(typeof object.foo.bar.value, 'bigint'); 22 | 23 | console.log(`value = ${object.foo.bar.value}`); 24 | -------------------------------------------------------------------------------- /examples/node-esm/src/native-reviver.ts: -------------------------------------------------------------------------------- 1 | 2 | import { strict as assert } from 'node:assert'; 3 | 4 | import { parseJson } from '@ton.js/json-parser'; 5 | 6 | 7 | interface DocumentType { 8 | birthDate: Date; 9 | } 10 | 11 | const content = '{ "birthDate": "1989-08-16T10:20:30.123Z" }'; 12 | 13 | const object = parseJson(content, (key, value) => ( 14 | (key.endsWith('Date') ? new Date(value) : value) 15 | )); 16 | 17 | assert(object.birthDate instanceof Date); 18 | 19 | console.log(`timestamp = ${object.birthDate.getTime()}`); 20 | -------------------------------------------------------------------------------- /examples/node-esm/src/proto-throw.ts: -------------------------------------------------------------------------------- 1 | 2 | import { parseJson } from '@ton.js/json-parser'; 3 | 4 | 5 | const content = '{ "foo": true, "__proto__": {} }'; 6 | 7 | try { 8 | parseJson(content, undefined, { 9 | throwOnProto: true, 10 | }); 11 | 12 | } catch (error: any) { 13 | // Forbidden object property name: "__proto__" 14 | console.log(error); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /examples/node-esm/src/simple.ts: -------------------------------------------------------------------------------- 1 | 2 | import { parseJson } from '@ton.js/json-parser'; 3 | 4 | interface DocumentType { 5 | greeting: string; 6 | name: string; 7 | } 8 | 9 | const content = '{ "greeting": "Hello", "name": "John" }'; 10 | 11 | const object = parseJson(content); 12 | 13 | console.log(`${object.greeting} ${object.name}!`); 14 | -------------------------------------------------------------------------------- /examples/node-esm/src/source-text-access.ts: -------------------------------------------------------------------------------- 1 | 2 | import { strict as assert } from 'node:assert'; 3 | 4 | import { parseJson } from '@ton.js/json-parser'; 5 | 6 | 7 | interface DocumentType { 8 | valueBN: bigint; 9 | } 10 | 11 | 12 | const content = '{ "valueBN": 12345678901234567890 }'; 13 | 14 | const object = parseJson(content, (key, value, context) => ( 15 | (key.endsWith('BN') ? BigInt(context.source) : value) 16 | )); 17 | 18 | assert.equal(typeof object.valueBN, 'bigint'); 19 | 20 | console.log(`valueBN = ${object.valueBN}`); 21 | -------------------------------------------------------------------------------- /examples/node-esm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.common.json", 4 | "compilerOptions": { 5 | "lib": [ 6 | "ESNext" 7 | ], 8 | "target": "ESNext", 9 | "outDir": "dist/" 10 | }, 11 | "include": [ 12 | "src/" 13 | ], 14 | "ts-node": { 15 | "esm": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "scripts": { 4 | }, 5 | "private": true, 6 | "license": "MIT", 7 | "devDependencies": { 8 | "@tsconfig/node-lts-strictest-esm": "^18.12.1" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/.gitignore: -------------------------------------------------------------------------------- 1 | !/dist 2 | /README.md 3 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "projectFolder": ".", 4 | "mainEntryPointFilePath": "/temp/types/index.d.ts", 5 | "bundledPackages": [ 6 | "@ton.js/json-parser" 7 | ], 8 | "newlineKind": "lf", 9 | "enumMemberOrder": "preserve", 10 | "compiler": { 11 | "tsconfigFilePath": "/tsconfig.types.json" 12 | }, 13 | "apiReport": { 14 | "enabled": true, 15 | "reportFileName": ".api.md", 16 | "reportFolder": "", 17 | "reportTempFolder": "/temp/" 18 | }, 19 | "docModel": { 20 | "enabled": true, 21 | "apiJsonFilePath": "/temp/.api.json", 22 | "projectFolderUrl": "https://github.com/ton-js/json-parser" 23 | }, 24 | "dtsRollup": { 25 | "enabled": true, 26 | "publicTrimmedFilePath": "/dist/index.d.ts", 27 | "alphaTrimmedFilePath": "/dist/index-alpha.d.ts", 28 | "betaTrimmedFilePath": "/dist/index-beta.d.ts", 29 | "untrimmedFilePath": "" 30 | }, 31 | "tsdocMetadata": {}, 32 | "messages": { 33 | "compilerMessageReporting": { 34 | "default": { 35 | "logLevel": "warning" 36 | } 37 | }, 38 | "extractorMessageReporting": { 39 | "default": { 40 | "logLevel": "warning" 41 | } 42 | }, 43 | "tsdocMessageReporting": { 44 | "default": { 45 | "logLevel": "warning" 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/dist/index-alpha.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * {@link https://www.json.org/json-en.html | JSON specification} 3 | */ 4 | /** 5 | * @public 6 | */ 7 | export declare type Maybe = (Type | undefined); 8 | 9 | /** 10 | * @public 11 | */ 12 | export declare let nativeJsonParse: typeof JSON.parse; 13 | 14 | /** 15 | * @public 16 | */ 17 | export declare interface Options { 18 | throwOnProto?: Maybe; 19 | } 20 | 21 | /** 22 | * @public 23 | * 24 | * Parses JSON document and returns the parsed data. 25 | */ 26 | export declare function parseJson(source: string, reviver?: Maybe, options?: Options): Type; 27 | 28 | /** 29 | * @public 30 | */ 31 | export declare interface ReviverContext { 32 | source: string; 33 | keys: string[]; 34 | } 35 | 36 | /** 37 | * @public 38 | */ 39 | export declare type ReviverFunc = (key: string, value: any, context: ReviverContext) => any; 40 | 41 | export { } 42 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/dist/index-beta.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * {@link https://www.json.org/json-en.html | JSON specification} 3 | */ 4 | /** 5 | * @public 6 | */ 7 | export declare type Maybe = (Type | undefined); 8 | 9 | /** 10 | * @public 11 | */ 12 | export declare let nativeJsonParse: typeof JSON.parse; 13 | 14 | /** 15 | * @public 16 | */ 17 | export declare interface Options { 18 | throwOnProto?: Maybe; 19 | } 20 | 21 | /** 22 | * @public 23 | * 24 | * Parses JSON document and returns the parsed data. 25 | */ 26 | export declare function parseJson(source: string, reviver?: Maybe, options?: Options): Type; 27 | 28 | /** 29 | * @public 30 | */ 31 | export declare interface ReviverContext { 32 | source: string; 33 | keys: string[]; 34 | } 35 | 36 | /** 37 | * @public 38 | */ 39 | export declare type ReviverFunc = (key: string, value: any, context: ReviverContext) => any; 40 | 41 | export { } 42 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/dist/index.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __defProp = Object.defineProperty; 3 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 4 | var __getOwnPropNames = Object.getOwnPropertyNames; 5 | var __hasOwnProp = Object.prototype.hasOwnProperty; 6 | var __export = (target, all) => { 7 | for (var name in all) 8 | __defProp(target, name, { get: all[name], enumerable: true }); 9 | }; 10 | var __copyProps = (to, from, except, desc) => { 11 | if (from && typeof from === "object" || typeof from === "function") { 12 | for (let key of __getOwnPropNames(from)) 13 | if (!__hasOwnProp.call(to, key) && key !== except) 14 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 15 | } 16 | return to; 17 | }; 18 | var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 19 | 20 | // src/index.ts 21 | var src_exports = {}; 22 | __export(src_exports, { 23 | nativeJsonParse: () => nativeJsonParse, 24 | parseJson: () => parseJson 25 | }); 26 | module.exports = __toCommonJS(src_exports); 27 | 28 | // ../json-parser/dist/index.mjs 29 | var codePoints = { 30 | '\\"': '"', 31 | "\\\\": "\\", 32 | "\\/": "/", 33 | "\\b": "\b", 34 | "\\f": "\f", 35 | "\\n": "\n", 36 | "\\r": "\r", 37 | "\\t": " " 38 | }; 39 | function parseJson(source, reviver, options) { 40 | const result = parseValue({ 41 | source, 42 | index: 0, 43 | keys: [], 44 | reviver, 45 | options 46 | }); 47 | const endIndex = skipSpaces(source, result.endIndex); 48 | if (endIndex <= source.length - 1) { 49 | throw new SyntaxError( 50 | `Unexpected extra characters after the parsed data: "${source.substring(endIndex, endIndex + 16)}\u2026" at position: ${endIndex}` 51 | ); 52 | } 53 | return result.value; 54 | } 55 | function parseValue(context) { 56 | const { source, index } = context; 57 | let result; 58 | let i = skipSpaces(source, index); 59 | const newContext = nextContext(context, i); 60 | if (isNumberStart(source[i])) { 61 | result = parseNumber(newContext); 62 | } else { 63 | switch (source[i]) { 64 | case '"': { 65 | result = parseString(newContext); 66 | break; 67 | } 68 | case "[": { 69 | result = parseArray(newContext); 70 | break; 71 | } 72 | case "{": { 73 | result = parseObject(newContext); 74 | break; 75 | } 76 | case "t": { 77 | result = parseKeyword(newContext, "true"); 78 | break; 79 | } 80 | case "f": { 81 | result = parseKeyword(newContext, "false"); 82 | break; 83 | } 84 | case "n": { 85 | result = parseKeyword(newContext, "null"); 86 | break; 87 | } 88 | default: { 89 | throw new SyntaxError( 90 | `Unexpected character: "${source[i]}" at position: ${i}` 91 | ); 92 | } 93 | } 94 | } 95 | result.value = callReviver({ 96 | context, 97 | rawValue: source.substring(i, result.endIndex), 98 | value: result.value 99 | }); 100 | return result; 101 | } 102 | function parseArray(context) { 103 | const { source, index } = context; 104 | const array = []; 105 | let i = index + 1; 106 | let expectElement = false; 107 | let elementIndex = 0; 108 | while (i < source.length) { 109 | i = skipSpaces(source, i); 110 | if (source[i] === "]" && !expectElement) { 111 | i++; 112 | break; 113 | } 114 | const result = parseValue( 115 | nextContext(context, i, elementIndex.toString()) 116 | ); 117 | array.push(result.value); 118 | i = result.endIndex; 119 | i = skipUntil(source, i, [",", "]"]); 120 | if (source[i] === ",") { 121 | expectElement = true; 122 | elementIndex++; 123 | i++; 124 | } else if (source[i] === "]") { 125 | i++; 126 | break; 127 | } 128 | } 129 | return { 130 | value: array, 131 | endIndex: i 132 | }; 133 | } 134 | function parseObject(context) { 135 | const { source, index } = context; 136 | let object = {}; 137 | let i = index + 1; 138 | let expectKeypair = false; 139 | while (i < source.length) { 140 | i = skipUntil(source, i, ['"', "}"]); 141 | if (source[i] === "}" && !expectKeypair) { 142 | i++; 143 | break; 144 | } 145 | let result = parseString( 146 | nextContext(context, i) 147 | ); 148 | const key = result.value; 149 | i = result.endIndex; 150 | i = skipUntil(source, i, ":") + 1; 151 | if (context.options?.throwOnProto) { 152 | if (key === "__proto__") { 153 | throw new SyntaxError( 154 | `Forbidden object property name: "__proto__"` 155 | ); 156 | } else if (isConstructorPrototype(key)) { 157 | throw new SyntaxError( 158 | `Forbidden object property path: "constructor.prototype"` 159 | ); 160 | } 161 | } 162 | i = skipSpaces(source, i); 163 | result = parseValue( 164 | nextContext(context, i, key) 165 | ); 166 | if (result.value !== void 0 && isAllowedKey(key)) { 167 | object[key] = result.value; 168 | } 169 | i = result.endIndex; 170 | i = skipUntil(source, i, [",", "}"]); 171 | if (source[i] === ",") { 172 | expectKeypair = true; 173 | i++; 174 | } else if (source[i] === "}") { 175 | i++; 176 | break; 177 | } 178 | } 179 | return { 180 | value: object, 181 | endIndex: i 182 | }; 183 | function isConstructorPrototype(key) { 184 | if (key !== "prototype") { 185 | return false; 186 | } 187 | const parentKey = context.keys.length > 0 ? context.keys[context.keys.length - 1] : void 0; 188 | return parentKey === "constructor"; 189 | } 190 | function isAllowedKey(key) { 191 | return key !== "__proto__" && !isConstructorPrototype(key); 192 | } 193 | } 194 | function parseString(context) { 195 | const { source, index } = context; 196 | let value = ""; 197 | let i = index + 1; 198 | while (i < source.length) { 199 | const char = source[i]; 200 | if (char === "\\") { 201 | const twoChars = source.substring(i, i + 2); 202 | const codepoint = codePoints[twoChars]; 203 | if (codepoint) { 204 | value += codepoint; 205 | i += 2; 206 | } else if (twoChars === "\\u") { 207 | const charHex = source.substring(i + 2, i + 6); 208 | value += String.fromCharCode(parseInt(charHex, 16)); 209 | i += 6; 210 | } else { 211 | throw new SyntaxError( 212 | `Unknown escape sequence: "${twoChars}"` 213 | ); 214 | } 215 | } else if (char === '"') { 216 | i++; 217 | break; 218 | } else { 219 | value += char; 220 | i++; 221 | } 222 | } 223 | return { value, endIndex: i }; 224 | } 225 | function isNumberStart(char) { 226 | return Boolean(char.match(/^(-|\d)$/)); 227 | } 228 | function parseNumber(context) { 229 | const { source, index } = context; 230 | let isNegative = false; 231 | let integer = "0"; 232 | let fraction = ""; 233 | let isExponentNegative = false; 234 | let exponent = ""; 235 | let i = index; 236 | if (source[i] === "-") { 237 | isNegative = true; 238 | i++; 239 | } 240 | if (source[i] === "0") { 241 | i++; 242 | } else if (source[i].match(/^[1-9]$/)) { 243 | integer = source[i]; 244 | i++; 245 | while (i < source.length) { 246 | if (source[i].match(/^\d$/)) { 247 | integer += source[i]; 248 | i++; 249 | } else { 250 | break; 251 | } 252 | } 253 | } else { 254 | throw new SyntaxError( 255 | `Failed to parse number at position: ${i}` 256 | ); 257 | } 258 | if (source[i] === ".") { 259 | i++; 260 | while (i < source.length) { 261 | if (source[i].match(/^\d$/)) { 262 | fraction += source[i]; 263 | i++; 264 | } else { 265 | break; 266 | } 267 | } 268 | } 269 | if (["e", "E"].includes(source[i])) { 270 | i++; 271 | if (source[i] === "+") { 272 | i++; 273 | } else if (source[i] === "-") { 274 | isExponentNegative = true; 275 | i++; 276 | } 277 | const exponentStartIndex = i; 278 | while (i < source.length) { 279 | if (source[i].match(/^\d$/)) { 280 | exponent += source[i]; 281 | i++; 282 | } else { 283 | break; 284 | } 285 | } 286 | if (exponent.length === 0) { 287 | throw new SyntaxError( 288 | `Failed to parse number's exponent value at position: ${exponentStartIndex}` 289 | ); 290 | } 291 | } 292 | let value = Number( 293 | (isNegative ? "-" : "") + integer + (fraction ? `.${fraction}` : "") + (exponent ? `e${isExponentNegative ? "-" : ""}${exponent}` : "") 294 | ); 295 | return { 296 | value, 297 | endIndex: i 298 | }; 299 | } 300 | function skipUntil(source, startIndex, endChar) { 301 | endChar = Array.isArray(endChar) ? endChar : [endChar]; 302 | const i = skipSpaces(source, startIndex); 303 | const char = source[i]; 304 | if (endChar.includes(char)) { 305 | return i; 306 | } else { 307 | throw new SyntaxError( 308 | `Unexpected character: "${char}" at position: ${i}` 309 | ); 310 | } 311 | } 312 | function skipSpaces(source, startIndex) { 313 | let i; 314 | for (i = startIndex; i < source.length; i++) { 315 | const char = source[i]; 316 | if (!isWhitespace(char)) { 317 | break; 318 | } 319 | } 320 | return i; 321 | } 322 | function isWhitespace(char) { 323 | return [" ", "\n", "\r", " "].includes(char); 324 | } 325 | function parseKeyword(context, keyword) { 326 | const { source, index } = context; 327 | const endIndex = index + keyword.length; 328 | const slice = source.substring(index, endIndex); 329 | if (slice !== keyword) { 330 | throw new SyntaxError( 331 | `Failed to parse value at position: ${index}` 332 | ); 333 | } 334 | let value = keyword === "true" ? true : keyword === "false" ? false : null; 335 | return { 336 | value, 337 | endIndex 338 | }; 339 | } 340 | function callReviver(args) { 341 | const { context, rawValue, value } = args; 342 | const { reviver, keys } = context; 343 | if (!reviver) { 344 | return value; 345 | } 346 | const key = keys.length > 0 ? keys[keys.length - 1] : ""; 347 | return reviver(key, value, { 348 | source: rawValue, 349 | keys 350 | }); 351 | } 352 | function nextContext(context, nextIndex, nextKey) { 353 | const newContext = { 354 | ...context, 355 | index: nextIndex 356 | }; 357 | if (nextKey) { 358 | newContext.keys = [ 359 | ...context.keys, 360 | nextKey 361 | ]; 362 | } 363 | return newContext; 364 | } 365 | 366 | // src/index.ts 367 | var nativeJsonParse; 368 | if (JSON.parse !== jsonParsePolyfill) { 369 | nativeJsonParse = JSON.parse; 370 | JSON.parse = jsonParsePolyfill; 371 | } 372 | function jsonParsePolyfill(source, reviver) { 373 | return (reviver?.length || 0) >= 3 ? parseJson(source, reviver) : nativeJsonParse(source, reviver); 374 | } 375 | // Annotate the CommonJS export names for ESM import in node: 376 | 0 && (module.exports = { 377 | nativeJsonParse, 378 | parseJson 379 | }); 380 | //# sourceMappingURL=index.cjs.map 381 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/dist/index.cjs.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "inputs": { 3 | "../json-parser/dist/index.mjs": { 4 | "bytes": 8005, 5 | "imports": [], 6 | "format": "esm" 7 | }, 8 | "src/index.ts": { 9 | "bytes": 730, 10 | "imports": [ 11 | { 12 | "path": "../json-parser/dist/index.mjs", 13 | "kind": "import-statement", 14 | "original": "@ton.js/json-parser" 15 | }, 16 | { 17 | "path": "../json-parser/dist/index.mjs", 18 | "kind": "import-statement", 19 | "original": "@ton.js/json-parser" 20 | } 21 | ], 22 | "format": "esm" 23 | } 24 | }, 25 | "outputs": { 26 | "dist/index.cjs.map": { 27 | "imports": [], 28 | "exports": [], 29 | "inputs": {}, 30 | "bytes": 17680 31 | }, 32 | "dist/index.cjs": { 33 | "imports": [], 34 | "exports": [], 35 | "entryPoint": "src/index.ts", 36 | "inputs": { 37 | "src/index.ts": { 38 | "bytesInOutput": 444 39 | }, 40 | "../json-parser/dist/index.mjs": { 41 | "bytesInOutput": 7924 42 | } 43 | }, 44 | "bytes": 9392 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /packages/json-parse-polyfill/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * {@link https://www.json.org/json-en.html | JSON specification} 3 | */ 4 | /** 5 | * @public 6 | */ 7 | export declare type Maybe = (Type | undefined); 8 | 9 | /** 10 | * @public 11 | */ 12 | export declare let nativeJsonParse: typeof JSON.parse; 13 | 14 | /** 15 | * @public 16 | */ 17 | export declare interface Options { 18 | throwOnProto?: Maybe; 19 | } 20 | 21 | /** 22 | * @public 23 | * 24 | * Parses JSON document and returns the parsed data. 25 | */ 26 | export declare function parseJson(source: string, reviver?: Maybe, options?: Options): Type; 27 | 28 | /** 29 | * @public 30 | */ 31 | export declare interface ReviverContext { 32 | source: string; 33 | keys: string[]; 34 | } 35 | 36 | /** 37 | * @public 38 | */ 39 | export declare type ReviverFunc = (key: string, value: any, context: ReviverContext) => any; 40 | 41 | export { } 42 | 43 | declare global { 44 | interface JSON { 45 | parse(text: string, reviver?: ReviverFunc): Type; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/dist/index.mjs: -------------------------------------------------------------------------------- 1 | // ../json-parser/dist/index.mjs 2 | var codePoints = { 3 | '\\"': '"', 4 | "\\\\": "\\", 5 | "\\/": "/", 6 | "\\b": "\b", 7 | "\\f": "\f", 8 | "\\n": "\n", 9 | "\\r": "\r", 10 | "\\t": " " 11 | }; 12 | function parseJson(source, reviver, options) { 13 | const result = parseValue({ 14 | source, 15 | index: 0, 16 | keys: [], 17 | reviver, 18 | options 19 | }); 20 | const endIndex = skipSpaces(source, result.endIndex); 21 | if (endIndex <= source.length - 1) { 22 | throw new SyntaxError( 23 | `Unexpected extra characters after the parsed data: "${source.substring(endIndex, endIndex + 16)}\u2026" at position: ${endIndex}` 24 | ); 25 | } 26 | return result.value; 27 | } 28 | function parseValue(context) { 29 | const { source, index } = context; 30 | let result; 31 | let i = skipSpaces(source, index); 32 | const newContext = nextContext(context, i); 33 | if (isNumberStart(source[i])) { 34 | result = parseNumber(newContext); 35 | } else { 36 | switch (source[i]) { 37 | case '"': { 38 | result = parseString(newContext); 39 | break; 40 | } 41 | case "[": { 42 | result = parseArray(newContext); 43 | break; 44 | } 45 | case "{": { 46 | result = parseObject(newContext); 47 | break; 48 | } 49 | case "t": { 50 | result = parseKeyword(newContext, "true"); 51 | break; 52 | } 53 | case "f": { 54 | result = parseKeyword(newContext, "false"); 55 | break; 56 | } 57 | case "n": { 58 | result = parseKeyword(newContext, "null"); 59 | break; 60 | } 61 | default: { 62 | throw new SyntaxError( 63 | `Unexpected character: "${source[i]}" at position: ${i}` 64 | ); 65 | } 66 | } 67 | } 68 | result.value = callReviver({ 69 | context, 70 | rawValue: source.substring(i, result.endIndex), 71 | value: result.value 72 | }); 73 | return result; 74 | } 75 | function parseArray(context) { 76 | const { source, index } = context; 77 | const array = []; 78 | let i = index + 1; 79 | let expectElement = false; 80 | let elementIndex = 0; 81 | while (i < source.length) { 82 | i = skipSpaces(source, i); 83 | if (source[i] === "]" && !expectElement) { 84 | i++; 85 | break; 86 | } 87 | const result = parseValue( 88 | nextContext(context, i, elementIndex.toString()) 89 | ); 90 | array.push(result.value); 91 | i = result.endIndex; 92 | i = skipUntil(source, i, [",", "]"]); 93 | if (source[i] === ",") { 94 | expectElement = true; 95 | elementIndex++; 96 | i++; 97 | } else if (source[i] === "]") { 98 | i++; 99 | break; 100 | } 101 | } 102 | return { 103 | value: array, 104 | endIndex: i 105 | }; 106 | } 107 | function parseObject(context) { 108 | const { source, index } = context; 109 | let object = {}; 110 | let i = index + 1; 111 | let expectKeypair = false; 112 | while (i < source.length) { 113 | i = skipUntil(source, i, ['"', "}"]); 114 | if (source[i] === "}" && !expectKeypair) { 115 | i++; 116 | break; 117 | } 118 | let result = parseString( 119 | nextContext(context, i) 120 | ); 121 | const key = result.value; 122 | i = result.endIndex; 123 | i = skipUntil(source, i, ":") + 1; 124 | if (context.options?.throwOnProto) { 125 | if (key === "__proto__") { 126 | throw new SyntaxError( 127 | `Forbidden object property name: "__proto__"` 128 | ); 129 | } else if (isConstructorPrototype(key)) { 130 | throw new SyntaxError( 131 | `Forbidden object property path: "constructor.prototype"` 132 | ); 133 | } 134 | } 135 | i = skipSpaces(source, i); 136 | result = parseValue( 137 | nextContext(context, i, key) 138 | ); 139 | if (result.value !== void 0 && isAllowedKey(key)) { 140 | object[key] = result.value; 141 | } 142 | i = result.endIndex; 143 | i = skipUntil(source, i, [",", "}"]); 144 | if (source[i] === ",") { 145 | expectKeypair = true; 146 | i++; 147 | } else if (source[i] === "}") { 148 | i++; 149 | break; 150 | } 151 | } 152 | return { 153 | value: object, 154 | endIndex: i 155 | }; 156 | function isConstructorPrototype(key) { 157 | if (key !== "prototype") { 158 | return false; 159 | } 160 | const parentKey = context.keys.length > 0 ? context.keys[context.keys.length - 1] : void 0; 161 | return parentKey === "constructor"; 162 | } 163 | function isAllowedKey(key) { 164 | return key !== "__proto__" && !isConstructorPrototype(key); 165 | } 166 | } 167 | function parseString(context) { 168 | const { source, index } = context; 169 | let value = ""; 170 | let i = index + 1; 171 | while (i < source.length) { 172 | const char = source[i]; 173 | if (char === "\\") { 174 | const twoChars = source.substring(i, i + 2); 175 | const codepoint = codePoints[twoChars]; 176 | if (codepoint) { 177 | value += codepoint; 178 | i += 2; 179 | } else if (twoChars === "\\u") { 180 | const charHex = source.substring(i + 2, i + 6); 181 | value += String.fromCharCode(parseInt(charHex, 16)); 182 | i += 6; 183 | } else { 184 | throw new SyntaxError( 185 | `Unknown escape sequence: "${twoChars}"` 186 | ); 187 | } 188 | } else if (char === '"') { 189 | i++; 190 | break; 191 | } else { 192 | value += char; 193 | i++; 194 | } 195 | } 196 | return { value, endIndex: i }; 197 | } 198 | function isNumberStart(char) { 199 | return Boolean(char.match(/^(-|\d)$/)); 200 | } 201 | function parseNumber(context) { 202 | const { source, index } = context; 203 | let isNegative = false; 204 | let integer = "0"; 205 | let fraction = ""; 206 | let isExponentNegative = false; 207 | let exponent = ""; 208 | let i = index; 209 | if (source[i] === "-") { 210 | isNegative = true; 211 | i++; 212 | } 213 | if (source[i] === "0") { 214 | i++; 215 | } else if (source[i].match(/^[1-9]$/)) { 216 | integer = source[i]; 217 | i++; 218 | while (i < source.length) { 219 | if (source[i].match(/^\d$/)) { 220 | integer += source[i]; 221 | i++; 222 | } else { 223 | break; 224 | } 225 | } 226 | } else { 227 | throw new SyntaxError( 228 | `Failed to parse number at position: ${i}` 229 | ); 230 | } 231 | if (source[i] === ".") { 232 | i++; 233 | while (i < source.length) { 234 | if (source[i].match(/^\d$/)) { 235 | fraction += source[i]; 236 | i++; 237 | } else { 238 | break; 239 | } 240 | } 241 | } 242 | if (["e", "E"].includes(source[i])) { 243 | i++; 244 | if (source[i] === "+") { 245 | i++; 246 | } else if (source[i] === "-") { 247 | isExponentNegative = true; 248 | i++; 249 | } 250 | const exponentStartIndex = i; 251 | while (i < source.length) { 252 | if (source[i].match(/^\d$/)) { 253 | exponent += source[i]; 254 | i++; 255 | } else { 256 | break; 257 | } 258 | } 259 | if (exponent.length === 0) { 260 | throw new SyntaxError( 261 | `Failed to parse number's exponent value at position: ${exponentStartIndex}` 262 | ); 263 | } 264 | } 265 | let value = Number( 266 | (isNegative ? "-" : "") + integer + (fraction ? `.${fraction}` : "") + (exponent ? `e${isExponentNegative ? "-" : ""}${exponent}` : "") 267 | ); 268 | return { 269 | value, 270 | endIndex: i 271 | }; 272 | } 273 | function skipUntil(source, startIndex, endChar) { 274 | endChar = Array.isArray(endChar) ? endChar : [endChar]; 275 | const i = skipSpaces(source, startIndex); 276 | const char = source[i]; 277 | if (endChar.includes(char)) { 278 | return i; 279 | } else { 280 | throw new SyntaxError( 281 | `Unexpected character: "${char}" at position: ${i}` 282 | ); 283 | } 284 | } 285 | function skipSpaces(source, startIndex) { 286 | let i; 287 | for (i = startIndex; i < source.length; i++) { 288 | const char = source[i]; 289 | if (!isWhitespace(char)) { 290 | break; 291 | } 292 | } 293 | return i; 294 | } 295 | function isWhitespace(char) { 296 | return [" ", "\n", "\r", " "].includes(char); 297 | } 298 | function parseKeyword(context, keyword) { 299 | const { source, index } = context; 300 | const endIndex = index + keyword.length; 301 | const slice = source.substring(index, endIndex); 302 | if (slice !== keyword) { 303 | throw new SyntaxError( 304 | `Failed to parse value at position: ${index}` 305 | ); 306 | } 307 | let value = keyword === "true" ? true : keyword === "false" ? false : null; 308 | return { 309 | value, 310 | endIndex 311 | }; 312 | } 313 | function callReviver(args) { 314 | const { context, rawValue, value } = args; 315 | const { reviver, keys } = context; 316 | if (!reviver) { 317 | return value; 318 | } 319 | const key = keys.length > 0 ? keys[keys.length - 1] : ""; 320 | return reviver(key, value, { 321 | source: rawValue, 322 | keys 323 | }); 324 | } 325 | function nextContext(context, nextIndex, nextKey) { 326 | const newContext = { 327 | ...context, 328 | index: nextIndex 329 | }; 330 | if (nextKey) { 331 | newContext.keys = [ 332 | ...context.keys, 333 | nextKey 334 | ]; 335 | } 336 | return newContext; 337 | } 338 | 339 | // src/index.ts 340 | var nativeJsonParse; 341 | if (JSON.parse !== jsonParsePolyfill) { 342 | nativeJsonParse = JSON.parse; 343 | JSON.parse = jsonParsePolyfill; 344 | } 345 | function jsonParsePolyfill(source, reviver) { 346 | return (reviver?.length || 0) >= 3 ? parseJson(source, reviver) : nativeJsonParse(source, reviver); 347 | } 348 | export { 349 | nativeJsonParse, 350 | parseJson 351 | }; 352 | //# sourceMappingURL=index.mjs.map 353 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/dist/index.mjs.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "inputs": { 3 | "../json-parser/dist/index.mjs": { 4 | "bytes": 8005, 5 | "imports": [], 6 | "format": "esm" 7 | }, 8 | "src/index.ts": { 9 | "bytes": 730, 10 | "imports": [ 11 | { 12 | "path": "../json-parser/dist/index.mjs", 13 | "kind": "import-statement", 14 | "original": "@ton.js/json-parser" 15 | }, 16 | { 17 | "path": "../json-parser/dist/index.mjs", 18 | "kind": "import-statement", 19 | "original": "@ton.js/json-parser" 20 | } 21 | ], 22 | "format": "esm" 23 | } 24 | }, 25 | "outputs": { 26 | "dist/index.mjs.map": { 27 | "imports": [], 28 | "exports": [], 29 | "inputs": {}, 30 | "bytes": 17629 31 | }, 32 | "dist/index.mjs": { 33 | "imports": [], 34 | "exports": [ 35 | "nativeJsonParse", 36 | "parseJson" 37 | ], 38 | "entryPoint": "src/index.ts", 39 | "inputs": { 40 | "../json-parser/dist/index.mjs": { 41 | "bytesInOutput": 7924 42 | }, 43 | "src/index.ts": { 44 | "bytesInOutput": 279 45 | } 46 | }, 47 | "bytes": 8331 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /packages/json-parse-polyfill/dist/tsdoc-metadata.json: -------------------------------------------------------------------------------- 1 | // This file is read by tools that parse documentation comments conforming to the TSDoc standard. 2 | // It should be published with your NPM package. It should not be tracked by Git. 3 | { 4 | "tsdocVersion": "0.12", 5 | "toolPackages": [ 6 | { 7 | "packageName": "@microsoft/api-extractor", 8 | "packageVersion": "7.33.8" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/esbuild.ts: -------------------------------------------------------------------------------- 1 | 2 | import { writeFile } from 'node:fs/promises'; 3 | 4 | import { build, BuildOptions } from 'esbuild'; 5 | 6 | import manifest from './package.json' assert { type: 'json' }; 7 | 8 | 9 | interface BuildDefinition { 10 | name: string; 11 | options: BuildOptions; 12 | } 13 | 14 | 15 | const builds: BuildDefinition[] = [ 16 | { 17 | name: 'cjs', 18 | options: { 19 | platform: 'node', 20 | format: 'cjs', 21 | outfile: 'dist/index.cjs', 22 | }, 23 | }, 24 | { 25 | name: 'esm', 26 | options: { 27 | platform: 'node', 28 | format: 'esm', 29 | outfile: 'dist/index.mjs', 30 | }, 31 | }, 32 | ]; 33 | 34 | 35 | for (const definition of builds) { 36 | 37 | console.log( 38 | `\n> BUILDING: ${definition.name}\n` 39 | ); 40 | 41 | const result = await build({ 42 | entryPoints: [ 43 | 'src/index.ts', 44 | ], 45 | bundle: true, 46 | tsconfig: 'tsconfig.lib.json', 47 | metafile: true, 48 | sourcemap: 'linked', 49 | define: { 50 | PACKAGE_NAME: `"${manifest.name}"`, 51 | PACKAGE_VERSION: `"${manifest.version}"`, 52 | }, 53 | plugins: [], 54 | ...definition.options, 55 | 56 | }).catch(error => { 57 | console.error(error); 58 | process.exit(1); 59 | 60 | }); 61 | 62 | const { 63 | warnings, 64 | errors, 65 | metafile, 66 | 67 | } = result; 68 | 69 | for (const message of errors) { 70 | console.error(message.location, message.text); 71 | } 72 | 73 | for (const message of warnings) { 74 | console.warn(message.location, message.text); 75 | } 76 | 77 | await writeFile( 78 | `${definition.options.outfile}.meta.json`, 79 | JSON.stringify(metafile, null, 4) 80 | ); 81 | 82 | } 83 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/json-parse-polyfill.api.md: -------------------------------------------------------------------------------- 1 | ## API Report File for "@ton.js/json-parse-polyfill" 2 | 3 | > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). 4 | 5 | ```ts 6 | 7 | // @public (undocumented) 8 | export type Maybe = (Type | undefined); 9 | 10 | // @public (undocumented) 11 | export let nativeJsonParse: typeof JSON.parse; 12 | 13 | // @public (undocumented) 14 | export interface Options { 15 | // (undocumented) 16 | throwOnProto?: Maybe; 17 | } 18 | 19 | // @public 20 | export function parseJson(source: string, reviver?: Maybe, options?: Options): Type; 21 | 22 | // @public (undocumented) 23 | export interface ReviverContext { 24 | // (undocumented) 25 | keys: string[]; 26 | // (undocumented) 27 | source: string; 28 | } 29 | 30 | // @public (undocumented) 31 | export type ReviverFunc = (key: string, value: any, context: ReviverContext) => any; 32 | 33 | // (No @packageDocumentation comment for this package) 34 | 35 | ``` 36 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ton.js/json-parse-polyfill", 3 | "version": "0.0.0-beta.1", 4 | "description": "A polyfill for JSON.parse", 5 | "license": "MIT", 6 | "type": "module", 7 | "main": "./dist/index.mjs", 8 | "types": "./dist/index.d.ts", 9 | "exports": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.cjs" 12 | }, 13 | "sideEffects": true, 14 | "scripts": { 15 | "build": "run-s build:clean && run-p build:esbuild build:types", 16 | "build:clean": "rimraf ./dist/**", 17 | "build:esbuild": "ts-node ./esbuild.ts", 18 | "build:types": "run-s build:types:compile build:types:extract build:types:augment", 19 | "build:types:compile": "tsc -p ./tsconfig.types.json", 20 | "build:types:extract": "api-extractor run --local --verbose", 21 | "build:types:augment": "cat ./src/global.d.ts >> ./dist/index.d.ts" 22 | }, 23 | "keywords": [ 24 | "json", 25 | "json-parse", 26 | "json-parse-polyfill", 27 | "json-parser", 28 | "polyfill", 29 | "ton.js" 30 | ], 31 | "homepage": "https://github.com/ton-js/json-parser#readme", 32 | "bugs": { 33 | "url": "https://github.com/ton-js/json-parser/issues" 34 | }, 35 | "author": { 36 | "name": "Slava Fomin II", 37 | "email": "slava@fomin.io", 38 | "url": "https://github.com/slavafomin" 39 | }, 40 | "engines": { 41 | "node": ">=14" 42 | }, 43 | "files": [ 44 | "dist/", 45 | "README.md" 46 | ], 47 | "publishConfig": { 48 | "access": "public", 49 | "tag": "beta" 50 | }, 51 | "devDependencies": { 52 | "@microsoft/api-extractor": "^7.33.8", 53 | "@ton.js/json-parser": "workspace:*", 54 | "@types/node": "^14.18.36", 55 | "esbuild": "^0.16.17", 56 | "npm-run-all": "^4.1.5", 57 | "rimraf": "^3.0.2", 58 | "ts-node": "^10.9.1", 59 | "typescript": "^4.9.4" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/src/global.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare global { 3 | interface JSON { 4 | parse(text: string, reviver?: ReviverFunc): Type; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { ReviverFunc } from '@ton.js/json-parser'; 3 | 4 | import { parseJson } from '@ton.js/json-parser'; 5 | 6 | 7 | export * from '@ton.js/json-parser'; 8 | 9 | 10 | /** 11 | * @public 12 | */ 13 | let nativeJsonParse: typeof JSON.parse; 14 | 15 | // Installing the polyfill only once 16 | if (JSON.parse !== jsonParsePolyfill) { 17 | nativeJsonParse = JSON.parse; 18 | JSON.parse = jsonParsePolyfill; 19 | } 20 | 21 | export { nativeJsonParse }; 22 | 23 | 24 | function jsonParsePolyfill( 25 | source: string, 26 | reviver?: ReviverFunc 27 | ) { 28 | 29 | // Polyfill is only used when the third argument is 30 | // defined on the reviver function, otherwise using 31 | // native (faster) implementation. 32 | 33 | return ((reviver?.length || 0) >= 3 34 | ? parseJson(source, reviver) 35 | : nativeJsonParse(source, reviver as any) 36 | ); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.common.json", 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "resolveJsonModule": true, 7 | }, 8 | "files": [ 9 | "esbuild.ts" 10 | ], 11 | "ts-node": { 12 | "esm": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.common.json", 4 | "files": [ 5 | "src/index.ts" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/json-parse-polyfill/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.common.json", 4 | "compilerOptions": { 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "declarationMap": true, 8 | "declarationDir": "temp/types", 9 | "isolatedModules": true 10 | }, 11 | "include": [ 12 | "src/" 13 | ], 14 | "exclude": [ 15 | "**/*.test.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/json-parser-rules/.gitignore: -------------------------------------------------------------------------------- 1 | !/dist 2 | /README.md 3 | -------------------------------------------------------------------------------- /packages/json-parser-rules/.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "loader": "ts-node/esm", 3 | "extensions": ["ts"], 4 | "spec": [ 5 | "src/**/*.test.ts", 6 | "test/**/*.test.ts" 7 | ], 8 | "watch-files": [ 9 | "src" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/json-parser-rules/api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "projectFolder": ".", 4 | "mainEntryPointFilePath": "/temp/types/index.d.ts", 5 | "bundledPackages": [], 6 | "newlineKind": "lf", 7 | "enumMemberOrder": "preserve", 8 | "compiler": { 9 | "tsconfigFilePath": "/tsconfig.types.json" 10 | }, 11 | "apiReport": { 12 | "enabled": true, 13 | "reportFileName": ".api.md", 14 | "reportFolder": "", 15 | "reportTempFolder": "/temp/" 16 | }, 17 | "docModel": { 18 | "enabled": true, 19 | "apiJsonFilePath": "/temp/.api.json", 20 | "projectFolderUrl": "https://github.com/ton-js/json-parser" 21 | }, 22 | "dtsRollup": { 23 | "enabled": true, 24 | "publicTrimmedFilePath": "/dist/index.d.ts", 25 | "alphaTrimmedFilePath": "/dist/index-alpha.d.ts", 26 | "betaTrimmedFilePath": "/dist/index-beta.d.ts", 27 | "untrimmedFilePath": "" 28 | }, 29 | "tsdocMetadata": {}, 30 | "messages": { 31 | "compilerMessageReporting": { 32 | "default": { 33 | "logLevel": "warning" 34 | } 35 | }, 36 | "extractorMessageReporting": { 37 | "default": { 38 | "logLevel": "warning" 39 | } 40 | }, 41 | "tsdocMessageReporting": { 42 | "default": { 43 | "logLevel": "warning" 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/json-parser-rules/bin/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -o pipefail 5 | 6 | SCRIPT_PATH="$(dirname "$0")" 7 | ROOT_PATH="${SCRIPT_PATH}/../../.." 8 | 9 | PKG_PATH="${SCRIPT_PATH}/.." 10 | MAN_PATH="${PKG_PATH}/package.json" 11 | 12 | # Reading some data from the main package manifest 13 | NAME=$(jq -r '.name' "${MAN_PATH}") 14 | TAG=$(jq -r '.publishConfig.tag' "${MAN_PATH}") 15 | VERSION=$(jq -r '.version' "${MAN_PATH}") 16 | 17 | 18 | echo "Starting to release the package…" 19 | 20 | echo "Building the project first…" 21 | pnpm run build 22 | 23 | # Asking user for tag and version 24 | read -e -p "Tag: " -i "${TAG}" TAG 25 | read -e -p "Version: " -i "${VERSION}" VERSION 26 | 27 | echo "Updating package manifest…" 28 | 29 | jq ".version = \"${VERSION}\"" "${MAN_PATH}" > "${PKG_PATH}/~package.json" 30 | mv "${PKG_PATH}/~package.json" "${MAN_PATH}" 31 | 32 | echo "Copying the README file…" 33 | cp "${ROOT_PATH}/README.md" "${PKG_PATH}/" 34 | 35 | # Making sure Git is "clean" 36 | if [ -n "$(git status --porcelain)" ]; then 37 | echo "Git repository content was updated, please commit all the changes and start again" 38 | exit 1 || return 1 39 | fi 40 | 41 | echo "Publishing the package: ${NAME}…" 42 | (cd "${PKG_PATH}" && npm publish --tag "${TAG}") 43 | 44 | printf "\nRelease complete…\n\n" 45 | 46 | printf "Install the packages with:\n" 47 | echo "[${NAME}]" 48 | echo "— ${NAME}@${VERSION}" 49 | echo "— ${NAME}@${TAG}" 50 | -------------------------------------------------------------------------------- /packages/json-parser-rules/dist/index-alpha.d.ts: -------------------------------------------------------------------------------- 1 | export declare type InternalReviver = (key: string, value: any, context: InternalReviverContext) => any; 2 | 3 | export declare interface InternalReviverContext { 4 | source: string; 5 | keys: string[]; 6 | } 7 | 8 | export declare type Maybe = (Type | undefined); 9 | 10 | export declare interface Options { 11 | rules: ReviverRule[]; 12 | parser?: Maybe; 13 | } 14 | 15 | export declare function parseJsonByRules(source: string, options: Options): Type; 16 | 17 | export declare type ParserFunc = (text: string, reviver?: Maybe) => Type; 18 | 19 | export declare type Reviver = ((context: ReviverContext) => any); 20 | 21 | export declare interface ReviverContext { 22 | value: any; 23 | source: string; 24 | path: string; 25 | } 26 | 27 | export declare interface ReviverRule { 28 | pattern: (string | string[]); 29 | reviver: Reviver; 30 | } 31 | 32 | export { } 33 | -------------------------------------------------------------------------------- /packages/json-parser-rules/dist/index-beta.d.ts: -------------------------------------------------------------------------------- 1 | export declare type InternalReviver = (key: string, value: any, context: InternalReviverContext) => any; 2 | 3 | export declare interface InternalReviverContext { 4 | source: string; 5 | keys: string[]; 6 | } 7 | 8 | export declare type Maybe = (Type | undefined); 9 | 10 | export declare interface Options { 11 | rules: ReviverRule[]; 12 | parser?: Maybe; 13 | } 14 | 15 | export declare function parseJsonByRules(source: string, options: Options): Type; 16 | 17 | export declare type ParserFunc = (text: string, reviver?: Maybe) => Type; 18 | 19 | export declare type Reviver = ((context: ReviverContext) => any); 20 | 21 | export declare interface ReviverContext { 22 | value: any; 23 | source: string; 24 | path: string; 25 | } 26 | 27 | export declare interface ReviverRule { 28 | pattern: (string | string[]); 29 | reviver: Reviver; 30 | } 31 | 32 | export { } 33 | -------------------------------------------------------------------------------- /packages/json-parser-rules/dist/index.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __defProp = Object.defineProperty; 3 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 4 | var __getOwnPropNames = Object.getOwnPropertyNames; 5 | var __hasOwnProp = Object.prototype.hasOwnProperty; 6 | var __export = (target, all) => { 7 | for (var name in all) 8 | __defProp(target, name, { get: all[name], enumerable: true }); 9 | }; 10 | var __copyProps = (to, from, except, desc) => { 11 | if (from && typeof from === "object" || typeof from === "function") { 12 | for (let key of __getOwnPropNames(from)) 13 | if (!__hasOwnProp.call(to, key) && key !== except) 14 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 15 | } 16 | return to; 17 | }; 18 | var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 19 | 20 | // src/index.ts 21 | var src_exports = {}; 22 | __export(src_exports, { 23 | parseJsonByRules: () => parseJsonByRules 24 | }); 25 | module.exports = __toCommonJS(src_exports); 26 | 27 | // src/json-parser-rules.ts 28 | function parseJsonByRules(source, options) { 29 | const compiledRules = compileRules(options.rules); 30 | const parser = options.parser ?? JSON.parse; 31 | return parser(source, (key, value, context) => { 32 | const { keys, source: source2 } = context; 33 | const path = keys.join("."); 34 | const reviver = findReviver(path); 35 | if (!reviver) { 36 | return value; 37 | } 38 | return reviver({ 39 | value, 40 | source: source2, 41 | path 42 | }); 43 | }); 44 | function findReviver(path) { 45 | for (const [matcher, reviver] of compiledRules) { 46 | if (typeof matcher === "string") { 47 | if (matcher === path) { 48 | return reviver; 49 | } 50 | } else { 51 | if (matcher.test(path)) { 52 | return reviver; 53 | } 54 | } 55 | } 56 | return void 0; 57 | } 58 | function compileRules(rules) { 59 | const compiledRules2 = []; 60 | for (const rule of rules) { 61 | const patterns = Array.isArray(rule.pattern) ? rule.pattern : [rule.pattern]; 62 | for (const pattern of patterns) { 63 | const matcher = compilePattern(pattern); 64 | compiledRules2.push([matcher, rule.reviver]); 65 | } 66 | } 67 | return compiledRules2; 68 | } 69 | function compilePattern(pattern) { 70 | let parts = []; 71 | let isRegExp = false; 72 | for (const token of pattern.split(".")) { 73 | if (token === "**") { 74 | parts.push(".*"); 75 | isRegExp = true; 76 | } else if (token.includes("*")) { 77 | parts.push(token.replace(/\*/g, "[^\\.]*?")); 78 | isRegExp = true; 79 | } else { 80 | parts.push(token); 81 | } 82 | } 83 | if (isRegExp) { 84 | const rePattern = "^" + parts.join("\\.") + "$"; 85 | return new RegExp(rePattern); 86 | } else { 87 | return pattern; 88 | } 89 | } 90 | } 91 | // Annotate the CommonJS export names for ESM import in node: 92 | 0 && (module.exports = { 93 | parseJsonByRules 94 | }); 95 | //# sourceMappingURL=index.cjs.map 96 | -------------------------------------------------------------------------------- /packages/json-parser-rules/dist/index.cjs.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../src/index.ts", "../src/json-parser-rules.ts"], 4 | "sourcesContent": ["\nexport { parseJsonByRules } from './json-parser-rules.js';\n\nexport type {\n Maybe,\n Options,\n ReviverRule,\n Reviver,\n ReviverContext,\n ParserFunc,\n InternalReviver,\n InternalReviverContext,\n\n} from './json-parser-rules.js';\n", "\nexport type Maybe = (Type | undefined);\n\nexport interface Options {\n rules: ReviverRule[];\n parser?: Maybe;\n}\n\nexport interface ReviverRule {\n pattern: (string | string[]);\n reviver: Reviver;\n}\n\nexport type Reviver = (\n (context: ReviverContext) => any\n);\n\nexport interface ReviverContext {\n value: any;\n source: string;\n path: string;\n}\n\nexport type ParserFunc = (\n text: string,\n reviver?: Maybe\n) => Type;\n\nexport type InternalReviver = (\n key: string,\n value: any,\n context: InternalReviverContext\n) => any;\n\nexport interface InternalReviverContext {\n source: string;\n keys: string[];\n}\n\ntype CompiledRule = [Matcher, Reviver];\ntype Matcher = (RegExp | string);\n\n\nexport function parseJsonByRules(\n source: string,\n options: Options\n\n): Type {\n\n const compiledRules = compileRules(options.rules);\n\n const parser: ParserFunc = (\n options.parser ??\n JSON.parse\n );\n\n return parser(source, (key, value, context) => {\n\n const { keys, source } = context;\n\n const path = keys.join('.');\n\n const reviver = findReviver(path);\n\n if (!reviver) {\n return value;\n }\n\n return reviver({\n value,\n source,\n path,\n });\n\n });\n\n\n function findReviver(path: string): Maybe {\n\n for (const [matcher, reviver] of compiledRules) {\n\n if (typeof matcher === 'string') {\n if (matcher === path) {\n return reviver;\n }\n } else {\n if (matcher.test(path)) {\n return reviver;\n }\n }\n\n }\n\n return undefined;\n\n }\n\n function compileRules(\n rules: ReviverRule[]\n\n ): CompiledRule[] {\n\n const compiledRules: CompiledRule[] = [];\n\n for (const rule of rules) {\n\n const patterns = (Array.isArray(rule.pattern)\n ? rule.pattern\n : [rule.pattern]\n );\n\n for (const pattern of patterns) {\n const matcher = compilePattern(pattern);\n compiledRules.push([matcher, rule.reviver]);\n }\n\n }\n\n return compiledRules;\n\n }\n\n function compilePattern(pattern: string): (RegExp | string) {\n\n let parts = [];\n let isRegExp = false;\n\n for (const token of pattern.split('.')) {\n if (token === '**') {\n parts.push('.*');\n isRegExp = true;\n } else if (token.includes('*')) {\n parts.push(token.replace(/\\*/g, '[^\\\\.]*?'));\n isRegExp = true;\n } else {\n parts.push(token);\n }\n }\n\n if (isRegExp) {\n\n const rePattern = (\n '^' + parts.join('\\\\.') + '$'\n );\n\n return new RegExp(rePattern);\n\n } else {\n\n return pattern;\n\n }\n\n }\n\n}\n"], 5 | "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC2CO,SAAS,iBACd,QACA,SAEM;AAEN,QAAM,gBAAgB,aAAa,QAAQ,KAAK;AAEhD,QAAM,SACJ,QAAQ,UACK,KAAK;AAGpB,SAAO,OAAO,QAAQ,CAAC,KAAK,OAAO,YAAY;AAE7C,UAAM,EAAE,MAAM,QAAAA,QAAO,IAAI;AAEzB,UAAM,OAAO,KAAK,KAAK,GAAG;AAE1B,UAAM,UAAU,YAAY,IAAI;AAEhC,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AAEA,WAAO,QAAQ;AAAA,MACb;AAAA,MACA,QAAAA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EAEH,CAAC;AAGD,WAAS,YAAY,MAA8B;AAEjD,eAAW,CAAC,SAAS,OAAO,KAAK,eAAe;AAE9C,UAAI,OAAO,YAAY,UAAU;AAC/B,YAAI,YAAY,MAAM;AACpB,iBAAO;AAAA,QACT;AAAA,MACF,OAAO;AACL,YAAI,QAAQ,KAAK,IAAI,GAAG;AACtB,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IAEF;AAEA,WAAO;AAAA,EAET;AAEA,WAAS,aACP,OAEgB;AAEhB,UAAMC,iBAAgC,CAAC;AAEvC,eAAW,QAAQ,OAAO;AAExB,YAAM,WAAY,MAAM,QAAQ,KAAK,OAAO,IACxC,KAAK,UACL,CAAC,KAAK,OAAO;AAGjB,iBAAW,WAAW,UAAU;AAC9B,cAAM,UAAU,eAAe,OAAO;AACtC,QAAAA,eAAc,KAAK,CAAC,SAAS,KAAK,OAAO,CAAC;AAAA,MAC5C;AAAA,IAEF;AAEA,WAAOA;AAAA,EAET;AAEA,WAAS,eAAe,SAAoC;AAE1D,QAAI,QAAQ,CAAC;AACb,QAAI,WAAW;AAEf,eAAW,SAAS,QAAQ,MAAM,GAAG,GAAG;AACtC,UAAI,UAAU,MAAM;AAClB,cAAM,KAAK,IAAI;AACf,mBAAW;AAAA,MACb,WAAW,MAAM,SAAS,GAAG,GAAG;AAC9B,cAAM,KAAK,MAAM,QAAQ,OAAO,UAAU,CAAC;AAC3C,mBAAW;AAAA,MACb,OAAO;AACL,cAAM,KAAK,KAAK;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,UAAU;AAEZ,YAAM,YACJ,MAAM,MAAM,KAAK,KAAK,IAAI;AAG5B,aAAO,IAAI,OAAO,SAAS;AAAA,IAE7B,OAAO;AAEL,aAAO;AAAA,IAET;AAAA,EAEF;AAEF;", 6 | "names": ["source", "compiledRules"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/json-parser-rules/dist/index.cjs.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "inputs": { 3 | "src/json-parser-rules.ts": { 4 | "bytes": 2655, 5 | "imports": [], 6 | "format": "esm" 7 | }, 8 | "src/index.ts": { 9 | "bytes": 232, 10 | "imports": [ 11 | { 12 | "path": "src/json-parser-rules.ts", 13 | "kind": "import-statement", 14 | "original": "./json-parser-rules.js" 15 | } 16 | ], 17 | "format": "esm" 18 | } 19 | }, 20 | "outputs": { 21 | "dist/index.cjs.map": { 22 | "imports": [], 23 | "exports": [], 24 | "inputs": {}, 25 | "bytes": 4552 26 | }, 27 | "dist/index.cjs": { 28 | "imports": [], 29 | "exports": [], 30 | "entryPoint": "src/index.ts", 31 | "inputs": { 32 | "src/index.ts": { 33 | "bytesInOutput": 137 34 | }, 35 | "src/json-parser-rules.ts": { 36 | "bytesInOutput": 1668 37 | } 38 | }, 39 | "bytes": 2795 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /packages/json-parser-rules/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare type InternalReviver = (key: string, value: any, context: InternalReviverContext) => any; 2 | 3 | export declare interface InternalReviverContext { 4 | source: string; 5 | keys: string[]; 6 | } 7 | 8 | export declare type Maybe = (Type | undefined); 9 | 10 | export declare interface Options { 11 | rules: ReviverRule[]; 12 | parser?: Maybe; 13 | } 14 | 15 | export declare function parseJsonByRules(source: string, options: Options): Type; 16 | 17 | export declare type ParserFunc = (text: string, reviver?: Maybe) => Type; 18 | 19 | export declare type Reviver = ((context: ReviverContext) => any); 20 | 21 | export declare interface ReviverContext { 22 | value: any; 23 | source: string; 24 | path: string; 25 | } 26 | 27 | export declare interface ReviverRule { 28 | pattern: (string | string[]); 29 | reviver: Reviver; 30 | } 31 | 32 | export { } 33 | -------------------------------------------------------------------------------- /packages/json-parser-rules/dist/index.mjs: -------------------------------------------------------------------------------- 1 | // src/json-parser-rules.ts 2 | function parseJsonByRules(source, options) { 3 | const compiledRules = compileRules(options.rules); 4 | const parser = options.parser ?? JSON.parse; 5 | return parser(source, (key, value, context) => { 6 | const { keys, source: source2 } = context; 7 | const path = keys.join("."); 8 | const reviver = findReviver(path); 9 | if (!reviver) { 10 | return value; 11 | } 12 | return reviver({ 13 | value, 14 | source: source2, 15 | path 16 | }); 17 | }); 18 | function findReviver(path) { 19 | for (const [matcher, reviver] of compiledRules) { 20 | if (typeof matcher === "string") { 21 | if (matcher === path) { 22 | return reviver; 23 | } 24 | } else { 25 | if (matcher.test(path)) { 26 | return reviver; 27 | } 28 | } 29 | } 30 | return void 0; 31 | } 32 | function compileRules(rules) { 33 | const compiledRules2 = []; 34 | for (const rule of rules) { 35 | const patterns = Array.isArray(rule.pattern) ? rule.pattern : [rule.pattern]; 36 | for (const pattern of patterns) { 37 | const matcher = compilePattern(pattern); 38 | compiledRules2.push([matcher, rule.reviver]); 39 | } 40 | } 41 | return compiledRules2; 42 | } 43 | function compilePattern(pattern) { 44 | let parts = []; 45 | let isRegExp = false; 46 | for (const token of pattern.split(".")) { 47 | if (token === "**") { 48 | parts.push(".*"); 49 | isRegExp = true; 50 | } else if (token.includes("*")) { 51 | parts.push(token.replace(/\*/g, "[^\\.]*?")); 52 | isRegExp = true; 53 | } else { 54 | parts.push(token); 55 | } 56 | } 57 | if (isRegExp) { 58 | const rePattern = "^" + parts.join("\\.") + "$"; 59 | return new RegExp(rePattern); 60 | } else { 61 | return pattern; 62 | } 63 | } 64 | } 65 | export { 66 | parseJsonByRules 67 | }; 68 | //# sourceMappingURL=index.mjs.map 69 | -------------------------------------------------------------------------------- /packages/json-parser-rules/dist/index.mjs.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../src/json-parser-rules.ts"], 4 | "sourcesContent": ["\nexport type Maybe = (Type | undefined);\n\nexport interface Options {\n rules: ReviverRule[];\n parser?: Maybe;\n}\n\nexport interface ReviverRule {\n pattern: (string | string[]);\n reviver: Reviver;\n}\n\nexport type Reviver = (\n (context: ReviverContext) => any\n);\n\nexport interface ReviverContext {\n value: any;\n source: string;\n path: string;\n}\n\nexport type ParserFunc = (\n text: string,\n reviver?: Maybe\n) => Type;\n\nexport type InternalReviver = (\n key: string,\n value: any,\n context: InternalReviverContext\n) => any;\n\nexport interface InternalReviverContext {\n source: string;\n keys: string[];\n}\n\ntype CompiledRule = [Matcher, Reviver];\ntype Matcher = (RegExp | string);\n\n\nexport function parseJsonByRules(\n source: string,\n options: Options\n\n): Type {\n\n const compiledRules = compileRules(options.rules);\n\n const parser: ParserFunc = (\n options.parser ??\n JSON.parse\n );\n\n return parser(source, (key, value, context) => {\n\n const { keys, source } = context;\n\n const path = keys.join('.');\n\n const reviver = findReviver(path);\n\n if (!reviver) {\n return value;\n }\n\n return reviver({\n value,\n source,\n path,\n });\n\n });\n\n\n function findReviver(path: string): Maybe {\n\n for (const [matcher, reviver] of compiledRules) {\n\n if (typeof matcher === 'string') {\n if (matcher === path) {\n return reviver;\n }\n } else {\n if (matcher.test(path)) {\n return reviver;\n }\n }\n\n }\n\n return undefined;\n\n }\n\n function compileRules(\n rules: ReviverRule[]\n\n ): CompiledRule[] {\n\n const compiledRules: CompiledRule[] = [];\n\n for (const rule of rules) {\n\n const patterns = (Array.isArray(rule.pattern)\n ? rule.pattern\n : [rule.pattern]\n );\n\n for (const pattern of patterns) {\n const matcher = compilePattern(pattern);\n compiledRules.push([matcher, rule.reviver]);\n }\n\n }\n\n return compiledRules;\n\n }\n\n function compilePattern(pattern: string): (RegExp | string) {\n\n let parts = [];\n let isRegExp = false;\n\n for (const token of pattern.split('.')) {\n if (token === '**') {\n parts.push('.*');\n isRegExp = true;\n } else if (token.includes('*')) {\n parts.push(token.replace(/\\*/g, '[^\\\\.]*?'));\n isRegExp = true;\n } else {\n parts.push(token);\n }\n }\n\n if (isRegExp) {\n\n const rePattern = (\n '^' + parts.join('\\\\.') + '$'\n );\n\n return new RegExp(rePattern);\n\n } else {\n\n return pattern;\n\n }\n\n }\n\n}\n"], 5 | "mappings": ";AA2CO,SAAS,iBACd,QACA,SAEM;AAEN,QAAM,gBAAgB,aAAa,QAAQ,KAAK;AAEhD,QAAM,SACJ,QAAQ,UACK,KAAK;AAGpB,SAAO,OAAO,QAAQ,CAAC,KAAK,OAAO,YAAY;AAE7C,UAAM,EAAE,MAAM,QAAAA,QAAO,IAAI;AAEzB,UAAM,OAAO,KAAK,KAAK,GAAG;AAE1B,UAAM,UAAU,YAAY,IAAI;AAEhC,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AAEA,WAAO,QAAQ;AAAA,MACb;AAAA,MACA,QAAAA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EAEH,CAAC;AAGD,WAAS,YAAY,MAA8B;AAEjD,eAAW,CAAC,SAAS,OAAO,KAAK,eAAe;AAE9C,UAAI,OAAO,YAAY,UAAU;AAC/B,YAAI,YAAY,MAAM;AACpB,iBAAO;AAAA,QACT;AAAA,MACF,OAAO;AACL,YAAI,QAAQ,KAAK,IAAI,GAAG;AACtB,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IAEF;AAEA,WAAO;AAAA,EAET;AAEA,WAAS,aACP,OAEgB;AAEhB,UAAMC,iBAAgC,CAAC;AAEvC,eAAW,QAAQ,OAAO;AAExB,YAAM,WAAY,MAAM,QAAQ,KAAK,OAAO,IACxC,KAAK,UACL,CAAC,KAAK,OAAO;AAGjB,iBAAW,WAAW,UAAU;AAC9B,cAAM,UAAU,eAAe,OAAO;AACtC,QAAAA,eAAc,KAAK,CAAC,SAAS,KAAK,OAAO,CAAC;AAAA,MAC5C;AAAA,IAEF;AAEA,WAAOA;AAAA,EAET;AAEA,WAAS,eAAe,SAAoC;AAE1D,QAAI,QAAQ,CAAC;AACb,QAAI,WAAW;AAEf,eAAW,SAAS,QAAQ,MAAM,GAAG,GAAG;AACtC,UAAI,UAAU,MAAM;AAClB,cAAM,KAAK,IAAI;AACf,mBAAW;AAAA,MACb,WAAW,MAAM,SAAS,GAAG,GAAG;AAC9B,cAAM,KAAK,MAAM,QAAQ,OAAO,UAAU,CAAC;AAC3C,mBAAW;AAAA,MACb,OAAO;AACL,cAAM,KAAK,KAAK;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,UAAU;AAEZ,YAAM,YACJ,MAAM,MAAM,KAAK,KAAK,IAAI;AAG5B,aAAO,IAAI,OAAO,SAAS;AAAA,IAE7B,OAAO;AAEL,aAAO;AAAA,IAET;AAAA,EAEF;AAEF;", 6 | "names": ["source", "compiledRules"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/json-parser-rules/dist/index.mjs.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "inputs": { 3 | "src/json-parser-rules.ts": { 4 | "bytes": 2655, 5 | "imports": [], 6 | "format": "esm" 7 | }, 8 | "src/index.ts": { 9 | "bytes": 232, 10 | "imports": [ 11 | { 12 | "path": "src/json-parser-rules.ts", 13 | "kind": "import-statement", 14 | "original": "./json-parser-rules.js" 15 | } 16 | ], 17 | "format": "esm" 18 | } 19 | }, 20 | "outputs": { 21 | "dist/index.mjs.map": { 22 | "imports": [], 23 | "exports": [], 24 | "inputs": {}, 25 | "bytes": 4237 26 | }, 27 | "dist/index.mjs": { 28 | "imports": [], 29 | "exports": [ 30 | "parseJsonByRules" 31 | ], 32 | "entryPoint": "src/index.ts", 33 | "inputs": { 34 | "src/json-parser-rules.ts": { 35 | "bytesInOutput": 1668 36 | }, 37 | "src/index.ts": { 38 | "bytesInOutput": 0 39 | } 40 | }, 41 | "bytes": 1762 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /packages/json-parser-rules/dist/tsdoc-metadata.json: -------------------------------------------------------------------------------- 1 | // This file is read by tools that parse documentation comments conforming to the TSDoc standard. 2 | // It should be published with your NPM package. It should not be tracked by Git. 3 | { 4 | "tsdocVersion": "0.12", 5 | "toolPackages": [ 6 | { 7 | "packageName": "@microsoft/api-extractor", 8 | "packageVersion": "7.33.8" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/json-parser-rules/esbuild.ts: -------------------------------------------------------------------------------- 1 | 2 | import { writeFile } from 'node:fs/promises'; 3 | 4 | import { build, BuildOptions } from 'esbuild'; 5 | 6 | import manifest from './package.json' assert { type: 'json' }; 7 | 8 | 9 | interface BuildDefinition { 10 | name: string; 11 | options: BuildOptions; 12 | } 13 | 14 | 15 | const builds: BuildDefinition[] = [ 16 | { 17 | name: 'cjs', 18 | options: { 19 | platform: 'node', 20 | format: 'cjs', 21 | outfile: 'dist/index.cjs', 22 | }, 23 | }, 24 | { 25 | name: 'esm', 26 | options: { 27 | platform: 'node', 28 | format: 'esm', 29 | outfile: 'dist/index.mjs', 30 | }, 31 | }, 32 | ]; 33 | 34 | 35 | for (const definition of builds) { 36 | 37 | console.log( 38 | `\n> BUILDING: ${definition.name}\n` 39 | ); 40 | 41 | const result = await build({ 42 | entryPoints: [ 43 | 'src/index.ts', 44 | ], 45 | bundle: true, 46 | tsconfig: 'tsconfig.lib.json', 47 | metafile: true, 48 | sourcemap: 'linked', 49 | define: { 50 | PACKAGE_NAME: `"${manifest.name}"`, 51 | PACKAGE_VERSION: `"${manifest.version}"`, 52 | }, 53 | plugins: [], 54 | ...definition.options, 55 | 56 | }).catch(error => { 57 | console.error(error); 58 | process.exit(1); 59 | 60 | }); 61 | 62 | const { 63 | warnings, 64 | errors, 65 | metafile, 66 | 67 | } = result; 68 | 69 | for (const message of errors) { 70 | console.error(message.location, message.text); 71 | } 72 | 73 | for (const message of warnings) { 74 | console.warn(message.location, message.text); 75 | } 76 | 77 | await writeFile( 78 | `${definition.options.outfile}.meta.json`, 79 | JSON.stringify(metafile, null, 4) 80 | ); 81 | 82 | } 83 | -------------------------------------------------------------------------------- /packages/json-parser-rules/json-parser-rules.api.md: -------------------------------------------------------------------------------- 1 | ## API Report File for "@ton.js/json-parser-rules" 2 | 3 | > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). 4 | 5 | ```ts 6 | 7 | // @public (undocumented) 8 | export type InternalReviver = (key: string, value: any, context: InternalReviverContext) => any; 9 | 10 | // @public (undocumented) 11 | export interface InternalReviverContext { 12 | // (undocumented) 13 | keys: string[]; 14 | // (undocumented) 15 | source: string; 16 | } 17 | 18 | // @public (undocumented) 19 | export type Maybe = (Type | undefined); 20 | 21 | // @public (undocumented) 22 | export interface Options { 23 | // (undocumented) 24 | parser?: Maybe; 25 | // (undocumented) 26 | rules: ReviverRule[]; 27 | } 28 | 29 | // @public (undocumented) 30 | export function parseJsonByRules(source: string, options: Options): Type; 31 | 32 | // @public (undocumented) 33 | export type ParserFunc = (text: string, reviver?: Maybe) => Type; 34 | 35 | // @public (undocumented) 36 | export type Reviver = ((context: ReviverContext) => any); 37 | 38 | // @public (undocumented) 39 | export interface ReviverContext { 40 | // (undocumented) 41 | path: string; 42 | // (undocumented) 43 | source: string; 44 | // (undocumented) 45 | value: any; 46 | } 47 | 48 | // @public (undocumented) 49 | export interface ReviverRule { 50 | // (undocumented) 51 | pattern: (string | string[]); 52 | // (undocumented) 53 | reviver: Reviver; 54 | } 55 | 56 | // (No @packageDocumentation comment for this package) 57 | 58 | ``` 59 | -------------------------------------------------------------------------------- /packages/json-parser-rules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ton.js/json-parser-rules", 3 | "version": "0.0.0-beta.0", 4 | "description": "Parse JSON by rules", 5 | "license": "MIT", 6 | "type": "module", 7 | "main": "./dist/index.mjs", 8 | "types": "./dist/index.d.ts", 9 | "exports": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.cjs" 12 | }, 13 | "sideEffects": false, 14 | "scripts": { 15 | "test": "TS_NODE_PROJECT='tsconfig.test.json' mocha", 16 | "build": "run-s build:clean && run-p build:types build:esbuild", 17 | "build:clean": "rimraf ./dist/** ./temp", 18 | "build:esbuild": "ts-node ./esbuild.ts", 19 | "build:types": "run-s build:types:compile build:types:extract", 20 | "build:types:compile": "tsc -p ./tsconfig.types.json", 21 | "build:types:extract": "api-extractor run --local --verbose" 22 | }, 23 | "keywords": [ 24 | "json", 25 | "json-parse", 26 | "json-parser", 27 | "ton.js" 28 | ], 29 | "homepage": "https://github.com/ton-js/json-parser#usage-rules", 30 | "bugs": { 31 | "url": "https://github.com/ton-js/json-parser/issues" 32 | }, 33 | "author": { 34 | "name": "Slava Fomin II", 35 | "email": "slava@fomin.io", 36 | "url": "https://github.com/slavafomin" 37 | }, 38 | "engines": { 39 | "node": ">=14" 40 | }, 41 | "files": [ 42 | "dist/", 43 | "README.md" 44 | ], 45 | "publishConfig": { 46 | "access": "public", 47 | "tag": "beta" 48 | }, 49 | "devDependencies": { 50 | "@microsoft/api-extractor": "^7.33.8", 51 | "@types/chai": "^4.3.4", 52 | "@types/mocha": "^10.0.1", 53 | "@types/node": "^14.18.36", 54 | "chai": "^4.3.7", 55 | "esbuild": "^0.16.17", 56 | "mocha": "^10.2.0", 57 | "npm-run-all": "^4.1.5", 58 | "rimraf": "^3.0.2", 59 | "ts-node": "^10.9.1", 60 | "typescript": "^4.9.4" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/json-parser-rules/src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { parseJsonByRules } from './json-parser-rules.js'; 3 | 4 | export type { 5 | Maybe, 6 | Options, 7 | ReviverRule, 8 | Reviver, 9 | ReviverContext, 10 | ParserFunc, 11 | InternalReviver, 12 | InternalReviverContext, 13 | 14 | } from './json-parser-rules.js'; 15 | -------------------------------------------------------------------------------- /packages/json-parser-rules/src/json-parser-rules.ts: -------------------------------------------------------------------------------- 1 | 2 | export type Maybe = (Type | undefined); 3 | 4 | export interface Options { 5 | rules: ReviverRule[]; 6 | parser?: Maybe; 7 | } 8 | 9 | export interface ReviverRule { 10 | pattern: (string | string[]); 11 | reviver: Reviver; 12 | } 13 | 14 | export type Reviver = ( 15 | (context: ReviverContext) => any 16 | ); 17 | 18 | export interface ReviverContext { 19 | value: any; 20 | source: string; 21 | path: string; 22 | } 23 | 24 | export type ParserFunc = ( 25 | text: string, 26 | reviver?: Maybe 27 | ) => Type; 28 | 29 | export type InternalReviver = ( 30 | key: string, 31 | value: any, 32 | context: InternalReviverContext 33 | ) => any; 34 | 35 | export interface InternalReviverContext { 36 | source: string; 37 | keys: string[]; 38 | } 39 | 40 | type CompiledRule = [Matcher, Reviver]; 41 | type Matcher = (RegExp | string); 42 | 43 | 44 | export function parseJsonByRules( 45 | source: string, 46 | options: Options 47 | 48 | ): Type { 49 | 50 | const compiledRules = compileRules(options.rules); 51 | 52 | const parser: ParserFunc = ( 53 | options.parser ?? 54 | JSON.parse 55 | ); 56 | 57 | return parser(source, (key, value, context) => { 58 | 59 | const { keys, source } = context; 60 | 61 | const path = keys.join('.'); 62 | 63 | const reviver = findReviver(path); 64 | 65 | if (!reviver) { 66 | return value; 67 | } 68 | 69 | return reviver({ 70 | value, 71 | source, 72 | path, 73 | }); 74 | 75 | }); 76 | 77 | 78 | function findReviver(path: string): Maybe { 79 | 80 | for (const [matcher, reviver] of compiledRules) { 81 | 82 | if (typeof matcher === 'string') { 83 | if (matcher === path) { 84 | return reviver; 85 | } 86 | } else { 87 | if (matcher.test(path)) { 88 | return reviver; 89 | } 90 | } 91 | 92 | } 93 | 94 | return undefined; 95 | 96 | } 97 | 98 | function compileRules( 99 | rules: ReviverRule[] 100 | 101 | ): CompiledRule[] { 102 | 103 | const compiledRules: CompiledRule[] = []; 104 | 105 | for (const rule of rules) { 106 | 107 | const patterns = (Array.isArray(rule.pattern) 108 | ? rule.pattern 109 | : [rule.pattern] 110 | ); 111 | 112 | for (const pattern of patterns) { 113 | const matcher = compilePattern(pattern); 114 | compiledRules.push([matcher, rule.reviver]); 115 | } 116 | 117 | } 118 | 119 | return compiledRules; 120 | 121 | } 122 | 123 | function compilePattern(pattern: string): (RegExp | string) { 124 | 125 | let parts = []; 126 | let isRegExp = false; 127 | 128 | for (const token of pattern.split('.')) { 129 | if (token === '**') { 130 | parts.push('.*'); 131 | isRegExp = true; 132 | } else if (token.includes('*')) { 133 | parts.push(token.replace(/\*/g, '[^\\.]*?')); 134 | isRegExp = true; 135 | } else { 136 | parts.push(token); 137 | } 138 | } 139 | 140 | if (isRegExp) { 141 | 142 | const rePattern = ( 143 | '^' + parts.join('\\.') + '$' 144 | ); 145 | 146 | return new RegExp(rePattern); 147 | 148 | } else { 149 | 150 | return pattern; 151 | 152 | } 153 | 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /packages/json-parser-rules/src/json-parser.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { expect } from 'chai'; 3 | import { suite, test } from 'mocha'; 4 | 5 | import { parseJsonByRules } from './index.js'; 6 | 7 | 8 | suite('parseJsonByRules()', () => { 9 | 10 | test('simple pattern', () => { 11 | 12 | const source = ( 13 | '{ "foo": { "bar": { "baz": 12345678901234567890 } } }' 14 | ); 15 | 16 | interface Object { 17 | foo: { 18 | bar: { 19 | baz: bigint; 20 | }; 21 | }; 22 | } 23 | 24 | const object = parseJsonByRules(source, { 25 | rules: [{ 26 | pattern: 'foo.bar.baz', 27 | reviver: context => BigInt(context.source), 28 | }], 29 | }); 30 | 31 | (expect(object.foo.bar.baz) 32 | .to.equal(12345678901234567890n) 33 | ); 34 | 35 | }); 36 | 37 | test('multiple patterns', () => { 38 | 39 | const source = ( 40 | '{ "foo": { "bar": { "baz": 12345678901234567890 }, "qux": { "quux": 12345678901234567890123 } } }' 41 | ); 42 | 43 | interface Object { 44 | foo: { 45 | bar: { 46 | baz: bigint; 47 | }; 48 | qux: { 49 | quux: bigint; 50 | }; 51 | }; 52 | } 53 | 54 | const object = parseJsonByRules(source, { 55 | rules: [{ 56 | pattern: [ 57 | 'foo.bar.baz', 58 | 'foo.qux.quux', 59 | ], 60 | reviver: context => BigInt(context.source), 61 | }], 62 | }); 63 | 64 | (expect(object.foo.bar.baz) 65 | .to.equal(12345678901234567890n) 66 | ); 67 | 68 | (expect(object.foo.qux.quux) 69 | .to.equal(12345678901234567890123n) 70 | ); 71 | 72 | }); 73 | 74 | test('multiple rules', () => { 75 | 76 | const source = ( 77 | '{"date":"2023-01-16T04:09:53.875Z","foo":{"bar":{"baz":12345678901234567890,"qux":"2023-01-16T04:02:36.815Z"}}}' 78 | ); 79 | 80 | interface Object { 81 | date: Date; 82 | foo: { 83 | bar: { 84 | baz: bigint; 85 | qux: Date; 86 | }; 87 | }; 88 | } 89 | 90 | const object = parseJsonByRules(source, { 91 | rules: [{ 92 | pattern: 'foo.bar.baz', 93 | reviver: context => BigInt(context.source), 94 | }, { 95 | pattern: ['date', 'foo.bar.qux'], 96 | reviver: context => new Date(context.value), 97 | }], 98 | }); 99 | 100 | (expect(object.foo.bar.baz) 101 | .to.equal(12345678901234567890n) 102 | ); 103 | 104 | expect(object.date).to.be.instanceOf(Date); 105 | (expect(object.date.toISOString()) 106 | .to.equal('2023-01-16T04:09:53.875Z') 107 | ); 108 | 109 | expect(object.foo.bar.qux).to.be.instanceOf(Date); 110 | (expect(object.foo.bar.qux.toISOString()) 111 | .to.equal('2023-01-16T04:02:36.815Z') 112 | ); 113 | 114 | }); 115 | 116 | test('wildcard keys', () => { 117 | 118 | // { 119 | // "foo": { 120 | // "bar": { 121 | // "value": 12345678901234567890, 122 | // "other": 12345678901234567890, 123 | // "date": "2023-01-16T04:45:17.618Z" 124 | // }, 125 | // "baz": { 126 | // "value": 12345678901234567890123, 127 | // "other": 12345678901234567890123 128 | // } 129 | // }, 130 | // "qux": { 131 | // "quux": { 132 | // "value": 12345678901234567890, 133 | // "other": 12345678901234567890, 134 | // "date": "2023-01-16T04:44:23.939Z" 135 | // } 136 | // } 137 | // } 138 | 139 | const source = ( 140 | '{"foo":{"bar":{"value":12345678901234567890,"other":12345678901234567890,"date":"2023-01-16T04:45:17.618Z"},"baz":{"value":12345678901234567890123,"other":12345678901234567890123}},"qux":{"quux":{"value":12345678901234567890,"other":12345678901234567890,"date":"2023-01-16T04:44:23.939Z"}}}' 141 | ); 142 | 143 | interface Result { 144 | foo: { 145 | bar: { 146 | value: bigint; 147 | other: number; 148 | date: Date; 149 | }; 150 | baz: { 151 | value: bigint; 152 | other: number; 153 | }; 154 | }; 155 | qux: { 156 | quux: { 157 | value: bigint; 158 | other: number; 159 | date: Date; 160 | }; 161 | }; 162 | } 163 | 164 | const result = parseJsonByRules(source, { 165 | rules: [{ 166 | pattern: 'foo.*.value', 167 | reviver: context => BigInt(context.source), 168 | }, { 169 | pattern: '*.*.date', 170 | reviver: context => new Date(context.value), 171 | }], 172 | }); 173 | 174 | (expect(result.foo.bar.value) 175 | .to.equal(12345678901234567890n) // < BN 176 | ); 177 | 178 | (expect(result.foo.bar.other) 179 | .to.equal(12345678901234567000) 180 | ); 181 | 182 | (expect(result.foo.baz.value) 183 | .to.equal(12345678901234567890123n) // < BN 184 | ); 185 | 186 | (expect(result.foo.baz.other) 187 | .to.equal(1.2345678901234568e+22) 188 | ); 189 | 190 | expect(result.foo.bar.date).to.be.instanceOf(Date); 191 | (expect(result.foo.bar.date.toISOString()) 192 | .to.equal('2023-01-16T04:45:17.618Z') 193 | ); 194 | 195 | expect(result.qux.quux.date).to.be.instanceOf(Date); 196 | (expect(result.qux.quux.date.toISOString()) 197 | .to.equal('2023-01-16T04:44:23.939Z') 198 | ); 199 | 200 | }); 201 | 202 | test('any depths wildcards', () => { 203 | 204 | // { 205 | // "noskip": { 206 | // "foo": { 207 | // "bar": { 208 | // "baz": { 209 | // "bn": 11145678901234567890, 210 | // "other": 11145678901234567890, 211 | // "date": "2023-01-16T05:05:01.040Z", 212 | // "binary": { 213 | // "hex": "68656c6c6f20776f726c64", 214 | // "skip": { 215 | // "hex": "646f6e2774206d61746368" 216 | // } 217 | // } 218 | // } 219 | // } 220 | // }, 221 | // "red": { 222 | // "blue": { 223 | // "bn": 22245678901234567890, 224 | // "other": 22245678901234567890 225 | // } 226 | // }, 227 | // "winter": { 228 | // "bn": 33345678901234567890, 229 | // "other": 33345678901234567890, 230 | // "binary": { 231 | // "value": "77696e74657220697320636f6d696e67" 232 | // } 233 | // } 234 | // }, 235 | // "skip": { 236 | // "apple": { 237 | // "bn": 44445678901234567890, 238 | // "other": 44445678901234567890, 239 | // "date": "2023-01-16T05:05:19.584Z", 240 | // "binary": { 241 | // "HEX": "616c6c20796f75206e656564206973206c6f7665" 242 | // } 243 | // } 244 | // } 245 | // } 246 | 247 | const source = ( 248 | '{"noskip":{"foo":{"bar":{"baz":{"bn":11145678901234567890,"other":11145678901234567890,"date":"2023-01-16T05:05:01.040Z","binary":{"hex":"68656c6c6f20776f726c64","skip":{"hex":"646f6e2774206d61746368"}}}}},"red":{"blue":{"bn":22245678901234567890,"other":22245678901234567890}},"winter":{"bn":33345678901234567890,"other":33345678901234567890,"binary":{"value":"77696e74657220697320636f6d696e67"}}},"skip":{"apple":{"bn":44445678901234567890,"other":44445678901234567890,"date":"2023-01-16T05:05:19.584Z","binary":{"HEX":"616c6c20796f75206e656564206973206c6f7665"}}}}' 249 | ); 250 | 251 | interface Result { 252 | noskip: { 253 | foo: { 254 | bar: { 255 | baz: { 256 | bn: bigint; 257 | other: number; 258 | date: Date; 259 | binary: { 260 | hex: string; 261 | skip: { 262 | hex: string; 263 | }; 264 | }; 265 | } 266 | } 267 | }, 268 | red: { 269 | blue: { 270 | bn: bigint; 271 | other: number; 272 | } 273 | }, 274 | winter: { 275 | bn: bigint; 276 | other: number; 277 | binary: { 278 | value: string; 279 | }; 280 | } 281 | }; 282 | skip: { 283 | apple: { 284 | bn: bigint; 285 | other: number; 286 | date: Date; 287 | binary: { 288 | HEX: string; 289 | }; 290 | } 291 | }, 292 | } 293 | 294 | const result = parseJsonByRules(source, { 295 | rules: [{ 296 | pattern: 'noskip.**.bn', 297 | reviver: context => BigInt(context.source), 298 | }, { 299 | pattern: '**.date', 300 | reviver: context => new Date(context.value), 301 | }, { 302 | pattern: '**.binary.*', 303 | reviver: context => (typeof context.value === 'string' ? 304 | Buffer.from(context.value, 'hex').toString() : 305 | context.value 306 | ), 307 | }], 308 | }); 309 | 310 | (expect(result.noskip.foo.bar.baz.bn) 311 | .to.equal(11145678901234567890n) // < BN 312 | ); 313 | 314 | (expect(result.noskip.foo.bar.baz.other) 315 | .to.equal(11145678901234567000) 316 | ); 317 | 318 | (expect(result.noskip.red.blue.bn) 319 | .to.equal(22245678901234567890n) // < BN 320 | ); 321 | 322 | (expect(result.noskip.red.blue.other) 323 | .to.equal(22245678901234570000) 324 | ); 325 | 326 | (expect(result.noskip.winter.bn) 327 | .to.equal(33345678901234567890n) // < BN 328 | ); 329 | 330 | (expect(result.noskip.winter.other) 331 | .to.equal(33345678901234570000) 332 | ); 333 | 334 | (expect(result.skip.apple.bn) 335 | .to.equal(44445678901234565000) 336 | ); 337 | 338 | (expect(result.skip.apple.other) 339 | .to.equal(44445678901234565000) 340 | ); 341 | 342 | expect(result.noskip.foo.bar.baz.date).to.be.instanceOf(Date); 343 | (expect(result.noskip.foo.bar.baz.date.toISOString()) 344 | .to.equal('2023-01-16T05:05:01.040Z') 345 | ); 346 | 347 | expect(result.skip.apple.date).to.be.instanceOf(Date); 348 | (expect(result.skip.apple.date.toISOString()) 349 | .to.equal('2023-01-16T05:05:19.584Z') 350 | ); 351 | 352 | (expect(result.noskip.foo.bar.baz.binary.hex) 353 | .to.equal('hello world') 354 | ); 355 | 356 | (expect(result.noskip.foo.bar.baz.binary.skip.hex) 357 | .to.equal('646f6e2774206d61746368') 358 | ); 359 | 360 | (expect(result.noskip.winter.binary.value) 361 | .to.equal('winter is coming') 362 | ); 363 | 364 | (expect(result.skip.apple.binary.HEX) 365 | .to.equal('all you need is love') 366 | ); 367 | 368 | }); 369 | 370 | test('partial match', () => { 371 | 372 | // { 373 | // "foo":"2023-01-20T10:00:10.247Z", 374 | // "barDate":"2023-01-20T10:02:08.313Z", 375 | // "baz":"2023-01-20T10:02:16.700Z", 376 | // "quxDate":"2023-01-20T10:02:22.875Z", 377 | // "deeper":{ 378 | // "foo":11145678901234567890, 379 | // "bnFoo":22245678901234567890, 380 | // "bn":33345678901234567890, 381 | // "bar_bn":44445678901234567890 382 | // }, 383 | // "deep":{ 384 | // "e":{ 385 | // "r":{ 386 | // "foo":11145678901234567890, 387 | // "bnFoo":22245678901234567890, 388 | // "bn":33345678901234567890, 389 | // "bar_bn":44445678901234567890 390 | // } 391 | // } 392 | // }, 393 | // "gecko":{ 394 | // "hex":"616c6c20796f75206e656564206973206c6f7665", 395 | // "foohex":"7768617473206f6e2074763f", 396 | // "hexBar":"6973206974206120636f6666656520627265616b3f", 397 | // "__hex__":"466f72204169757221" 398 | // } 399 | // } 400 | 401 | const source = ( 402 | '{"foo":"2023-01-20T10:00:10.247Z","barDate":"2023-01-20T10:02:08.313Z","baz":"2023-01-20T10:02:16.700Z","quxDate":"2023-01-20T10:02:22.875Z","deeper":{"foo":11145678901234567890,"bnFoo":22245678901234567890,"bn":33345678901234567890,"bar_bn":44445678901234567890},"deep":{"e":{"r":{"foo":11145678901234567890,"bnFoo":22245678901234567890,"bn":33345678901234567890,"bar_bn":44445678901234567890}}},"gecko":{"hex":"616c6c20796f75206e656564206973206c6f7665","foohex":"7768617473206f6e2074763f","hexBar":"6973206974206120636f6666656520627265616b3f","__hex__":"466f72204169757221"}}' 403 | ); 404 | 405 | interface Result { 406 | foo: string; 407 | barDate: Date; 408 | baz: string; 409 | quxDate: Date; 410 | deeper: { 411 | foo: number, 412 | bnFoo: bigint; 413 | bn: bigint; 414 | bar_bn: number; 415 | }; 416 | deep: { 417 | e: { 418 | r: { 419 | foo: number, 420 | bnFoo: bigint; 421 | bn: bigint; 422 | bar_bn: number; 423 | }; 424 | }; 425 | }; 426 | gecko: { 427 | hex: string; 428 | foohex: string; 429 | hexBar: string; 430 | __hex__: string; 431 | }; 432 | } 433 | 434 | const result = parseJsonByRules(source, { 435 | rules: [{ 436 | pattern: '*Date', 437 | reviver: context => new Date(context.value), 438 | }, { 439 | pattern: 'deeper.bn*', 440 | reviver: context => BigInt(context.source), 441 | }, { 442 | pattern: 'deep.**.bn*', 443 | reviver: context => BigInt(context.source), 444 | }, { 445 | pattern: '**.*hex*', 446 | reviver: context => ( 447 | Buffer.from(context.value, 'hex').toString() 448 | ), 449 | }], 450 | }); 451 | 452 | (expect(result.foo) 453 | .to.equal('2023-01-20T10:00:10.247Z') 454 | ); 455 | 456 | expect(result.barDate).to.be.instanceOf(Date); 457 | (expect(result.barDate.toISOString()) 458 | .to.equal('2023-01-20T10:02:08.313Z') 459 | ); 460 | 461 | (expect(result.baz) 462 | .to.equal('2023-01-20T10:02:16.700Z') 463 | ); 464 | 465 | expect(result.quxDate).to.be.instanceOf(Date); 466 | (expect(result.quxDate.toISOString()) 467 | .to.equal('2023-01-20T10:02:22.875Z') 468 | ); 469 | 470 | (expect(result.deeper.foo) 471 | .to.equal(11145678901234567000) 472 | ); 473 | 474 | (expect(result.deeper.bnFoo) 475 | .to.equal(22245678901234567890n) // < BN 476 | ); 477 | 478 | (expect(result.deeper.bn) 479 | .to.equal(33345678901234567890n) // < BN 480 | ); 481 | 482 | (expect(result.deeper.bar_bn) 483 | .to.equal(44445678901234565000) 484 | ); 485 | 486 | (expect(result.deep.e.r.foo) 487 | .to.equal(11145678901234567000) 488 | ); 489 | 490 | (expect(result.deep.e.r.bnFoo) 491 | .to.equal(22245678901234567890n) // < BN 492 | ); 493 | 494 | (expect(result.deep.e.r.bn) 495 | .to.equal(33345678901234567890n) // < BN 496 | ); 497 | 498 | (expect(result.deep.e.r.bar_bn) 499 | .to.equal(44445678901234565000) 500 | ); 501 | 502 | (expect(result.gecko.hex) 503 | .to.equal('all you need is love') 504 | ); 505 | 506 | (expect(result.gecko.foohex) 507 | .to.equal('whats on tv?') 508 | ); 509 | 510 | (expect(result.gecko.hexBar) 511 | .to.equal('is it a coffee break?') 512 | ); 513 | 514 | (expect(result.gecko.__hex__) 515 | .to.equal('For Aiur!') 516 | ); 517 | 518 | }); 519 | 520 | }); 521 | -------------------------------------------------------------------------------- /packages/json-parser-rules/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.common.json", 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "resolveJsonModule": true, 7 | }, 8 | "files": [ 9 | "esbuild.ts" 10 | ], 11 | "ts-node": { 12 | "esm": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/json-parser-rules/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.common.json", 4 | "include": [ 5 | "src/" 6 | ], 7 | "exclude": [ 8 | "**/*.test.ts" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/json-parser-rules/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.common.json", 4 | "compilerOptions": { 5 | }, 6 | "include": [ 7 | "src/", 8 | "test/" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/json-parser-rules/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.common.json", 4 | "compilerOptions": { 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "declarationMap": true, 8 | "declarationDir": "temp/types", 9 | "isolatedModules": true 10 | }, 11 | "include": [ 12 | "src/" 13 | ], 14 | "exclude": [ 15 | "**/*.test.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/json-parser/.gitignore: -------------------------------------------------------------------------------- 1 | !/dist 2 | /README.md 3 | -------------------------------------------------------------------------------- /packages/json-parser/.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "loader": "ts-node/esm", 3 | "extensions": ["ts"], 4 | "spec": [ 5 | "src/**/*.test.ts", 6 | "test/**/*.test.ts" 7 | ], 8 | "watch-files": [ 9 | "src" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/json-parser/.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@istanbuljs/nyc-config-typescript", 3 | "extension": [ 4 | ".ts" 5 | ], 6 | "all": true 7 | } 8 | -------------------------------------------------------------------------------- /packages/json-parser/api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "projectFolder": ".", 4 | "mainEntryPointFilePath": "/temp/types/index.d.ts", 5 | "bundledPackages": [], 6 | "newlineKind": "lf", 7 | "enumMemberOrder": "preserve", 8 | "compiler": { 9 | "tsconfigFilePath": "/tsconfig.types.json" 10 | }, 11 | "apiReport": { 12 | "enabled": true, 13 | "reportFileName": ".api.md", 14 | "reportFolder": "", 15 | "reportTempFolder": "/temp/" 16 | }, 17 | "docModel": { 18 | "enabled": true, 19 | "apiJsonFilePath": "/temp/.api.json", 20 | "projectFolderUrl": "https://github.com/ton-js/json-parser" 21 | }, 22 | "dtsRollup": { 23 | "enabled": true, 24 | "publicTrimmedFilePath": "/dist/index.d.ts", 25 | "alphaTrimmedFilePath": "/dist/index-alpha.d.ts", 26 | "betaTrimmedFilePath": "/dist/index-beta.d.ts", 27 | "untrimmedFilePath": "" 28 | }, 29 | "tsdocMetadata": {}, 30 | "messages": { 31 | "compilerMessageReporting": { 32 | "default": { 33 | "logLevel": "warning" 34 | } 35 | }, 36 | "extractorMessageReporting": { 37 | "default": { 38 | "logLevel": "warning" 39 | } 40 | }, 41 | "tsdocMessageReporting": { 42 | "default": { 43 | "logLevel": "warning" 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/json-parser/dist/index-alpha.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * {@link https://www.json.org/json-en.html | JSON specification} 3 | */ 4 | /** 5 | * @public 6 | */ 7 | export declare type Maybe = (Type | undefined); 8 | 9 | /** 10 | * @public 11 | */ 12 | export declare interface Options { 13 | throwOnProto?: Maybe; 14 | } 15 | 16 | /** 17 | * @public 18 | * 19 | * Parses JSON document and returns the parsed data. 20 | */ 21 | export declare function parseJson(source: string, reviver?: Maybe, options?: Options): Type; 22 | 23 | /** 24 | * @public 25 | */ 26 | export declare interface ReviverContext { 27 | source: string; 28 | keys: string[]; 29 | } 30 | 31 | /** 32 | * @public 33 | */ 34 | export declare type ReviverFunc = (key: string, value: any, context: ReviverContext) => any; 35 | 36 | export { } 37 | -------------------------------------------------------------------------------- /packages/json-parser/dist/index-beta.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * {@link https://www.json.org/json-en.html | JSON specification} 3 | */ 4 | /** 5 | * @public 6 | */ 7 | export declare type Maybe = (Type | undefined); 8 | 9 | /** 10 | * @public 11 | */ 12 | export declare interface Options { 13 | throwOnProto?: Maybe; 14 | } 15 | 16 | /** 17 | * @public 18 | * 19 | * Parses JSON document and returns the parsed data. 20 | */ 21 | export declare function parseJson(source: string, reviver?: Maybe, options?: Options): Type; 22 | 23 | /** 24 | * @public 25 | */ 26 | export declare interface ReviverContext { 27 | source: string; 28 | keys: string[]; 29 | } 30 | 31 | /** 32 | * @public 33 | */ 34 | export declare type ReviverFunc = (key: string, value: any, context: ReviverContext) => any; 35 | 36 | export { } 37 | -------------------------------------------------------------------------------- /packages/json-parser/dist/index.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __defProp = Object.defineProperty; 3 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 4 | var __getOwnPropNames = Object.getOwnPropertyNames; 5 | var __hasOwnProp = Object.prototype.hasOwnProperty; 6 | var __export = (target, all) => { 7 | for (var name in all) 8 | __defProp(target, name, { get: all[name], enumerable: true }); 9 | }; 10 | var __copyProps = (to, from, except, desc) => { 11 | if (from && typeof from === "object" || typeof from === "function") { 12 | for (let key of __getOwnPropNames(from)) 13 | if (!__hasOwnProp.call(to, key) && key !== except) 14 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 15 | } 16 | return to; 17 | }; 18 | var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 19 | 20 | // src/index.ts 21 | var src_exports = {}; 22 | __export(src_exports, { 23 | parseJson: () => parseJson 24 | }); 25 | module.exports = __toCommonJS(src_exports); 26 | 27 | // src/json-parser.ts 28 | var codePoints = { 29 | '\\"': '"', 30 | "\\\\": "\\", 31 | "\\/": "/", 32 | "\\b": "\b", 33 | "\\f": "\f", 34 | "\\n": "\n", 35 | "\\r": "\r", 36 | "\\t": " " 37 | }; 38 | function parseJson(source, reviver, options) { 39 | const result = parseValue({ 40 | source, 41 | index: 0, 42 | keys: [], 43 | reviver, 44 | options 45 | }); 46 | const endIndex = skipSpaces(source, result.endIndex); 47 | if (endIndex <= source.length - 1) { 48 | throw new SyntaxError( 49 | `Unexpected extra characters after the parsed data: "${source.substring(endIndex, endIndex + 16)}\u2026" at position: ${endIndex}` 50 | ); 51 | } 52 | return result.value; 53 | } 54 | function parseValue(context) { 55 | const { source, index } = context; 56 | let result; 57 | let i = skipSpaces(source, index); 58 | const newContext = nextContext(context, i); 59 | if (isNumberStart(source[i])) { 60 | result = parseNumber(newContext); 61 | } else { 62 | switch (source[i]) { 63 | case '"': { 64 | result = parseString(newContext); 65 | break; 66 | } 67 | case "[": { 68 | result = parseArray(newContext); 69 | break; 70 | } 71 | case "{": { 72 | result = parseObject(newContext); 73 | break; 74 | } 75 | case "t": { 76 | result = parseKeyword(newContext, "true"); 77 | break; 78 | } 79 | case "f": { 80 | result = parseKeyword(newContext, "false"); 81 | break; 82 | } 83 | case "n": { 84 | result = parseKeyword(newContext, "null"); 85 | break; 86 | } 87 | default: { 88 | throw new SyntaxError( 89 | `Unexpected character: "${source[i]}" at position: ${i}` 90 | ); 91 | } 92 | } 93 | } 94 | result.value = callReviver({ 95 | context, 96 | rawValue: source.substring(i, result.endIndex), 97 | value: result.value 98 | }); 99 | return result; 100 | } 101 | function parseArray(context) { 102 | const { source, index } = context; 103 | const array = []; 104 | let i = index + 1; 105 | let expectElement = false; 106 | let elementIndex = 0; 107 | while (i < source.length) { 108 | i = skipSpaces(source, i); 109 | if (source[i] === "]" && !expectElement) { 110 | i++; 111 | break; 112 | } 113 | const result = parseValue( 114 | nextContext(context, i, elementIndex.toString()) 115 | ); 116 | array.push(result.value); 117 | i = result.endIndex; 118 | i = skipUntil(source, i, [",", "]"]); 119 | if (source[i] === ",") { 120 | expectElement = true; 121 | elementIndex++; 122 | i++; 123 | } else if (source[i] === "]") { 124 | i++; 125 | break; 126 | } 127 | } 128 | return { 129 | value: array, 130 | endIndex: i 131 | }; 132 | } 133 | function parseObject(context) { 134 | const { source, index } = context; 135 | let object = {}; 136 | let i = index + 1; 137 | let expectKeypair = false; 138 | while (i < source.length) { 139 | i = skipUntil(source, i, ['"', "}"]); 140 | if (source[i] === "}" && !expectKeypair) { 141 | i++; 142 | break; 143 | } 144 | let result = parseString( 145 | nextContext(context, i) 146 | ); 147 | const key = result.value; 148 | i = result.endIndex; 149 | i = skipUntil(source, i, ":") + 1; 150 | if (context.options?.throwOnProto) { 151 | if (key === "__proto__") { 152 | throw new SyntaxError( 153 | `Forbidden object property name: "__proto__"` 154 | ); 155 | } else if (isConstructorPrototype(key)) { 156 | throw new SyntaxError( 157 | `Forbidden object property path: "constructor.prototype"` 158 | ); 159 | } 160 | } 161 | i = skipSpaces(source, i); 162 | result = parseValue( 163 | nextContext(context, i, key) 164 | ); 165 | if (result.value !== void 0 && isAllowedKey(key)) { 166 | object[key] = result.value; 167 | } 168 | i = result.endIndex; 169 | i = skipUntil(source, i, [",", "}"]); 170 | if (source[i] === ",") { 171 | expectKeypair = true; 172 | i++; 173 | } else if (source[i] === "}") { 174 | i++; 175 | break; 176 | } 177 | } 178 | return { 179 | value: object, 180 | endIndex: i 181 | }; 182 | function isConstructorPrototype(key) { 183 | if (key !== "prototype") { 184 | return false; 185 | } 186 | const parentKey = context.keys.length > 0 ? context.keys[context.keys.length - 1] : void 0; 187 | return parentKey === "constructor"; 188 | } 189 | function isAllowedKey(key) { 190 | return key !== "__proto__" && !isConstructorPrototype(key); 191 | } 192 | } 193 | function parseString(context) { 194 | const { source, index } = context; 195 | let value = ""; 196 | let i = index + 1; 197 | while (i < source.length) { 198 | const char = source[i]; 199 | if (char === "\\") { 200 | const twoChars = source.substring(i, i + 2); 201 | const codepoint = codePoints[twoChars]; 202 | if (codepoint) { 203 | value += codepoint; 204 | i += 2; 205 | } else if (twoChars === "\\u") { 206 | const charHex = source.substring(i + 2, i + 6); 207 | value += String.fromCharCode(parseInt(charHex, 16)); 208 | i += 6; 209 | } else { 210 | throw new SyntaxError( 211 | `Unknown escape sequence: "${twoChars}"` 212 | ); 213 | } 214 | } else if (char === '"') { 215 | i++; 216 | break; 217 | } else { 218 | value += char; 219 | i++; 220 | } 221 | } 222 | return { value, endIndex: i }; 223 | } 224 | function isNumberStart(char) { 225 | return Boolean(char.match(/^(-|\d)$/)); 226 | } 227 | function parseNumber(context) { 228 | const { source, index } = context; 229 | let isNegative = false; 230 | let integer = "0"; 231 | let fraction = ""; 232 | let isExponentNegative = false; 233 | let exponent = ""; 234 | let i = index; 235 | if (source[i] === "-") { 236 | isNegative = true; 237 | i++; 238 | } 239 | if (source[i] === "0") { 240 | i++; 241 | } else if (source[i].match(/^[1-9]$/)) { 242 | integer = source[i]; 243 | i++; 244 | while (i < source.length) { 245 | if (source[i].match(/^\d$/)) { 246 | integer += source[i]; 247 | i++; 248 | } else { 249 | break; 250 | } 251 | } 252 | } else { 253 | throw new SyntaxError( 254 | `Failed to parse number at position: ${i}` 255 | ); 256 | } 257 | if (source[i] === ".") { 258 | i++; 259 | while (i < source.length) { 260 | if (source[i].match(/^\d$/)) { 261 | fraction += source[i]; 262 | i++; 263 | } else { 264 | break; 265 | } 266 | } 267 | } 268 | if (["e", "E"].includes(source[i])) { 269 | i++; 270 | if (source[i] === "+") { 271 | i++; 272 | } else if (source[i] === "-") { 273 | isExponentNegative = true; 274 | i++; 275 | } 276 | const exponentStartIndex = i; 277 | while (i < source.length) { 278 | if (source[i].match(/^\d$/)) { 279 | exponent += source[i]; 280 | i++; 281 | } else { 282 | break; 283 | } 284 | } 285 | if (exponent.length === 0) { 286 | throw new SyntaxError( 287 | `Failed to parse number's exponent value at position: ${exponentStartIndex}` 288 | ); 289 | } 290 | } 291 | let value = Number( 292 | (isNegative ? "-" : "") + integer + (fraction ? `.${fraction}` : "") + (exponent ? `e${isExponentNegative ? "-" : ""}${exponent}` : "") 293 | ); 294 | return { 295 | value, 296 | endIndex: i 297 | }; 298 | } 299 | function skipUntil(source, startIndex, endChar) { 300 | endChar = Array.isArray(endChar) ? endChar : [endChar]; 301 | const i = skipSpaces(source, startIndex); 302 | const char = source[i]; 303 | if (endChar.includes(char)) { 304 | return i; 305 | } else { 306 | throw new SyntaxError( 307 | `Unexpected character: "${char}" at position: ${i}` 308 | ); 309 | } 310 | } 311 | function skipSpaces(source, startIndex) { 312 | let i; 313 | for (i = startIndex; i < source.length; i++) { 314 | const char = source[i]; 315 | if (!isWhitespace(char)) { 316 | break; 317 | } 318 | } 319 | return i; 320 | } 321 | function isWhitespace(char) { 322 | return [" ", "\n", "\r", " "].includes(char); 323 | } 324 | function parseKeyword(context, keyword) { 325 | const { source, index } = context; 326 | const endIndex = index + keyword.length; 327 | const slice = source.substring(index, endIndex); 328 | if (slice !== keyword) { 329 | throw new SyntaxError( 330 | `Failed to parse value at position: ${index}` 331 | ); 332 | } 333 | let value = keyword === "true" ? true : keyword === "false" ? false : null; 334 | return { 335 | value, 336 | endIndex 337 | }; 338 | } 339 | function callReviver(args) { 340 | const { context, rawValue, value } = args; 341 | const { reviver, keys } = context; 342 | if (!reviver) { 343 | return value; 344 | } 345 | const key = keys.length > 0 ? keys[keys.length - 1] : ""; 346 | return reviver(key, value, { 347 | source: rawValue, 348 | keys 349 | }); 350 | } 351 | function nextContext(context, nextIndex, nextKey) { 352 | const newContext = { 353 | ...context, 354 | index: nextIndex 355 | }; 356 | if (nextKey) { 357 | newContext.keys = [ 358 | ...context.keys, 359 | nextKey 360 | ]; 361 | } 362 | return newContext; 363 | } 364 | // Annotate the CommonJS export names for ESM import in node: 365 | 0 && (module.exports = { 366 | parseJson 367 | }); 368 | //# sourceMappingURL=index.cjs.map 369 | -------------------------------------------------------------------------------- /packages/json-parser/dist/index.cjs.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "inputs": { 3 | "src/json-parser.ts": { 4 | "bytes": 10398, 5 | "imports": [], 6 | "format": "esm" 7 | }, 8 | "src/index.ts": { 9 | "bytes": 143, 10 | "imports": [ 11 | { 12 | "path": "src/json-parser.ts", 13 | "kind": "import-statement", 14 | "original": "./json-parser.js" 15 | } 16 | ], 17 | "format": "esm" 18 | } 19 | }, 20 | "outputs": { 21 | "dist/index.cjs.map": { 22 | "imports": [], 23 | "exports": [], 24 | "inputs": {}, 25 | "bytes": 17514 26 | }, 27 | "dist/index.cjs": { 28 | "imports": [], 29 | "exports": [], 30 | "entryPoint": "src/index.ts", 31 | "inputs": { 32 | "src/index.ts": { 33 | "bytesInOutput": 123 34 | }, 35 | "src/json-parser.ts": { 36 | "bytesInOutput": 7924 37 | } 38 | }, 39 | "bytes": 9024 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /packages/json-parser/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * {@link https://www.json.org/json-en.html | JSON specification} 3 | */ 4 | /** 5 | * @public 6 | */ 7 | export declare type Maybe = (Type | undefined); 8 | 9 | /** 10 | * @public 11 | */ 12 | export declare interface Options { 13 | throwOnProto?: Maybe; 14 | } 15 | 16 | /** 17 | * @public 18 | * 19 | * Parses JSON document and returns the parsed data. 20 | */ 21 | export declare function parseJson(source: string, reviver?: Maybe, options?: Options): Type; 22 | 23 | /** 24 | * @public 25 | */ 26 | export declare interface ReviverContext { 27 | source: string; 28 | keys: string[]; 29 | } 30 | 31 | /** 32 | * @public 33 | */ 34 | export declare type ReviverFunc = (key: string, value: any, context: ReviverContext) => any; 35 | 36 | export { } 37 | -------------------------------------------------------------------------------- /packages/json-parser/dist/index.mjs: -------------------------------------------------------------------------------- 1 | // src/json-parser.ts 2 | var codePoints = { 3 | '\\"': '"', 4 | "\\\\": "\\", 5 | "\\/": "/", 6 | "\\b": "\b", 7 | "\\f": "\f", 8 | "\\n": "\n", 9 | "\\r": "\r", 10 | "\\t": " " 11 | }; 12 | function parseJson(source, reviver, options) { 13 | const result = parseValue({ 14 | source, 15 | index: 0, 16 | keys: [], 17 | reviver, 18 | options 19 | }); 20 | const endIndex = skipSpaces(source, result.endIndex); 21 | if (endIndex <= source.length - 1) { 22 | throw new SyntaxError( 23 | `Unexpected extra characters after the parsed data: "${source.substring(endIndex, endIndex + 16)}\u2026" at position: ${endIndex}` 24 | ); 25 | } 26 | return result.value; 27 | } 28 | function parseValue(context) { 29 | const { source, index } = context; 30 | let result; 31 | let i = skipSpaces(source, index); 32 | const newContext = nextContext(context, i); 33 | if (isNumberStart(source[i])) { 34 | result = parseNumber(newContext); 35 | } else { 36 | switch (source[i]) { 37 | case '"': { 38 | result = parseString(newContext); 39 | break; 40 | } 41 | case "[": { 42 | result = parseArray(newContext); 43 | break; 44 | } 45 | case "{": { 46 | result = parseObject(newContext); 47 | break; 48 | } 49 | case "t": { 50 | result = parseKeyword(newContext, "true"); 51 | break; 52 | } 53 | case "f": { 54 | result = parseKeyword(newContext, "false"); 55 | break; 56 | } 57 | case "n": { 58 | result = parseKeyword(newContext, "null"); 59 | break; 60 | } 61 | default: { 62 | throw new SyntaxError( 63 | `Unexpected character: "${source[i]}" at position: ${i}` 64 | ); 65 | } 66 | } 67 | } 68 | result.value = callReviver({ 69 | context, 70 | rawValue: source.substring(i, result.endIndex), 71 | value: result.value 72 | }); 73 | return result; 74 | } 75 | function parseArray(context) { 76 | const { source, index } = context; 77 | const array = []; 78 | let i = index + 1; 79 | let expectElement = false; 80 | let elementIndex = 0; 81 | while (i < source.length) { 82 | i = skipSpaces(source, i); 83 | if (source[i] === "]" && !expectElement) { 84 | i++; 85 | break; 86 | } 87 | const result = parseValue( 88 | nextContext(context, i, elementIndex.toString()) 89 | ); 90 | array.push(result.value); 91 | i = result.endIndex; 92 | i = skipUntil(source, i, [",", "]"]); 93 | if (source[i] === ",") { 94 | expectElement = true; 95 | elementIndex++; 96 | i++; 97 | } else if (source[i] === "]") { 98 | i++; 99 | break; 100 | } 101 | } 102 | return { 103 | value: array, 104 | endIndex: i 105 | }; 106 | } 107 | function parseObject(context) { 108 | const { source, index } = context; 109 | let object = {}; 110 | let i = index + 1; 111 | let expectKeypair = false; 112 | while (i < source.length) { 113 | i = skipUntil(source, i, ['"', "}"]); 114 | if (source[i] === "}" && !expectKeypair) { 115 | i++; 116 | break; 117 | } 118 | let result = parseString( 119 | nextContext(context, i) 120 | ); 121 | const key = result.value; 122 | i = result.endIndex; 123 | i = skipUntil(source, i, ":") + 1; 124 | if (context.options?.throwOnProto) { 125 | if (key === "__proto__") { 126 | throw new SyntaxError( 127 | `Forbidden object property name: "__proto__"` 128 | ); 129 | } else if (isConstructorPrototype(key)) { 130 | throw new SyntaxError( 131 | `Forbidden object property path: "constructor.prototype"` 132 | ); 133 | } 134 | } 135 | i = skipSpaces(source, i); 136 | result = parseValue( 137 | nextContext(context, i, key) 138 | ); 139 | if (result.value !== void 0 && isAllowedKey(key)) { 140 | object[key] = result.value; 141 | } 142 | i = result.endIndex; 143 | i = skipUntil(source, i, [",", "}"]); 144 | if (source[i] === ",") { 145 | expectKeypair = true; 146 | i++; 147 | } else if (source[i] === "}") { 148 | i++; 149 | break; 150 | } 151 | } 152 | return { 153 | value: object, 154 | endIndex: i 155 | }; 156 | function isConstructorPrototype(key) { 157 | if (key !== "prototype") { 158 | return false; 159 | } 160 | const parentKey = context.keys.length > 0 ? context.keys[context.keys.length - 1] : void 0; 161 | return parentKey === "constructor"; 162 | } 163 | function isAllowedKey(key) { 164 | return key !== "__proto__" && !isConstructorPrototype(key); 165 | } 166 | } 167 | function parseString(context) { 168 | const { source, index } = context; 169 | let value = ""; 170 | let i = index + 1; 171 | while (i < source.length) { 172 | const char = source[i]; 173 | if (char === "\\") { 174 | const twoChars = source.substring(i, i + 2); 175 | const codepoint = codePoints[twoChars]; 176 | if (codepoint) { 177 | value += codepoint; 178 | i += 2; 179 | } else if (twoChars === "\\u") { 180 | const charHex = source.substring(i + 2, i + 6); 181 | value += String.fromCharCode(parseInt(charHex, 16)); 182 | i += 6; 183 | } else { 184 | throw new SyntaxError( 185 | `Unknown escape sequence: "${twoChars}"` 186 | ); 187 | } 188 | } else if (char === '"') { 189 | i++; 190 | break; 191 | } else { 192 | value += char; 193 | i++; 194 | } 195 | } 196 | return { value, endIndex: i }; 197 | } 198 | function isNumberStart(char) { 199 | return Boolean(char.match(/^(-|\d)$/)); 200 | } 201 | function parseNumber(context) { 202 | const { source, index } = context; 203 | let isNegative = false; 204 | let integer = "0"; 205 | let fraction = ""; 206 | let isExponentNegative = false; 207 | let exponent = ""; 208 | let i = index; 209 | if (source[i] === "-") { 210 | isNegative = true; 211 | i++; 212 | } 213 | if (source[i] === "0") { 214 | i++; 215 | } else if (source[i].match(/^[1-9]$/)) { 216 | integer = source[i]; 217 | i++; 218 | while (i < source.length) { 219 | if (source[i].match(/^\d$/)) { 220 | integer += source[i]; 221 | i++; 222 | } else { 223 | break; 224 | } 225 | } 226 | } else { 227 | throw new SyntaxError( 228 | `Failed to parse number at position: ${i}` 229 | ); 230 | } 231 | if (source[i] === ".") { 232 | i++; 233 | while (i < source.length) { 234 | if (source[i].match(/^\d$/)) { 235 | fraction += source[i]; 236 | i++; 237 | } else { 238 | break; 239 | } 240 | } 241 | } 242 | if (["e", "E"].includes(source[i])) { 243 | i++; 244 | if (source[i] === "+") { 245 | i++; 246 | } else if (source[i] === "-") { 247 | isExponentNegative = true; 248 | i++; 249 | } 250 | const exponentStartIndex = i; 251 | while (i < source.length) { 252 | if (source[i].match(/^\d$/)) { 253 | exponent += source[i]; 254 | i++; 255 | } else { 256 | break; 257 | } 258 | } 259 | if (exponent.length === 0) { 260 | throw new SyntaxError( 261 | `Failed to parse number's exponent value at position: ${exponentStartIndex}` 262 | ); 263 | } 264 | } 265 | let value = Number( 266 | (isNegative ? "-" : "") + integer + (fraction ? `.${fraction}` : "") + (exponent ? `e${isExponentNegative ? "-" : ""}${exponent}` : "") 267 | ); 268 | return { 269 | value, 270 | endIndex: i 271 | }; 272 | } 273 | function skipUntil(source, startIndex, endChar) { 274 | endChar = Array.isArray(endChar) ? endChar : [endChar]; 275 | const i = skipSpaces(source, startIndex); 276 | const char = source[i]; 277 | if (endChar.includes(char)) { 278 | return i; 279 | } else { 280 | throw new SyntaxError( 281 | `Unexpected character: "${char}" at position: ${i}` 282 | ); 283 | } 284 | } 285 | function skipSpaces(source, startIndex) { 286 | let i; 287 | for (i = startIndex; i < source.length; i++) { 288 | const char = source[i]; 289 | if (!isWhitespace(char)) { 290 | break; 291 | } 292 | } 293 | return i; 294 | } 295 | function isWhitespace(char) { 296 | return [" ", "\n", "\r", " "].includes(char); 297 | } 298 | function parseKeyword(context, keyword) { 299 | const { source, index } = context; 300 | const endIndex = index + keyword.length; 301 | const slice = source.substring(index, endIndex); 302 | if (slice !== keyword) { 303 | throw new SyntaxError( 304 | `Failed to parse value at position: ${index}` 305 | ); 306 | } 307 | let value = keyword === "true" ? true : keyword === "false" ? false : null; 308 | return { 309 | value, 310 | endIndex 311 | }; 312 | } 313 | function callReviver(args) { 314 | const { context, rawValue, value } = args; 315 | const { reviver, keys } = context; 316 | if (!reviver) { 317 | return value; 318 | } 319 | const key = keys.length > 0 ? keys[keys.length - 1] : ""; 320 | return reviver(key, value, { 321 | source: rawValue, 322 | keys 323 | }); 324 | } 325 | function nextContext(context, nextIndex, nextKey) { 326 | const newContext = { 327 | ...context, 328 | index: nextIndex 329 | }; 330 | if (nextKey) { 331 | newContext.keys = [ 332 | ...context.keys, 333 | nextKey 334 | ]; 335 | } 336 | return newContext; 337 | } 338 | export { 339 | parseJson 340 | }; 341 | //# sourceMappingURL=index.mjs.map 342 | -------------------------------------------------------------------------------- /packages/json-parser/dist/index.mjs.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../src/json-parser.ts"], 4 | "sourcesContent": ["\n/**\n * {@link https://www.json.org/json-en.html | JSON specification}\n */\n\n/**\n * @public\n */\nexport type Maybe = (Type | undefined);\n\n/**\n * @public\n */\nexport type ReviverFunc = (\n key: string,\n value: any,\n context: ReviverContext\n\n) => any;\n\n/**\n * @public\n */\nexport interface ReviverContext {\n source: string;\n keys: string[];\n}\n\n/**\n * @public\n */\nexport interface Options {\n throwOnProto?: Maybe;\n}\n\ninterface ParseContext {\n source: string;\n index: number;\n keys: string[];\n reviver?: Maybe;\n options?: Maybe;\n}\n\ninterface ParseResult {\n value: Type;\n endIndex: number;\n}\n\ntype Keyword = (\n | 'true'\n | 'false'\n | 'null'\n);\n\n\nconst codePoints: Record = {\n '\\\\\"': '\"',\n '\\\\\\\\': '\\\\',\n '\\\\/': '/',\n '\\\\b': '\\b',\n '\\\\f': '\\f',\n '\\\\n': '\\n',\n '\\\\r': '\\r',\n '\\\\t': '\\t',\n};\n\n/**\n * @public\n *\n * Parses JSON document and returns the parsed data.\n */\nexport function parseJson(\n source: string,\n reviver?: Maybe,\n options?: Options\n\n): Type {\n\n const result = parseValue({\n source,\n index: 0,\n keys: [],\n reviver,\n options,\n });\n\n const endIndex = skipSpaces(source, result.endIndex);\n\n if (endIndex <= (source.length - 1)) {\n throw new SyntaxError(\n `Unexpected extra characters after the parsed data: ` +\n `\"${source.substring(endIndex, endIndex + 16)}\u2026\" ` +\n `at position: ${endIndex}`\n );\n }\n\n return result.value;\n\n}\n\nfunction parseValue(context: ParseContext): ParseResult {\n\n const { source, index } = context;\n\n let result: ParseResult;\n\n let i = skipSpaces(source, index);\n\n const newContext = nextContext(context, i);\n\n if (isNumberStart(source[i]!)) {\n\n result = parseNumber(newContext);\n\n } else {\n\n switch (source[i]) {\n case '\"': {\n result = parseString(newContext);\n break;\n }\n case '[': {\n result = parseArray(newContext);\n break;\n }\n case '{': {\n result = parseObject(newContext);\n break;\n }\n case 't': {\n result = parseKeyword(newContext, 'true');\n break;\n }\n case 'f': {\n result = parseKeyword(newContext, 'false');\n break;\n }\n case 'n': {\n result = parseKeyword(newContext, 'null');\n break;\n }\n default: {\n throw new SyntaxError(\n `Unexpected character: \"${source[i]}\" ` +\n `at position: ${i}`\n );\n }\n }\n\n }\n\n result.value = callReviver({\n context,\n rawValue: source.substring(i, result.endIndex),\n value: result.value,\n });\n\n return result;\n\n}\n\nfunction parseArray(context: ParseContext): ParseResult {\n\n const { source, index } = context;\n\n const array: any[] = [];\n\n let i = (index + 1);\n let expectElement = false;\n let elementIndex = 0;\n\n while (i < source.length) {\n\n i = skipSpaces(source, i);\n\n if (source[i] === ']' && !expectElement) {\n // End of the array\n i++;\n break;\n }\n\n const result = parseValue(\n nextContext(context, i, elementIndex.toString())\n );\n\n array.push(result.value);\n i = result.endIndex;\n i = skipUntil(source, i, [',', ']']);\n if (source[i] === ',') {\n // Going to parse the next value\n expectElement = true;\n elementIndex++;\n i++;\n } else if (source[i] === ']') {\n // End of the array\n i++;\n break;\n }\n\n }\n\n return {\n value: array,\n endIndex: i,\n };\n\n}\n\nfunction parseObject(context: ParseContext): ParseResult {\n\n const { source, index } = context;\n\n let object: Record = {};\n\n let i = (index + 1);\n let expectKeypair = false;\n\n while (i < source.length) {\n\n i = skipUntil(source, i, ['\"', '}']);\n\n if (source[i] === '}' && !expectKeypair) {\n // End of the object\n i++;\n break;\n }\n\n // Parsing the key\n let result = parseString(\n nextContext(context, i)\n );\n const key = result.value;\n i = result.endIndex;\n i = skipUntil(source, i, ':') + 1;\n\n // Checking forbidden prototype property names and\n // throwing error if related option is set.\n if (context.options?.throwOnProto) {\n if (key === '__proto__') {\n throw new SyntaxError(\n `Forbidden object property name: \"__proto__\"`\n );\n } else if (isConstructorPrototype(key)) {\n throw new SyntaxError(\n `Forbidden object property path: \"constructor.prototype\"`\n );\n }\n }\n\n // Parsing value\n i = skipSpaces(source, i);\n result = parseValue(\n nextContext(context, i, key)\n );\n if (result.value !== undefined && isAllowedKey(key)) {\n object[key] = result.value;\n }\n i = result.endIndex;\n i = skipUntil(source, i, [',', '}']);\n if (source[i] === ',') {\n // Going to parse the next keypair\n expectKeypair = true;\n i++;\n } else if (source[i] === '}') {\n // End of the object\n i++;\n break;\n }\n\n }\n\n return {\n value: object,\n endIndex: i,\n };\n\n\n /**\n * Checks whether the key is part of \"constructor.prototype\"\n * path.\n */\n function isConstructorPrototype(key: string) {\n\n if (key !== 'prototype') {\n return false;\n }\n\n const parentKey = (context.keys.length > 0\n ? context.keys[context.keys.length - 1]\n : undefined\n );\n\n return (parentKey === 'constructor');\n\n }\n\n function isAllowedKey(key: string) {\n\n return (\n key !== '__proto__' &&\n !isConstructorPrototype(key)\n );\n\n }\n\n}\n\nfunction parseString(\n context: ParseContext\n\n): ParseResult {\n\n const { source, index } = context;\n\n let value = '';\n\n let i = (index + 1);\n\n while (i < source.length) {\n\n const char = source[i] as string;\n\n if (char === '\\\\') {\n\n const twoChars = source.substring(i, i + 2);\n const codepoint = codePoints[twoChars];\n\n if (codepoint) {\n value += codepoint;\n i += 2;\n\n } else if (twoChars === '\\\\u') {\n const charHex = source.substring(i + 2, i + 6);\n value += String.fromCharCode(parseInt(charHex, 16));\n i += 6;\n\n } else {\n throw new SyntaxError(\n `Unknown escape sequence: \"${twoChars}\"`\n );\n\n }\n\n } else if (char === '\"') {\n // End of string\n i++;\n break;\n\n } else {\n value += char;\n i++;\n\n }\n\n }\n\n return { value, endIndex: i };\n\n}\n\nfunction isNumberStart(char: string): boolean {\n\n return Boolean(char.match(/^(-|\\d)$/));\n\n}\n\nfunction parseNumber(\n context: ParseContext\n\n): ParseResult {\n\n const { source, index } = context;\n\n let isNegative = false;\n let integer = '0';\n let fraction = '';\n let isExponentNegative = false;\n let exponent = '';\n\n let i = index;\n\n // Parsing sign\n if (source[i] === '-') {\n isNegative = true;\n i++;\n }\n\n // Parsing integer part\n // -----\n\n if (source[i] === '0') {\n i++;\n\n } else if (source[i]!.match(/^[1-9]$/)) {\n\n integer = source[i]!;\n i++;\n\n while (i < source.length) {\n if (source[i]!.match(/^\\d$/)) {\n integer += source[i]!;\n i++;\n } else {\n break;\n }\n }\n\n } else {\n throw new SyntaxError(\n `Failed to parse number at position: ${i}`\n );\n\n }\n\n // Parsing fractional part\n // -----\n\n if (source[i] === '.') {\n\n i++;\n\n while (i < source.length) {\n if (source[i]!.match(/^\\d$/)) {\n fraction += source[i]!;\n i++;\n } else {\n break;\n }\n }\n\n }\n\n // Parsing exponent\n // -----\n\n if (['e', 'E'].includes(source[i]!)) {\n\n i++;\n\n if (source[i] === '+') {\n i++;\n } else if (source[i] === '-') {\n isExponentNegative = true;\n i++;\n }\n\n const exponentStartIndex = i;\n\n while (i < source.length) {\n if (source[i]!.match(/^\\d$/)) {\n exponent += source[i]!;\n i++;\n } else {\n break;\n }\n }\n\n if (exponent.length === 0) {\n throw new SyntaxError(\n `Failed to parse number's exponent value ` +\n `at position: ${exponentStartIndex}`\n );\n }\n\n }\n\n let value = Number(\n (isNegative ? '-' : '') + integer +\n (fraction ? `.${fraction}` : '') +\n (exponent ? `e${isExponentNegative ? '-' : ''}${exponent}` : '')\n );\n\n return {\n value,\n endIndex: i,\n };\n\n}\n\nfunction skipUntil(\n source: string,\n startIndex: number,\n endChar: (string | string[])\n\n): number {\n\n endChar = (Array.isArray(endChar) ? endChar : [endChar]);\n\n const i = skipSpaces(source, startIndex);\n\n const char = source[i] as string;\n\n if (endChar.includes(char)) {\n return i;\n\n } else {\n throw new SyntaxError(\n `Unexpected character: \"${char}\" ` +\n `at position: ${i}`\n );\n\n }\n\n}\n\nfunction skipSpaces(\n source: string,\n startIndex: number\n\n): number {\n\n let i: number;\n\n for (i = startIndex; i < source.length; i++) {\n\n const char = source[i] as string;\n\n if (!isWhitespace(char)) {\n break;\n }\n\n }\n\n return i;\n\n}\n\nfunction isWhitespace(char: string) {\n return [' ', '\\n', '\\r', '\\t'].includes(char);\n}\n\nfunction parseKeyword(\n context: ParseContext,\n keyword: Keyword,\n\n): ParseResult {\n\n const { source, index } = context;\n\n const endIndex = (index + keyword.length);\n\n const slice = source.substring(index, endIndex);\n\n if (slice !== keyword) {\n throw new SyntaxError(\n `Failed to parse value at position: ${index}`\n );\n }\n\n let value = (\n keyword === 'true' ? true :\n keyword === 'false' ? false :\n null\n );\n\n return {\n value,\n endIndex,\n };\n\n}\n\nfunction callReviver(args: {\n context: ParseContext,\n rawValue: string;\n value: any;\n\n}): any {\n\n const { context, rawValue, value } = args;\n const { reviver, keys } = context;\n\n if (!reviver) {\n return value;\n }\n\n const key = ((keys.length > 0)\n ? keys[keys.length - 1]!\n : ''\n );\n\n return reviver(key, value, {\n source: rawValue,\n keys,\n });\n\n}\n\n/**\n * A helper function that creates new parsing context\n * based on the previous one by advancing the reading index\n * and optionally adding a key to the list of keys.\n */\nfunction nextContext(\n context: ParseContext,\n nextIndex: number,\n nextKey?: string,\n\n): ParseContext {\n\n const newContext = {\n ...context,\n index: nextIndex,\n };\n\n if (nextKey) {\n newContext.keys = [\n ...context.keys,\n nextKey,\n ];\n }\n\n return newContext;\n\n}\n"], 5 | "mappings": ";AAuDA,IAAM,aAAqC;AAAA,EACzC,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACT;AAOO,SAAS,UACd,QACA,SACA,SAEM;AAEN,QAAM,SAAS,WAAW;AAAA,IACxB;AAAA,IACA,OAAO;AAAA,IACP,MAAM,CAAC;AAAA,IACP;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,WAAW,WAAW,QAAQ,OAAO,QAAQ;AAEnD,MAAI,YAAa,OAAO,SAAS,GAAI;AACnC,UAAM,IAAI;AAAA,MACR,uDACI,OAAO,UAAU,UAAU,WAAW,EAAE,yBAC5B;AAAA,IAClB;AAAA,EACF;AAEA,SAAO,OAAO;AAEhB;AAEA,SAAS,WAAW,SAAoC;AAEtD,QAAM,EAAE,QAAQ,MAAM,IAAI;AAE1B,MAAI;AAEJ,MAAI,IAAI,WAAW,QAAQ,KAAK;AAEhC,QAAM,aAAa,YAAY,SAAS,CAAC;AAEzC,MAAI,cAAc,OAAO,CAAC,CAAE,GAAG;AAE7B,aAAS,YAAY,UAAU;AAAA,EAEjC,OAAO;AAEL,YAAQ,OAAO,CAAC,GAAG;AAAA,MACjB,KAAK,KAAK;AACR,iBAAS,YAAY,UAAU;AAC/B;AAAA,MACF;AAAA,MACA,KAAK,KAAK;AACR,iBAAS,WAAW,UAAU;AAC9B;AAAA,MACF;AAAA,MACA,KAAK,KAAK;AACR,iBAAS,YAAY,UAAU;AAC/B;AAAA,MACF;AAAA,MACA,KAAK,KAAK;AACR,iBAAS,aAAa,YAAY,MAAM;AACxC;AAAA,MACF;AAAA,MACA,KAAK,KAAK;AACR,iBAAS,aAAa,YAAY,OAAO;AACzC;AAAA,MACF;AAAA,MACA,KAAK,KAAK;AACR,iBAAS,aAAa,YAAY,MAAM;AACxC;AAAA,MACF;AAAA,MACA,SAAS;AACP,cAAM,IAAI;AAAA,UACR,0BAA0B,OAAO,CAAC,mBAClB;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,EAEF;AAEA,SAAO,QAAQ,YAAY;AAAA,IACzB;AAAA,IACA,UAAU,OAAO,UAAU,GAAG,OAAO,QAAQ;AAAA,IAC7C,OAAO,OAAO;AAAA,EAChB,CAAC;AAED,SAAO;AAET;AAEA,SAAS,WAAW,SAAoC;AAEtD,QAAM,EAAE,QAAQ,MAAM,IAAI;AAE1B,QAAM,QAAe,CAAC;AAEtB,MAAI,IAAK,QAAQ;AACjB,MAAI,gBAAgB;AACpB,MAAI,eAAe;AAEnB,SAAO,IAAI,OAAO,QAAQ;AAExB,QAAI,WAAW,QAAQ,CAAC;AAExB,QAAI,OAAO,CAAC,MAAM,OAAO,CAAC,eAAe;AAEvC;AACA;AAAA,IACF;AAEA,UAAM,SAAS;AAAA,MACb,YAAY,SAAS,GAAG,aAAa,SAAS,CAAC;AAAA,IACjD;AAEA,UAAM,KAAK,OAAO,KAAK;AACvB,QAAI,OAAO;AACX,QAAI,UAAU,QAAQ,GAAG,CAAC,KAAK,GAAG,CAAC;AACnC,QAAI,OAAO,CAAC,MAAM,KAAK;AAErB,sBAAgB;AAChB;AACA;AAAA,IACF,WAAW,OAAO,CAAC,MAAM,KAAK;AAE5B;AACA;AAAA,IACF;AAAA,EAEF;AAEA,SAAO;AAAA,IACL,OAAO;AAAA,IACP,UAAU;AAAA,EACZ;AAEF;AAEA,SAAS,YAAY,SAAoC;AAEvD,QAAM,EAAE,QAAQ,MAAM,IAAI;AAE1B,MAAI,SAA8B,CAAC;AAEnC,MAAI,IAAK,QAAQ;AACjB,MAAI,gBAAgB;AAEpB,SAAO,IAAI,OAAO,QAAQ;AAExB,QAAI,UAAU,QAAQ,GAAG,CAAC,KAAK,GAAG,CAAC;AAEnC,QAAI,OAAO,CAAC,MAAM,OAAO,CAAC,eAAe;AAEvC;AACA;AAAA,IACF;AAGA,QAAI,SAAS;AAAA,MACX,YAAY,SAAS,CAAC;AAAA,IACxB;AACA,UAAM,MAAM,OAAO;AACnB,QAAI,OAAO;AACX,QAAI,UAAU,QAAQ,GAAG,GAAG,IAAI;AAIhC,QAAI,QAAQ,SAAS,cAAc;AACjC,UAAI,QAAQ,aAAa;AACvB,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF,WAAW,uBAAuB,GAAG,GAAG;AACtC,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,WAAW,QAAQ,CAAC;AACxB,aAAS;AAAA,MACP,YAAY,SAAS,GAAG,GAAG;AAAA,IAC7B;AACA,QAAI,OAAO,UAAU,UAAa,aAAa,GAAG,GAAG;AACnD,aAAO,GAAG,IAAI,OAAO;AAAA,IACvB;AACA,QAAI,OAAO;AACX,QAAI,UAAU,QAAQ,GAAG,CAAC,KAAK,GAAG,CAAC;AACnC,QAAI,OAAO,CAAC,MAAM,KAAK;AAErB,sBAAgB;AAChB;AAAA,IACF,WAAW,OAAO,CAAC,MAAM,KAAK;AAE5B;AACA;AAAA,IACF;AAAA,EAEF;AAEA,SAAO;AAAA,IACL,OAAO;AAAA,IACP,UAAU;AAAA,EACZ;AAOA,WAAS,uBAAuB,KAAa;AAE3C,QAAI,QAAQ,aAAa;AACvB,aAAO;AAAA,IACT;AAEA,UAAM,YAAa,QAAQ,KAAK,SAAS,IACrC,QAAQ,KAAK,QAAQ,KAAK,SAAS,CAAC,IACpC;AAGJ,WAAQ,cAAc;AAAA,EAExB;AAEA,WAAS,aAAa,KAAa;AAEjC,WACE,QAAQ,eACR,CAAC,uBAAuB,GAAG;AAAA,EAG/B;AAEF;AAEA,SAAS,YACP,SAEqB;AAErB,QAAM,EAAE,QAAQ,MAAM,IAAI;AAE1B,MAAI,QAAQ;AAEZ,MAAI,IAAK,QAAQ;AAEjB,SAAO,IAAI,OAAO,QAAQ;AAExB,UAAM,OAAO,OAAO,CAAC;AAErB,QAAI,SAAS,MAAM;AAEjB,YAAM,WAAW,OAAO,UAAU,GAAG,IAAI,CAAC;AAC1C,YAAM,YAAY,WAAW,QAAQ;AAErC,UAAI,WAAW;AACb,iBAAS;AACT,aAAK;AAAA,MAEP,WAAW,aAAa,OAAO;AAC7B,cAAM,UAAU,OAAO,UAAU,IAAI,GAAG,IAAI,CAAC;AAC7C,iBAAS,OAAO,aAAa,SAAS,SAAS,EAAE,CAAC;AAClD,aAAK;AAAA,MAEP,OAAO;AACL,cAAM,IAAI;AAAA,UACR,6BAA6B;AAAA,QAC/B;AAAA,MAEF;AAAA,IAEF,WAAW,SAAS,KAAK;AAEvB;AACA;AAAA,IAEF,OAAO;AACL,eAAS;AACT;AAAA,IAEF;AAAA,EAEF;AAEA,SAAO,EAAE,OAAO,UAAU,EAAE;AAE9B;AAEA,SAAS,cAAc,MAAuB;AAE5C,SAAO,QAAQ,KAAK,MAAM,UAAU,CAAC;AAEvC;AAEA,SAAS,YACP,SAEqB;AAErB,QAAM,EAAE,QAAQ,MAAM,IAAI;AAE1B,MAAI,aAAa;AACjB,MAAI,UAAU;AACd,MAAI,WAAW;AACf,MAAI,qBAAqB;AACzB,MAAI,WAAW;AAEf,MAAI,IAAI;AAGR,MAAI,OAAO,CAAC,MAAM,KAAK;AACrB,iBAAa;AACb;AAAA,EACF;AAKA,MAAI,OAAO,CAAC,MAAM,KAAK;AACrB;AAAA,EAEF,WAAW,OAAO,CAAC,EAAG,MAAM,SAAS,GAAG;AAEtC,cAAU,OAAO,CAAC;AAClB;AAEA,WAAO,IAAI,OAAO,QAAQ;AACxB,UAAI,OAAO,CAAC,EAAG,MAAM,MAAM,GAAG;AAC5B,mBAAW,OAAO,CAAC;AACnB;AAAA,MACF,OAAO;AACL;AAAA,MACF;AAAA,IACF;AAAA,EAEF,OAAO;AACL,UAAM,IAAI;AAAA,MACR,uCAAuC;AAAA,IACzC;AAAA,EAEF;AAKA,MAAI,OAAO,CAAC,MAAM,KAAK;AAErB;AAEA,WAAO,IAAI,OAAO,QAAQ;AACxB,UAAI,OAAO,CAAC,EAAG,MAAM,MAAM,GAAG;AAC5B,oBAAY,OAAO,CAAC;AACpB;AAAA,MACF,OAAO;AACL;AAAA,MACF;AAAA,IACF;AAAA,EAEF;AAKA,MAAI,CAAC,KAAK,GAAG,EAAE,SAAS,OAAO,CAAC,CAAE,GAAG;AAEnC;AAEA,QAAI,OAAO,CAAC,MAAM,KAAK;AACrB;AAAA,IACF,WAAW,OAAO,CAAC,MAAM,KAAK;AAC5B,2BAAqB;AACrB;AAAA,IACF;AAEA,UAAM,qBAAqB;AAE3B,WAAO,IAAI,OAAO,QAAQ;AACxB,UAAI,OAAO,CAAC,EAAG,MAAM,MAAM,GAAG;AAC5B,oBAAY,OAAO,CAAC;AACpB;AAAA,MACF,OAAO;AACL;AAAA,MACF;AAAA,IACF;AAEA,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,wDACgB;AAAA,MAClB;AAAA,IACF;AAAA,EAEF;AAEA,MAAI,QAAQ;AAAA,KACT,aAAa,MAAM,MAAM,WACzB,WAAW,IAAI,aAAa,OAC5B,WAAW,IAAI,qBAAqB,MAAM,KAAK,aAAa;AAAA,EAC/D;AAEA,SAAO;AAAA,IACL;AAAA,IACA,UAAU;AAAA,EACZ;AAEF;AAEA,SAAS,UACP,QACA,YACA,SAEQ;AAER,YAAW,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,OAAO;AAEtD,QAAM,IAAI,WAAW,QAAQ,UAAU;AAEvC,QAAM,OAAO,OAAO,CAAC;AAErB,MAAI,QAAQ,SAAS,IAAI,GAAG;AAC1B,WAAO;AAAA,EAET,OAAO;AACL,UAAM,IAAI;AAAA,MACR,0BAA0B,sBACV;AAAA,IAClB;AAAA,EAEF;AAEF;AAEA,SAAS,WACP,QACA,YAEQ;AAER,MAAI;AAEJ,OAAK,IAAI,YAAY,IAAI,OAAO,QAAQ,KAAK;AAE3C,UAAM,OAAO,OAAO,CAAC;AAErB,QAAI,CAAC,aAAa,IAAI,GAAG;AACvB;AAAA,IACF;AAAA,EAEF;AAEA,SAAO;AAET;AAEA,SAAS,aAAa,MAAc;AAClC,SAAO,CAAC,KAAK,MAAM,MAAM,GAAI,EAAE,SAAS,IAAI;AAC9C;AAEA,SAAS,aACP,SACA,SAEa;AAEb,QAAM,EAAE,QAAQ,MAAM,IAAI;AAE1B,QAAM,WAAY,QAAQ,QAAQ;AAElC,QAAM,QAAQ,OAAO,UAAU,OAAO,QAAQ;AAE9C,MAAI,UAAU,SAAS;AACrB,UAAM,IAAI;AAAA,MACR,sCAAsC;AAAA,IACxC;AAAA,EACF;AAEA,MAAI,QACF,YAAY,SAAS,OACrB,YAAY,UAAU,QACtB;AAGF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AAEF;AAEA,SAAS,YAAY,MAKb;AAEN,QAAM,EAAE,SAAS,UAAU,MAAM,IAAI;AACrC,QAAM,EAAE,SAAS,KAAK,IAAI;AAE1B,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,QAAM,MAAQ,KAAK,SAAS,IACxB,KAAK,KAAK,SAAS,CAAC,IACpB;AAGJ,SAAO,QAAQ,KAAK,OAAO;AAAA,IACzB,QAAQ;AAAA,IACR;AAAA,EACF,CAAC;AAEH;AAOA,SAAS,YACP,SACA,WACA,SAEc;AAEd,QAAM,aAAa;AAAA,IACjB,GAAG;AAAA,IACH,OAAO;AAAA,EACT;AAEA,MAAI,SAAS;AACX,eAAW,OAAO;AAAA,MAChB,GAAG,QAAQ;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAET;", 6 | "names": [] 7 | } 8 | -------------------------------------------------------------------------------- /packages/json-parser/dist/index.mjs.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "inputs": { 3 | "src/json-parser.ts": { 4 | "bytes": 10398, 5 | "imports": [], 6 | "format": "esm" 7 | }, 8 | "src/index.ts": { 9 | "bytes": 143, 10 | "imports": [ 11 | { 12 | "path": "src/json-parser.ts", 13 | "kind": "import-statement", 14 | "original": "./json-parser.js" 15 | } 16 | ], 17 | "format": "esm" 18 | } 19 | }, 20 | "outputs": { 21 | "dist/index.mjs.map": { 22 | "imports": [], 23 | "exports": [], 24 | "inputs": {}, 25 | "bytes": 17292 26 | }, 27 | "dist/index.mjs": { 28 | "imports": [], 29 | "exports": [ 30 | "parseJson" 31 | ], 32 | "entryPoint": "src/index.ts", 33 | "inputs": { 34 | "src/json-parser.ts": { 35 | "bytesInOutput": 7924 36 | }, 37 | "src/index.ts": { 38 | "bytesInOutput": 0 39 | } 40 | }, 41 | "bytes": 8005 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /packages/json-parser/dist/tsdoc-metadata.json: -------------------------------------------------------------------------------- 1 | // This file is read by tools that parse documentation comments conforming to the TSDoc standard. 2 | // It should be published with your NPM package. It should not be tracked by Git. 3 | { 4 | "tsdocVersion": "0.12", 5 | "toolPackages": [ 6 | { 7 | "packageName": "@microsoft/api-extractor", 8 | "packageVersion": "7.33.8" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/json-parser/esbuild.ts: -------------------------------------------------------------------------------- 1 | 2 | import { writeFile } from 'node:fs/promises'; 3 | 4 | import { build, BuildOptions } from 'esbuild'; 5 | 6 | import manifest from './package.json' assert { type: 'json' }; 7 | 8 | 9 | interface BuildDefinition { 10 | name: string; 11 | options: BuildOptions; 12 | } 13 | 14 | 15 | const builds: BuildDefinition[] = [ 16 | { 17 | name: 'cjs', 18 | options: { 19 | platform: 'node', 20 | format: 'cjs', 21 | outfile: 'dist/index.cjs', 22 | }, 23 | }, 24 | { 25 | name: 'esm', 26 | options: { 27 | platform: 'node', 28 | format: 'esm', 29 | outfile: 'dist/index.mjs', 30 | }, 31 | }, 32 | ]; 33 | 34 | 35 | for (const definition of builds) { 36 | 37 | console.log( 38 | `\n> BUILDING: ${definition.name}\n` 39 | ); 40 | 41 | const result = await build({ 42 | entryPoints: [ 43 | 'src/index.ts', 44 | ], 45 | bundle: true, 46 | tsconfig: 'tsconfig.lib.json', 47 | metafile: true, 48 | sourcemap: 'linked', 49 | define: { 50 | PACKAGE_NAME: `"${manifest.name}"`, 51 | PACKAGE_VERSION: `"${manifest.version}"`, 52 | }, 53 | plugins: [], 54 | ...definition.options, 55 | 56 | }).catch(error => { 57 | console.error(error); 58 | process.exit(1); 59 | 60 | }); 61 | 62 | const { 63 | warnings, 64 | errors, 65 | metafile, 66 | 67 | } = result; 68 | 69 | for (const message of errors) { 70 | console.error(message.location, message.text); 71 | } 72 | 73 | for (const message of warnings) { 74 | console.warn(message.location, message.text); 75 | } 76 | 77 | await writeFile( 78 | `${definition.options.outfile}.meta.json`, 79 | JSON.stringify(metafile, null, 4) 80 | ); 81 | 82 | } 83 | -------------------------------------------------------------------------------- /packages/json-parser/json-parser.api.md: -------------------------------------------------------------------------------- 1 | ## API Report File for "@ton.js/json-parser" 2 | 3 | > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). 4 | 5 | ```ts 6 | 7 | // @public (undocumented) 8 | export type Maybe = (Type | undefined); 9 | 10 | // @public (undocumented) 11 | export interface Options { 12 | // (undocumented) 13 | throwOnProto?: Maybe; 14 | } 15 | 16 | // @public 17 | export function parseJson(source: string, reviver?: Maybe, options?: Options): Type; 18 | 19 | // @public (undocumented) 20 | export interface ReviverContext { 21 | // (undocumented) 22 | keys: string[]; 23 | // (undocumented) 24 | source: string; 25 | } 26 | 27 | // @public (undocumented) 28 | export type ReviverFunc = (key: string, value: any, context: ReviverContext) => any; 29 | 30 | // (No @packageDocumentation comment for this package) 31 | 32 | ``` 33 | -------------------------------------------------------------------------------- /packages/json-parser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ton.js/json-parser", 3 | "version": "0.0.0-beta.1", 4 | "description": "Compatible and customizable JSON parser with better security", 5 | "license": "MIT", 6 | "type": "module", 7 | "main": "./dist/index.mjs", 8 | "types": "./dist/index.d.ts", 9 | "exports": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.cjs" 12 | }, 13 | "sideEffects": false, 14 | "scripts": { 15 | "test": "TS_NODE_PROJECT='tsconfig.test.json' mocha", 16 | "build": "run-s build:clean && run-p build:types build:esbuild", 17 | "build:clean": "rimraf ./dist/** ./temp", 18 | "build:esbuild": "ts-node ./esbuild.ts", 19 | "build:types": "run-s build:types:compile build:types:extract", 20 | "build:types:compile": "tsc -p ./tsconfig.types.json", 21 | "build:types:extract": "api-extractor run --local --verbose" 22 | }, 23 | "keywords": [ 24 | "json", 25 | "json-parse", 26 | "json-parser", 27 | "ponyfill", 28 | "ton.js" 29 | ], 30 | "homepage": "https://github.com/ton-js/json-parser#readme", 31 | "bugs": { 32 | "url": "https://github.com/ton-js/json-parser/issues" 33 | }, 34 | "author": { 35 | "name": "Slava Fomin II", 36 | "email": "slava@fomin.io", 37 | "url": "https://github.com/slavafomin" 38 | }, 39 | "engines": { 40 | "node": ">=14" 41 | }, 42 | "files": [ 43 | "dist/", 44 | "README.md" 45 | ], 46 | "publishConfig": { 47 | "access": "public", 48 | "tag": "beta" 49 | }, 50 | "devDependencies": { 51 | "@microsoft/api-extractor": "^7.33.8", 52 | "@tsconfig/node-lts-strictest-esm": "^18.12.1", 53 | "@types/chai": "^4.3.4", 54 | "@types/mocha": "^10.0.1", 55 | "@types/node": "^14.18.36", 56 | "chai": "^4.3.7", 57 | "esbuild": "^0.16.17", 58 | "mocha": "^10.2.0", 59 | "npm-run-all": "^4.1.5", 60 | "rimraf": "^3.0.2", 61 | "ts-node": "^10.9.1", 62 | "typescript": "^4.9.4" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/json-parser/src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export { parseJson } from './json-parser.js'; 3 | 4 | export type { 5 | Maybe, 6 | ReviverFunc, 7 | ReviverContext, 8 | Options, 9 | 10 | } from './json-parser.js'; 11 | -------------------------------------------------------------------------------- /packages/json-parser/src/json-parser.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { expect } from 'chai'; 3 | 4 | import { parseJson } from './json-parser.js'; 5 | 6 | 7 | type Tests = Array<[string, any]>; 8 | 9 | type NativeReviver = ( 10 | key: (string | undefined), 11 | value: any, 12 | 13 | ) => any; 14 | 15 | type JsonParseFunc = ( 16 | source: string, 17 | reviver: NativeReviver, 18 | 19 | ) => any; 20 | 21 | 22 | const FAIL = Symbol('FAIL'); 23 | 24 | const tests: Record = { 25 | numbers: [ 26 | ['0', 0], 27 | ['-0', -0], 28 | ['1234567890', 1234567890], 29 | ['-1234567890', -1234567890], 30 | ['1234567890.1234567890', 1234567890.1234567890], 31 | ['-1234567890.1234567890', -1234567890.1234567890], 32 | ['0.0', 0], 33 | ['123e9', 123000000000], 34 | ['123e+9', 123000000000], 35 | ['123e-9', 0.000000123], 36 | ['123E9', 123000000000], 37 | ['123E+9', 123000000000], 38 | ['123E-9', 0.000000123], 39 | ['123.456e9', 123456000000], 40 | ['123.456e+9', 123456000000], 41 | ['123.456e-9', 0.000000123456], 42 | ['123.456E9', 123456000000], 43 | ['123.456E+9', 123456000000], 44 | ['123.456E-9', 0.000000123456], 45 | ['+0', FAIL], 46 | ['.0', FAIL], 47 | ['0123', FAIL], 48 | ], 49 | strings: [ 50 | ['"hello"', 'hello'], 51 | ['" hello "', ' hello '], 52 | ['"hel\\"lo"', 'hel"lo'], 53 | ['"hel\\\\lo"', 'hel\\lo'], 54 | ['"hel\/lo"', 'hel/lo'], 55 | ['"hel\\blo"', 'hel\blo'], 56 | ['"hel\\flo"', 'hel\flo'], 57 | ['"hel\\nlo"', 'hel\nlo'], 58 | ['"hel\\rlo"', 'hel\rlo'], 59 | ['"hel\\tlo"', 'hel\tlo'], 60 | ['"[\\u0057, \\u042F, \\u2605, \\uffFd]"', '[W, Я, ★, �]'], 61 | [' "hello" ', 'hello'], 62 | ['\r"hello"\r', 'hello'], 63 | ['\n"hello"\n', 'hello'], 64 | ['\t"hello"\t', 'hello'], 65 | ], 66 | keywords: [ 67 | ['true', true], 68 | ['false', false], 69 | ['null', null], 70 | [' null ', null], 71 | ['\rnull\r', null], 72 | ['\nnull\n', null], 73 | ['\tnull\t', null], 74 | ], 75 | arrays: [ 76 | ['[]', []], 77 | ['[1, 2, 3]', [1, 2, 3]], 78 | [ 79 | '[-123.456e6,"hello",true,false,null]', 80 | [-123456000, 'hello', true, false, null] 81 | ], 82 | [ 83 | ' \r \n \t [ \r \n \t "hello" \r \n \t , \r \n \t "world" \r \n \t ] \r \n \t', 84 | ['hello', 'world'] 85 | ], 86 | ['["hello" "world"]', FAIL], 87 | ['["hello" 1]', FAIL], 88 | ['[true false]', FAIL], 89 | ['[,]', FAIL], 90 | ['[,,]', FAIL], 91 | ['[1, 2, 3,]', FAIL], 92 | ], 93 | objects: [ 94 | ['{ "foo": "bar" }', {foo:'bar'}], 95 | ['{ "foo": -123.456e6 }', {foo:-123456000}], 96 | [ 97 | '{ "foo": true, "bar": false, "qux": null }', 98 | { foo: true, bar: false, qux: null } 99 | ], 100 | ['{"compact":true,"foo":1}', { compact: true, foo: 1 }], 101 | ['{}', {}], 102 | ['{,}', FAIL], 103 | ['{,,}', FAIL], 104 | ['{:}', FAIL], 105 | ['{1:1}', FAIL], 106 | ['{foo:1}', FAIL], 107 | ['{ "foo": true, }', FAIL], 108 | ['{ "foo" true }', FAIL], 109 | ['{ "foo"::true }', FAIL], 110 | ['{ "foo", true: }', FAIL], 111 | [ 112 | '\r \n \t { \r \n \t "foo" \r \n \t : \r \n \t true \r \n \t , \r \n \t "bar": \r \n \t 1 \r \n\ \t } \r \n \t ', 113 | { foo: true, bar: 1 } 114 | ], 115 | ['{"foo":true,"__proto__":"hello"}', { foo: true }], 116 | ], 117 | nested: [ 118 | ['[[[[[]]]]]', [[[[[]]]]]], 119 | ['[[], []]', [[], []]], 120 | ['[1, [2], 3, [4], 5, [[0], [0]]]', [1, [2], 3, [4], 5, [[0], [0]]]], 121 | ['[{}, [[{}]], {}]', [{}, [[{}]], {}]], 122 | ['[{"foo": [1]}]', [{"foo": [1]}]], 123 | ['[{"foo": {"bar": [{}]}}]', [{"foo": {"bar": [{}]}}]], 124 | ], 125 | }; 126 | 127 | 128 | describe('parseJson()', () => { 129 | 130 | for (const [group, cases] of Object.entries(tests)) { 131 | 132 | describe(`[${group}]`, () => { 133 | 134 | for (const [input, expected] of cases) { 135 | 136 | it(`[${input}]`, () => { 137 | 138 | if (expected === FAIL) { 139 | (expect(() => parseJson(input)) 140 | .to.throw(SyntaxError) 141 | ); 142 | 143 | } else { 144 | expect(parseJson(input)).to.deep.equal(expected); 145 | 146 | } 147 | 148 | }); 149 | 150 | } 151 | 152 | }); 153 | 154 | } 155 | 156 | describe('proto properties', () => { 157 | 158 | it('should throw on __proto__', () => { 159 | 160 | const callable = () => ( 161 | parseJson('{ "__proto__": {} }', undefined, { 162 | throwOnProto: true, 163 | }) 164 | ); 165 | 166 | expect(callable).to.throw( 167 | SyntaxError, 168 | /Forbidden.*__proto__/ 169 | ); 170 | 171 | }); 172 | 173 | it('should remove __proto__', () => { 174 | 175 | const result = parseJson( 176 | '{ "foo": true, "__proto__": {}, "bar": { "__proto__": {} } }' 177 | ); 178 | 179 | expect(result).to.deep.equal({ 180 | foo: true, 181 | bar: {}, 182 | }); 183 | 184 | expect(result.__proto__).to.equal(Object.prototype); 185 | expect(result.bar.__proto__).to.equal(Object.prototype); 186 | 187 | }); 188 | 189 | it('should throw on constructor.prototype', () => { 190 | 191 | const callable = () => ( 192 | parseJson('{ "constructor": { "prototype": {} } }', undefined, { 193 | throwOnProto: true, 194 | }) 195 | ); 196 | 197 | expect(callable).to.throw( 198 | SyntaxError, 199 | /Forbidden.*constructor\.prototype/ 200 | ); 201 | 202 | }); 203 | 204 | it('should remove constructor.prototype', () => { 205 | 206 | const result = parseJson( 207 | '{ "foo": true, "constructor": { "prototype": {}, "baz": "hello" }, "bar": { "constructor": { "prototype": {} } } }' 208 | ); 209 | 210 | expect(result).to.deep.equal({ 211 | foo: true, 212 | constructor: { 213 | baz: 'hello', 214 | }, 215 | bar: { 216 | constructor: {}, 217 | }, 218 | }); 219 | 220 | expect(result.constructor.prototype).to.equal(undefined); 221 | expect(result.bar.constructor.prototype).to.equal(undefined); 222 | 223 | }); 224 | 225 | }); 226 | 227 | describe('reviver native behavior', () => { 228 | 229 | it('should delete undefined values', () => { 230 | 231 | const source = '{ "foo": true, "removeMe": 123, "arr": [{ "bar": 123, "removeMe": false }] }'; 232 | 233 | const result = parseJson(source, (key, value) => ( 234 | (key === 'removeMe' ? undefined : value) 235 | )); 236 | 237 | expect(result).to.deep.equal({ 238 | foo: true, 239 | arr: [{ 240 | bar: 123, 241 | }], 242 | }); 243 | 244 | }); 245 | 246 | it('should transform values', () => { 247 | 248 | const source = '{ "foo": true, "date": "1989-08-16T10:20:30.123Z", "num": 123 }'; 249 | 250 | const result = parseJson(source, (key, value) => ( 251 | (key === 'date' ? new Date(value) : value) 252 | )); 253 | 254 | expect(result).to.deep.equal({ 255 | foo: true, 256 | date: new Date(619266030123), 257 | num: 123, 258 | }); 259 | 260 | }); 261 | 262 | it('should be compatible with native implementation', () => { 263 | 264 | const source = '{"obj1":{"arr1":[{"obj2":[true,false,null,0,123,-123.456e3,123.456e-3,"\\rhello world\\n","[\\u0057, \\u042F, \\u2605, \\uffFd]",{"kwTrue":true,"kwFalse":false,"kwNull":null,"zero":0,"num1":123,"num2":-123.456e3,"num3":123.456e-3,"arr2":[1,2,true,false],"str1":"\\rhello world\\n","str2":"[\\u0057, \\u042F, \\u2605, \\uffFd]"}]}]}}'; 265 | 266 | const nativeCalls = runAndCaptureCalls( 267 | JSON.parse.bind(JSON) 268 | ); 269 | 270 | const libCalls = runAndCaptureCalls(parseJson); 271 | 272 | expect(libCalls).to.deep.equal(nativeCalls); 273 | 274 | 275 | function runAndCaptureCalls( 276 | parseFunc: JsonParseFunc 277 | 278 | ): any[] { 279 | 280 | const calls: any[] = []; 281 | 282 | parseFunc(source, (key, value) => { 283 | calls.push([key, value]); 284 | return value; 285 | }); 286 | 287 | return calls; 288 | 289 | } 290 | 291 | }); 292 | 293 | }); 294 | 295 | describe('reviver source text access proposal', () => { 296 | 297 | /** 298 | * {@link https://github.com/tc39/proposal-json-parse-with-source} 299 | */ 300 | 301 | it('should have access to source text', () => { 302 | 303 | const source = '{"str":"hello\\r\\n\\t\\u2605 world","num":-1.23456e-7,"kw1":true,"kw2":false,"kw3":null,"arr":[-1.23456e-7,"hello\\r\\n\\t\\u2605 world",true,false,null]}'; 304 | 305 | const expectedSources: Record = { 306 | root: source, 307 | str: '"hello\\r\\n\\t\\u2605 world"', 308 | num: '-1.23456e-7', 309 | kw1: 'true', 310 | kw2: 'false', 311 | kw3: 'null', 312 | arr: '[-1.23456e-7,"hello\\r\\n\\t\\u2605 world",true,false,null]', 313 | '0': '-1.23456e-7', 314 | '1': '"hello\\r\\n\\t\\u2605 world"', 315 | '2': 'true', 316 | '3': 'false', 317 | '4': 'null' 318 | }; 319 | 320 | const sources: Record = {}; 321 | 322 | parseJson(source, (key, value, context) => { 323 | sources[key || 'root'] = context.source; 324 | }); 325 | 326 | expect(sources).to.deep.equal(expectedSources); 327 | 328 | }); 329 | 330 | it('should have path of keys', () => { 331 | 332 | const source = '{"foo":[{"bar":"hello"}],"baz":{"qux":{"quux":"test","quuux":[1,2,3]}}}'; 333 | 334 | const expected: Record = { 335 | 'foo.0.bar': '"hello"', 336 | 'foo.0': '{"bar":"hello"}', 337 | 'foo': '[{"bar":"hello"}]', 338 | 'baz.qux.quux': '"test"', 339 | 'baz.qux.quuux.0': '1', 340 | 'baz.qux.quuux.1': '2', 341 | 'baz.qux.quuux.2': '3', 342 | 'baz.qux.quuux': '[1,2,3]', 343 | 'baz.qux': '{"quux":"test","quuux":[1,2,3]}', 344 | 'baz': '{"qux":{"quux":"test","quuux":[1,2,3]}}', 345 | '': source, 346 | }; 347 | 348 | const keyValues: Record = {}; 349 | 350 | parseJson(source, (key, value, context) => { 351 | expect(context.keys).to.be.an('array'); 352 | context.keys.forEach(key => 353 | expect(key).to.be.a('string') 354 | ); 355 | const path = context.keys.join('.'); 356 | keyValues[path] = context.source; 357 | }); 358 | 359 | expect(keyValues).to.deep.equal(expected); 360 | 361 | // Also checking order of keys 362 | (expect(Object.keys(keyValues)) 363 | .to.deep.equal(Object.keys(expected)) 364 | ); 365 | 366 | }); 367 | 368 | it('should parse big integers', () => { 369 | 370 | const source = '{ "foo": true, "big": 12345678901234567890, "num": 12345678901234567890 }'; 371 | 372 | const result = parseJson(source, (key, value, context) => ( 373 | (key === 'big' ? BigInt(context.source) : value) 374 | )); 375 | 376 | expect(result).to.deep.equal({ 377 | foo: true, 378 | big: 12345678901234567890n, 379 | num: 12345678901234567000, 380 | }); 381 | 382 | }); 383 | 384 | }); 385 | 386 | }); 387 | -------------------------------------------------------------------------------- /packages/json-parser/src/json-parser.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * {@link https://www.json.org/json-en.html | JSON specification} 4 | */ 5 | 6 | /** 7 | * @public 8 | */ 9 | export type Maybe = (Type | undefined); 10 | 11 | /** 12 | * @public 13 | */ 14 | export type ReviverFunc = ( 15 | key: string, 16 | value: any, 17 | context: ReviverContext 18 | 19 | ) => any; 20 | 21 | /** 22 | * @public 23 | */ 24 | export interface ReviverContext { 25 | source: string; 26 | keys: string[]; 27 | } 28 | 29 | /** 30 | * @public 31 | */ 32 | export interface Options { 33 | throwOnProto?: Maybe; 34 | } 35 | 36 | interface ParseContext { 37 | source: string; 38 | index: number; 39 | keys: string[]; 40 | reviver?: Maybe; 41 | options?: Maybe; 42 | } 43 | 44 | interface ParseResult { 45 | value: Type; 46 | endIndex: number; 47 | } 48 | 49 | type Keyword = ( 50 | | 'true' 51 | | 'false' 52 | | 'null' 53 | ); 54 | 55 | 56 | const codePoints: Record = { 57 | '\\"': '"', 58 | '\\\\': '\\', 59 | '\\/': '/', 60 | '\\b': '\b', 61 | '\\f': '\f', 62 | '\\n': '\n', 63 | '\\r': '\r', 64 | '\\t': '\t', 65 | }; 66 | 67 | /** 68 | * @public 69 | * 70 | * Parses JSON document and returns the parsed data. 71 | */ 72 | export function parseJson( 73 | source: string, 74 | reviver?: Maybe, 75 | options?: Options 76 | 77 | ): Type { 78 | 79 | const result = parseValue({ 80 | source, 81 | index: 0, 82 | keys: [], 83 | reviver, 84 | options, 85 | }); 86 | 87 | const endIndex = skipSpaces(source, result.endIndex); 88 | 89 | if (endIndex <= (source.length - 1)) { 90 | throw new SyntaxError( 91 | `Unexpected extra characters after the parsed data: ` + 92 | `"${source.substring(endIndex, endIndex + 16)}…" ` + 93 | `at position: ${endIndex}` 94 | ); 95 | } 96 | 97 | return result.value; 98 | 99 | } 100 | 101 | function parseValue(context: ParseContext): ParseResult { 102 | 103 | const { source, index } = context; 104 | 105 | let result: ParseResult; 106 | 107 | let i = skipSpaces(source, index); 108 | 109 | const newContext = nextContext(context, i); 110 | 111 | if (isNumberStart(source[i]!)) { 112 | 113 | result = parseNumber(newContext); 114 | 115 | } else { 116 | 117 | switch (source[i]) { 118 | case '"': { 119 | result = parseString(newContext); 120 | break; 121 | } 122 | case '[': { 123 | result = parseArray(newContext); 124 | break; 125 | } 126 | case '{': { 127 | result = parseObject(newContext); 128 | break; 129 | } 130 | case 't': { 131 | result = parseKeyword(newContext, 'true'); 132 | break; 133 | } 134 | case 'f': { 135 | result = parseKeyword(newContext, 'false'); 136 | break; 137 | } 138 | case 'n': { 139 | result = parseKeyword(newContext, 'null'); 140 | break; 141 | } 142 | default: { 143 | throw new SyntaxError( 144 | `Unexpected character: "${source[i]}" ` + 145 | `at position: ${i}` 146 | ); 147 | } 148 | } 149 | 150 | } 151 | 152 | result.value = callReviver({ 153 | context, 154 | rawValue: source.substring(i, result.endIndex), 155 | value: result.value, 156 | }); 157 | 158 | return result; 159 | 160 | } 161 | 162 | function parseArray(context: ParseContext): ParseResult { 163 | 164 | const { source, index } = context; 165 | 166 | const array: any[] = []; 167 | 168 | let i = (index + 1); 169 | let expectElement = false; 170 | let elementIndex = 0; 171 | 172 | while (i < source.length) { 173 | 174 | i = skipSpaces(source, i); 175 | 176 | if (source[i] === ']' && !expectElement) { 177 | // End of the array 178 | i++; 179 | break; 180 | } 181 | 182 | const result = parseValue( 183 | nextContext(context, i, elementIndex.toString()) 184 | ); 185 | 186 | array.push(result.value); 187 | i = result.endIndex; 188 | i = skipUntil(source, i, [',', ']']); 189 | if (source[i] === ',') { 190 | // Going to parse the next value 191 | expectElement = true; 192 | elementIndex++; 193 | i++; 194 | } else if (source[i] === ']') { 195 | // End of the array 196 | i++; 197 | break; 198 | } 199 | 200 | } 201 | 202 | return { 203 | value: array, 204 | endIndex: i, 205 | }; 206 | 207 | } 208 | 209 | function parseObject(context: ParseContext): ParseResult { 210 | 211 | const { source, index } = context; 212 | 213 | let object: Record = {}; 214 | 215 | let i = (index + 1); 216 | let expectKeypair = false; 217 | 218 | while (i < source.length) { 219 | 220 | i = skipUntil(source, i, ['"', '}']); 221 | 222 | if (source[i] === '}' && !expectKeypair) { 223 | // End of the object 224 | i++; 225 | break; 226 | } 227 | 228 | // Parsing the key 229 | let result = parseString( 230 | nextContext(context, i) 231 | ); 232 | const key = result.value; 233 | i = result.endIndex; 234 | i = skipUntil(source, i, ':') + 1; 235 | 236 | // Checking forbidden prototype property names and 237 | // throwing error if related option is set. 238 | if (context.options?.throwOnProto) { 239 | if (key === '__proto__') { 240 | throw new SyntaxError( 241 | `Forbidden object property name: "__proto__"` 242 | ); 243 | } else if (isConstructorPrototype(key)) { 244 | throw new SyntaxError( 245 | `Forbidden object property path: "constructor.prototype"` 246 | ); 247 | } 248 | } 249 | 250 | // Parsing value 251 | i = skipSpaces(source, i); 252 | result = parseValue( 253 | nextContext(context, i, key) 254 | ); 255 | if (result.value !== undefined && isAllowedKey(key)) { 256 | object[key] = result.value; 257 | } 258 | i = result.endIndex; 259 | i = skipUntil(source, i, [',', '}']); 260 | if (source[i] === ',') { 261 | // Going to parse the next keypair 262 | expectKeypair = true; 263 | i++; 264 | } else if (source[i] === '}') { 265 | // End of the object 266 | i++; 267 | break; 268 | } 269 | 270 | } 271 | 272 | return { 273 | value: object, 274 | endIndex: i, 275 | }; 276 | 277 | 278 | /** 279 | * Checks whether the key is part of "constructor.prototype" 280 | * path. 281 | */ 282 | function isConstructorPrototype(key: string) { 283 | 284 | if (key !== 'prototype') { 285 | return false; 286 | } 287 | 288 | const parentKey = (context.keys.length > 0 289 | ? context.keys[context.keys.length - 1] 290 | : undefined 291 | ); 292 | 293 | return (parentKey === 'constructor'); 294 | 295 | } 296 | 297 | function isAllowedKey(key: string) { 298 | 299 | return ( 300 | key !== '__proto__' && 301 | !isConstructorPrototype(key) 302 | ); 303 | 304 | } 305 | 306 | } 307 | 308 | function parseString( 309 | context: ParseContext 310 | 311 | ): ParseResult { 312 | 313 | const { source, index } = context; 314 | 315 | let value = ''; 316 | 317 | let i = (index + 1); 318 | 319 | while (i < source.length) { 320 | 321 | const char = source[i] as string; 322 | 323 | if (char === '\\') { 324 | 325 | const twoChars = source.substring(i, i + 2); 326 | const codepoint = codePoints[twoChars]; 327 | 328 | if (codepoint) { 329 | value += codepoint; 330 | i += 2; 331 | 332 | } else if (twoChars === '\\u') { 333 | const charHex = source.substring(i + 2, i + 6); 334 | value += String.fromCharCode(parseInt(charHex, 16)); 335 | i += 6; 336 | 337 | } else { 338 | throw new SyntaxError( 339 | `Unknown escape sequence: "${twoChars}"` 340 | ); 341 | 342 | } 343 | 344 | } else if (char === '"') { 345 | // End of string 346 | i++; 347 | break; 348 | 349 | } else { 350 | value += char; 351 | i++; 352 | 353 | } 354 | 355 | } 356 | 357 | return { value, endIndex: i }; 358 | 359 | } 360 | 361 | function isNumberStart(char: string): boolean { 362 | 363 | return Boolean(char.match(/^(-|\d)$/)); 364 | 365 | } 366 | 367 | function parseNumber( 368 | context: ParseContext 369 | 370 | ): ParseResult { 371 | 372 | const { source, index } = context; 373 | 374 | let isNegative = false; 375 | let integer = '0'; 376 | let fraction = ''; 377 | let isExponentNegative = false; 378 | let exponent = ''; 379 | 380 | let i = index; 381 | 382 | // Parsing sign 383 | if (source[i] === '-') { 384 | isNegative = true; 385 | i++; 386 | } 387 | 388 | // Parsing integer part 389 | // ----- 390 | 391 | if (source[i] === '0') { 392 | i++; 393 | 394 | } else if (source[i]!.match(/^[1-9]$/)) { 395 | 396 | integer = source[i]!; 397 | i++; 398 | 399 | while (i < source.length) { 400 | if (source[i]!.match(/^\d$/)) { 401 | integer += source[i]!; 402 | i++; 403 | } else { 404 | break; 405 | } 406 | } 407 | 408 | } else { 409 | throw new SyntaxError( 410 | `Failed to parse number at position: ${i}` 411 | ); 412 | 413 | } 414 | 415 | // Parsing fractional part 416 | // ----- 417 | 418 | if (source[i] === '.') { 419 | 420 | i++; 421 | 422 | while (i < source.length) { 423 | if (source[i]!.match(/^\d$/)) { 424 | fraction += source[i]!; 425 | i++; 426 | } else { 427 | break; 428 | } 429 | } 430 | 431 | } 432 | 433 | // Parsing exponent 434 | // ----- 435 | 436 | if (['e', 'E'].includes(source[i]!)) { 437 | 438 | i++; 439 | 440 | if (source[i] === '+') { 441 | i++; 442 | } else if (source[i] === '-') { 443 | isExponentNegative = true; 444 | i++; 445 | } 446 | 447 | const exponentStartIndex = i; 448 | 449 | while (i < source.length) { 450 | if (source[i]!.match(/^\d$/)) { 451 | exponent += source[i]!; 452 | i++; 453 | } else { 454 | break; 455 | } 456 | } 457 | 458 | if (exponent.length === 0) { 459 | throw new SyntaxError( 460 | `Failed to parse number's exponent value ` + 461 | `at position: ${exponentStartIndex}` 462 | ); 463 | } 464 | 465 | } 466 | 467 | let value = Number( 468 | (isNegative ? '-' : '') + integer + 469 | (fraction ? `.${fraction}` : '') + 470 | (exponent ? `e${isExponentNegative ? '-' : ''}${exponent}` : '') 471 | ); 472 | 473 | return { 474 | value, 475 | endIndex: i, 476 | }; 477 | 478 | } 479 | 480 | function skipUntil( 481 | source: string, 482 | startIndex: number, 483 | endChar: (string | string[]) 484 | 485 | ): number { 486 | 487 | endChar = (Array.isArray(endChar) ? endChar : [endChar]); 488 | 489 | const i = skipSpaces(source, startIndex); 490 | 491 | const char = source[i] as string; 492 | 493 | if (endChar.includes(char)) { 494 | return i; 495 | 496 | } else { 497 | throw new SyntaxError( 498 | `Unexpected character: "${char}" ` + 499 | `at position: ${i}` 500 | ); 501 | 502 | } 503 | 504 | } 505 | 506 | function skipSpaces( 507 | source: string, 508 | startIndex: number 509 | 510 | ): number { 511 | 512 | let i: number; 513 | 514 | for (i = startIndex; i < source.length; i++) { 515 | 516 | const char = source[i] as string; 517 | 518 | if (!isWhitespace(char)) { 519 | break; 520 | } 521 | 522 | } 523 | 524 | return i; 525 | 526 | } 527 | 528 | function isWhitespace(char: string) { 529 | return [' ', '\n', '\r', '\t'].includes(char); 530 | } 531 | 532 | function parseKeyword( 533 | context: ParseContext, 534 | keyword: Keyword, 535 | 536 | ): ParseResult { 537 | 538 | const { source, index } = context; 539 | 540 | const endIndex = (index + keyword.length); 541 | 542 | const slice = source.substring(index, endIndex); 543 | 544 | if (slice !== keyword) { 545 | throw new SyntaxError( 546 | `Failed to parse value at position: ${index}` 547 | ); 548 | } 549 | 550 | let value = ( 551 | keyword === 'true' ? true : 552 | keyword === 'false' ? false : 553 | null 554 | ); 555 | 556 | return { 557 | value, 558 | endIndex, 559 | }; 560 | 561 | } 562 | 563 | function callReviver(args: { 564 | context: ParseContext, 565 | rawValue: string; 566 | value: any; 567 | 568 | }): any { 569 | 570 | const { context, rawValue, value } = args; 571 | const { reviver, keys } = context; 572 | 573 | if (!reviver) { 574 | return value; 575 | } 576 | 577 | const key = ((keys.length > 0) 578 | ? keys[keys.length - 1]! 579 | : '' 580 | ); 581 | 582 | return reviver(key, value, { 583 | source: rawValue, 584 | keys, 585 | }); 586 | 587 | } 588 | 589 | /** 590 | * A helper function that creates new parsing context 591 | * based on the previous one by advancing the reading index 592 | * and optionally adding a key to the list of keys. 593 | */ 594 | function nextContext( 595 | context: ParseContext, 596 | nextIndex: number, 597 | nextKey?: string, 598 | 599 | ): ParseContext { 600 | 601 | const newContext = { 602 | ...context, 603 | index: nextIndex, 604 | }; 605 | 606 | if (nextKey) { 607 | newContext.keys = [ 608 | ...context.keys, 609 | nextKey, 610 | ]; 611 | } 612 | 613 | return newContext; 614 | 615 | } 616 | -------------------------------------------------------------------------------- /packages/json-parser/test/samples.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { dirname, join as pathJoin } from 'node:path'; 3 | import { readdir, readFile } from 'node:fs/promises'; 4 | import { fileURLToPath } from 'node:url'; 5 | 6 | import { expect } from 'chai'; 7 | 8 | import { parseJson } from '../src/index.js'; 9 | 10 | const __dirname = dirname(fileURLToPath(import.meta.url)); 11 | const samplesPath = pathJoin(__dirname, 'samples/'); 12 | 13 | const fileNames = await readdir(samplesPath); 14 | 15 | 16 | describe('test samples', () => { 17 | 18 | for (const filename of fileNames) { 19 | 20 | it(`${filename}`, async () => { 21 | const filePath = pathJoin(samplesPath, filename); 22 | const source = await readFile(filePath, 'utf-8'); 23 | const data1 = JSON.parse(source); 24 | const data2 = parseJson(source); 25 | expect(data1).to.deep.equal(data2); 26 | }); 27 | 28 | } 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /packages/json-parser/test/samples/blocks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "workchain": -1, 4 | "shard": "-9223372036854775808", 5 | "seqno": 26410546, 6 | "root_hash": "qiA5Dv5VJUcZDKBeaP7dHi4FuHhWMbO+HpdGU3P78LQ=", 7 | "file_hash": "8F0xYD1WRIe6fxSz0mSmHhQCqf5PazKzEirMExpz990=", 8 | "gen_utime": 1673195652, 9 | "start_lt": 34312319000000, 10 | "end_lt": 34312319000004 11 | }, 12 | { 13 | "workchain": 0, 14 | "shard": "-9223372036854775808", 15 | "seqno": 31969870, 16 | "root_hash": "5Lr7C6VLdYExtF7TDuQfZoJWzUI9UD4V3aizcRjxOCY=", 17 | "file_hash": "F+YcvS0hYy1LPQbHxW7oDzDF8gORf/tnhbXOeRpg3TQ=", 18 | "gen_utime": 1673195649, 19 | "start_lt": 34312318000000, 20 | "end_lt": 34312318000033 21 | }, 22 | { 23 | "workchain": -1, 24 | "shard": "-9223372036854775808", 25 | "seqno": 26410545, 26 | "root_hash": "gLeco6lb5eErNOALGq85jsFaXye4mQKNLOfymGFTnb8=", 27 | "file_hash": "+Z8x35Z0tSsbOB74Q9JicXkcLk1mljg6cJtPvfRzUao=", 28 | "gen_utime": 1673195649, 29 | "start_lt": 34312317000000, 30 | "end_lt": 34312317000004 31 | }, 32 | { 33 | "workchain": 0, 34 | "shard": "-9223372036854775808", 35 | "seqno": 31969869, 36 | "root_hash": "U15+LUb96uejw8aZblriOUdE4jiWeGKvoX3q/IXduPU=", 37 | "file_hash": "BgVzK/7GWviFBLqlLvttJS0Ax7IrXoCQR6R/gAclFdM=", 38 | "gen_utime": 1673195647, 39 | "start_lt": 34312317000000, 40 | "end_lt": 34312317000001 41 | }, 42 | { 43 | "workchain": -1, 44 | "shard": "-9223372036854775808", 45 | "seqno": 26410544, 46 | "root_hash": "NazT0iXycPDzoA/oV14eszHt+VZzuNdNvNpzLVwQ2Jo=", 47 | "file_hash": "tb5ZDjZvhNTD/kKeTjq1H+UdXhCPvdZs+2FjcK8nxEs=", 48 | "gen_utime": 1673195646, 49 | "start_lt": 34312316000000, 50 | "end_lt": 34312316000004 51 | }, 52 | { 53 | "workchain": 0, 54 | "shard": "-9223372036854775808", 55 | "seqno": 31969868, 56 | "root_hash": "ivBp1zyy+Vyt7cuQzko3qgWMtnT+BJ/pv/h+w5yfY2Q=", 57 | "file_hash": "d7r5m9WQwjJ05P55ueYB6I4rbcvWloLNW/Qi6aStcT8=", 58 | "gen_utime": 1673195643, 59 | "start_lt": 34312316000000, 60 | "end_lt": 34312316000008 61 | }, 62 | { 63 | "workchain": -1, 64 | "shard": "-9223372036854775808", 65 | "seqno": 26410543, 66 | "root_hash": "UtpuYyxiDRuLoWm0ho7A0XKchE0BD3+WFSW4Znok8nk=", 67 | "file_hash": "7hCv4vzSqbgDYz2UQN5v8ldWwCLtsCY1j/1g6QlNHEw=", 68 | "gen_utime": 1673195642, 69 | "start_lt": 34312315000000, 70 | "end_lt": 34312315000004 71 | }, 72 | { 73 | "workchain": 0, 74 | "shard": "-9223372036854775808", 75 | "seqno": 31969867, 76 | "root_hash": "Ifu1Y1ss4rlAFPPtNSEtIhtLx5DBL1gQB3NRALUOc9g=", 77 | "file_hash": "ta6ml/5tYqPHJOiivdwJX2hvzTB6VWpYxUpQOnwdBec=", 78 | "gen_utime": 1673195640, 79 | "start_lt": 34312315000000, 80 | "end_lt": 34312315000004 81 | }, 82 | { 83 | "workchain": 0, 84 | "shard": "-9223372036854775808", 85 | "seqno": 31969866, 86 | "root_hash": "o7+hHyRaCm9ultr3CdBXE1TN+Egjvm/zwAk4HKsjbco=", 87 | "file_hash": "Ay+cL5KMWU1qzkI/Auug2ka9gxm3khe2M+8YVhFiTHM=", 88 | "gen_utime": 1673195637, 89 | "start_lt": 34312314000000, 90 | "end_lt": 34312314000008 91 | }, 92 | { 93 | "workchain": -1, 94 | "shard": "-9223372036854775808", 95 | "seqno": 26410542, 96 | "root_hash": "zPbxL1uJIQkRhFXBtU1/PgXWe2uAq5KezLO/BjZCuts=", 97 | "file_hash": "cifIJlHf2ddkWhKDStrlk2dPigYIVeI/C5uhvkh4oo4=", 98 | "gen_utime": 1673195637, 99 | "start_lt": 34312314000000, 100 | "end_lt": 34312314000004 101 | }, 102 | { 103 | "workchain": 0, 104 | "shard": "-9223372036854775808", 105 | "seqno": 31969865, 106 | "root_hash": "SgueBxRSlYh930ibPSFf6bqoLQyl32unZ8dlB3BVKDU=", 107 | "file_hash": "uyX3Dz6HRzy/8TFriVwCpIfHBel2fO/7uJjGqCk2rCk=", 108 | "gen_utime": 1673195634, 109 | "start_lt": 34312313000000, 110 | "end_lt": 34312313000006 111 | }, 112 | { 113 | "workchain": -1, 114 | "shard": "-9223372036854775808", 115 | "seqno": 26410541, 116 | "root_hash": "ytzmf4+6FrzKABR9z9t1a/zpxtEAl3ZroSc5qNh5RDI=", 117 | "file_hash": "LWjFq5hLJeYUPV1w4PRep60ltRUB2pWQGJ1CzmyJQKE=", 118 | "gen_utime": 1673195634, 119 | "start_lt": 34312313000000, 120 | "end_lt": 34312313000004 121 | }, 122 | { 123 | "workchain": 0, 124 | "shard": "-9223372036854775808", 125 | "seqno": 31969864, 126 | "root_hash": "KdIcHFWXu6DPkpGWP3h4Nv+tMJjwRpW/Qq/1i90e75Q=", 127 | "file_hash": "v4dUnPc4Pa8b6f/5ExTRfZ38oMJQRpA12/usd/SSJj8=", 128 | "gen_utime": 1673195632, 129 | "start_lt": 34312312000000, 130 | "end_lt": 34312312000012 131 | }, 132 | { 133 | "workchain": -1, 134 | "shard": "-9223372036854775808", 135 | "seqno": 26410540, 136 | "root_hash": "GW1YOgZqpVcuZrA5U/Kocf8ktxUUQl17Cf8zcvvZqJU=", 137 | "file_hash": "NxAoexB9uTdcRjPMkvl5hXmEg6e1+/BgxLp/ymbCF6k=", 138 | "gen_utime": 1673195631, 139 | "start_lt": 34312312000000, 140 | "end_lt": 34312312000004 141 | }, 142 | { 143 | "workchain": 0, 144 | "shard": "-9223372036854775808", 145 | "seqno": 31969863, 146 | "root_hash": "7Lphu7uuQ7AA4Rhy8Kb6J3rD4yJJJEoJoS4+d4BNkes=", 147 | "file_hash": "E0R1iFv2ewlL1+AltxDXSsv9vTUTb58V4bYv2ygAc7s=", 148 | "gen_utime": 1673195629, 149 | "start_lt": 34312311000000, 150 | "end_lt": 34312311000007 151 | }, 152 | { 153 | "workchain": -1, 154 | "shard": "-9223372036854775808", 155 | "seqno": 26410539, 156 | "root_hash": "Hze2BViWqgePriEFwqcHTzMJUsWQg4R4WQ5lHy6Show=", 157 | "file_hash": "Q0qRxBlKNy5Toqed1Pgs7HA/wGLkCI6fCOIvqytdd/4=", 158 | "gen_utime": 1673195628, 159 | "start_lt": 34312311000000, 160 | "end_lt": 34312311000004 161 | }, 162 | { 163 | "workchain": 0, 164 | "shard": "-9223372036854775808", 165 | "seqno": 31969862, 166 | "root_hash": "FJfKkesn+2s1YxK1jJsM+cfI9C3toXiC97f7SK1uknY=", 167 | "file_hash": "axvpl4errKlLynYgdlNtQsxoJ5UScNnetxxhconp9B4=", 168 | "gen_utime": 1673195626, 169 | "start_lt": 34312310000000, 170 | "end_lt": 34312310000037 171 | }, 172 | { 173 | "workchain": -1, 174 | "shard": "-9223372036854775808", 175 | "seqno": 26410538, 176 | "root_hash": "njiLlEMVaARto4csc+9DBTKhu0xrmNv4kU2e2M2BbKY=", 177 | "file_hash": "cwiRStCwasZXdFelb/SjBuCXyLnhQSONrj6xIqS10TE=", 178 | "gen_utime": 1673195625, 179 | "start_lt": 34312310000000, 180 | "end_lt": 34312310000004 181 | }, 182 | { 183 | "workchain": 0, 184 | "shard": "-9223372036854775808", 185 | "seqno": 31969861, 186 | "root_hash": "qE/QRTYs8fxFJEhevcx6alIRRx1bUPa8a/S8u98n9y0=", 187 | "file_hash": "Ek/N45o24wULNtHmB5NWpDMW6zQk6tioY7Rtv1VAfsI=", 188 | "gen_utime": 1673195622, 189 | "start_lt": 34312309000000, 190 | "end_lt": 34312309000012 191 | }, 192 | { 193 | "workchain": -1, 194 | "shard": "-9223372036854775808", 195 | "seqno": 26410537, 196 | "root_hash": "TrH6YUqAr28Owrgd1ZM5H9JD8NmcJFEGrkmdWezlABI=", 197 | "file_hash": "+BE0N+K8UbJTBC8E5XFuqVy6R08rkFFb7tL+T3P6kZ8=", 198 | "gen_utime": 1673195622, 199 | "start_lt": 34312309000000, 200 | "end_lt": 34312309000004 201 | } 202 | ] 203 | -------------------------------------------------------------------------------- /packages/json-parser/test/samples/super-json.json: -------------------------------------------------------------------------------- 1 | {"obj1":{"arr1":[{"obj2":[true,false,null,0,123,-123.456e3,123.456e-3,"\\rhello world\\n","[\\\u0057, \\\u042F, \\\u2605, \\\uffFd]",{"kwTrue":true,"kwFalse":false,"kwNull":null,"zero":0,"num1":123,"num2":-123.456e3,"num3":123.456e-3,"arr2":[1,2,true,false],"str1":"\\rhello world\\n","str2":"[\\\u0057, \\\u042F, \\\u2605, \\\uffFd]"}]}]}} 2 | -------------------------------------------------------------------------------- /packages/json-parser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.common.json", 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "resolveJsonModule": true, 7 | }, 8 | "files": [ 9 | "esbuild.ts" 10 | ], 11 | "ts-node": { 12 | "esm": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/json-parser/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.common.json", 4 | "include": [ 5 | "src/" 6 | ], 7 | "exclude": [ 8 | "**/*.test.ts" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/json-parser/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.common.json", 4 | "compilerOptions": { 5 | }, 6 | "include": [ 7 | "src/", 8 | "test/" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/json-parser/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.common.json", 4 | "compilerOptions": { 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "declarationMap": true, 8 | "declarationDir": "temp/types", 9 | "isolatedModules": true 10 | }, 11 | "include": [ 12 | "src/" 13 | ], 14 | "exclude": [ 15 | "**/*.test.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | 2 | packages: 3 | - 'benchmark' 4 | - 'examples/*' 5 | - 'packages/*' 6 | - 'readme' 7 | -------------------------------------------------------------------------------- /readme/build.ts: -------------------------------------------------------------------------------- 1 | 2 | import { readFile, writeFile } from 'node:fs/promises'; 3 | import { dirname, join as pathJoin } from 'node:path'; 4 | import { fileURLToPath } from 'node:url'; 5 | 6 | 7 | import nunjucks from 'nunjucks'; 8 | import * as cheerio from 'cheerio'; 9 | import { minify as minifyHtml } from 'html-minifier-terser'; 10 | 11 | import { remark } from 'remark'; 12 | import remarkToc from 'remark-toc'; 13 | import remarkValidateLinks from 'remark-validate-links'; 14 | 15 | import manifest from '../packages/json-parser/package.json' assert { type: 'json' }; 16 | 17 | 18 | const __dirname = dirname( 19 | fileURLToPath(import.meta.url) 20 | ); 21 | 22 | const rootPath = pathJoin(__dirname, '/..'); 23 | const templatePath = pathJoin(__dirname, 'src/index.md'); 24 | const outputPath = pathJoin(__dirname, '../README.md'); 25 | 26 | 27 | console.log('Building the README file…'); 28 | 29 | console.log('Compiling nunjucks template…'); 30 | 31 | const benchmarks = await loadBenchmarks(); 32 | 33 | let source = nunjucks.render(templatePath, { 34 | packageName: manifest.name, 35 | copyright: { 36 | years: renderYears(2023), 37 | entity: '💎 TON FOUNDATION', 38 | }, 39 | benchmarks, 40 | }); 41 | 42 | console.log('Applying remark transforms…'); 43 | 44 | const processor = (remark() 45 | .use(remarkToc, { 46 | heading: 'Contents', 47 | tight: true, 48 | skip: '.*License.*', 49 | }) 50 | .use(remarkValidateLinks) 51 | ); 52 | 53 | const result = await processor.process(source); 54 | 55 | result.messages.forEach(message => console.log(message)); 56 | 57 | source = result.toString(); 58 | 59 | await writeFile(outputPath, source, 'utf-8'); 60 | 61 | console.log(`Written README file to:\n${outputPath}`); 62 | 63 | 64 | function renderYears(startYear: number): string { 65 | const currentYear = new Date().getFullYear(); 66 | return ((currentYear === startYear) 67 | ? currentYear.toString() 68 | : `${startYear}—${currentYear}` 69 | ); 70 | } 71 | 72 | async function loadBenchmarks(): Promise { 73 | 74 | let source = ''; 75 | 76 | source += await loadBenchmark({ 77 | name: 'normal-dataset', 78 | title: 'Normal dataset', 79 | }); 80 | 81 | source += '\n\n'; 82 | 83 | source += await loadBenchmark({ 84 | name: 'big-dataset', 85 | title: 'Big dataset', 86 | }); 87 | 88 | return source; 89 | 90 | } 91 | 92 | async function loadBenchmark(args: { 93 | name: string; 94 | title: string; 95 | 96 | }): Promise { 97 | 98 | const filePath = pathJoin( 99 | rootPath, 100 | 'benchmark/results', 101 | `${args.name}.table.html` 102 | ); 103 | 104 | const $ = cheerio.load( 105 | await readFile(filePath, 'utf-8') 106 | ); 107 | 108 | let tableContent = $.html($('table')); 109 | if (!tableContent) { 110 | throw new Error(`Failed to parse out of benchmark content`); 111 | } 112 | 113 | tableContent = await minifyHtml(tableContent, { 114 | collapseWhitespace: true, 115 | }); 116 | 117 | return `### ${args.title}\n\n${tableContent}`; 118 | 119 | } 120 | -------------------------------------------------------------------------------- /readme/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "readme", 3 | "type": "module", 4 | "private": true, 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "ts-node ./build.ts" 8 | }, 9 | "repository": "https://github.com/ton-js/json-parser", 10 | "devDependencies": { 11 | "@types/html-minifier-terser": "^7.0.0", 12 | "@types/node": "^18.11.18", 13 | "@types/nunjucks": "^3.2.1", 14 | "ts-node": "^10.9.1", 15 | "typescript": "^4.9.4" 16 | }, 17 | "dependencies": { 18 | "benchmark": "workspace:*", 19 | "cheerio": "1.0.0-rc.12", 20 | "html-minifier-terser": "^7.1.0", 21 | "nunjucks": "^3.2.3", 22 | "remark": "^14.0.2", 23 | "remark-toc": "^8.0.1", 24 | "remark-validate-links": "^12.1.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /readme/src/index.md: -------------------------------------------------------------------------------- 1 | 2 | Type definitionsLicense 3 |
4 | npm (scoped with tag)node-lts (scoped with tag)npm bundle size (scoped) 5 |
6 | GitHub issuesGitHub pull requestsGitHub last commit 7 |
8 | Telegram 9 |
10 | 11 | # 💎 {{ packageName }} 12 | 13 | A customizable JSON parser that is 100% compatible 14 | with native implementation (`JSON.parse()`), 15 | can be used as a [polyfill](#usage-polyfill), 16 | adds TC39 [source access proposal][tc39-proposal] and additional features, 17 | have better security like [secure-json-parse][secure-json-parse] 18 | and is… 25 times slower… 19 | 20 | 21 | ## Rationale 22 | 23 | The native implementation of JSON parser in JavaScript 24 | (i.e. `JSON.parse()`) doesn't allow to fully customize the 25 | parsing behavior. The [JSON specification][json-standard] allows 26 | documents to include numbers of arbitrary and unlimited size. 27 | However, EcmaScript is using IEEE 754 standard to represent 28 | numbers internally and doesn't support numbers of the unlimited 29 | size. This leads to the data loss when `JSON.parse()` is 30 | attempting to read big numbers from the JSON documents. 31 | 32 | Modern versions of JavaScript support a special BigInt data 33 | type specifically designed to represent integer numbers of 34 | unlimited size, but there is no way to tell `JSON.parse()` 35 | to use `BigInt` for parsing numbers. 36 | 37 | Web 3.0 community tends to use very big numbers to represent 38 | cryptocurrency values, like the number of nanocoins in transaction. 39 | This library was designed as a workaround that allows to read 40 | big integer numbers from the JSON documents. However, considering 41 | that it's a full-fledged customized JSON parser you can use it 42 | for other cases as well. 43 | 44 | 45 | ## Contents 46 | 47 | 48 | 49 | ## Features 50 | 51 | - modern cross-platform multi-format package that can be 52 | used in any JavaScript environment, 53 | 54 | - written in pure super-strict TypeScript with 100% type coverage, 55 | 56 | - minimum size package with zero dependencies, 57 | 58 | - robust [security practices][security-policy], 59 | 60 | - 100% compatible with the [JSON standard][json-standard] 61 | and native `JSON.parse()` implementation, 62 | can be used as a [polyfill](#usage-polyfill), 63 | 64 | - future-compatible by implementing the Stage-3 65 | TC39 [source access proposal][tc39-proposal] and additional 66 | features, 67 | 68 | - adds special handling for `__proto__` and `constructor.prototype` 69 | object properties to implement better security, 70 | 71 | - extensively covered by [unit tests][tests] and tested out 72 | on multiple real-life [JSON samples][test-samples], 73 | 74 | - parses `1 MB` of nested JSON in `~40 ms`. 75 | (25 times slower than native implementation). 76 | 77 | 78 | {% include "./usage-ponyfill.md" %} 79 | 80 | {% include "./usage-polyfill.md" %} 81 | 82 | {% include "./usage-rules.md" %} 83 | 84 | 85 | ## Benchmarks 86 | 87 | {{ benchmarks | safe }} 88 | 89 | 90 | ## Security 91 | 92 | Please see our [security policy][security-policy]. 93 | 94 | 95 | ## Contributing 96 | 97 | Want to help? Please see the [contributing guide][contributing]. 98 | 99 | 100 | ## Support 101 | 102 | If you have any questions regarding this library or 103 | TON development in general — feel free to join our official 104 | [TON development][tondev-chat] Telegram group. 105 | 106 | 107 | {% include "./license.md" %} 108 | 109 | 110 | 111 | [contributing]: ./CONTRIBUTING.md 112 | [test-samples]: ./packages/json-parser/test/samples 113 | [tests]: ./packages/json-parser/src/json-parser.test.ts 114 | [examples-ponyfill]: ./examples/node-esm 115 | [examples-polyfill]: ./examples/node-esm-polyfill 116 | [examples-rules]: ./examples/node-esm-rules 117 | 118 | [security-policy]: https://github.com/ton-js/json-parser/security/policy 119 | [json-standard]: https://www.json.org/json-en.html 120 | [secure-json-parse]: https://github.com/fastify/secure-json-parse 121 | [tc39-proposal]: https://github.com/tc39/proposal-json-parse-with-source 122 | [tondev-chat]: https://t.me/tondev_eng 123 | -------------------------------------------------------------------------------- /readme/src/license.md: -------------------------------------------------------------------------------- 1 | 2 | ## The MIT License (MIT) 3 | 4 | Copyright © {{ copyright.years }} {{ copyright.entity }} 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons 12 | to whom the Software is furnished to do so, subject to the 13 | following conditions: 14 | 15 | The above copyright notice and this permission notice shall 16 | be included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 25 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /readme/src/usage-polyfill.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Polyfill usage 4 | 5 | > The polyfill implements the Stage-3 TC39 6 | > [source access proposal][tc39-proposal] and adds some 7 | > additional useful features. 8 | 9 | Polyfill is a package that when globally imported, overrides 10 | the behavior of the native `JSON.parse()` method. The polyfill 11 | will detect the number of arguments that your reviver function 12 | has and will use custom JSON parser implementation only when 13 | it has three parameters (i.e. `context`), in other case the 14 | native implementation will be used instead. 15 | 16 | Please see the [polyfill examples project][examples-polyfill] 17 | for the complete examples. 18 | 19 | > Be advised, that polyfill can't detect if you are using 20 | > `arguments[2]` to access the `context`, so make sure to 21 | > use a normal function parameter. 22 | 23 | ### Prerequisites 24 | 25 | 1). Install the polyfill package: 26 | 27 | ```shell 28 | npm install --save @ton.js/json-parse-polyfill 29 | ``` 30 | 31 | 2). Import the package only once as close to the 32 | beginning of your program as possible: 33 | 34 | ```typescript 35 | import '@ton.js/json-parse-polyfill'; 36 | 37 | (function main() { 38 | // … 39 | })(); 40 | ``` 41 | 42 | ### Simple parsing 43 | 44 | ```ts 45 | interface DocumentType { 46 | // … 47 | } 48 | 49 | // Native implementation will be used 50 | const object = JSON.parse('{ … }'); 51 | ``` 52 | 53 | ### Using native reviver 54 | 55 | ```ts 56 | interface DocumentType { 57 | birthDate: Date; 58 | } 59 | 60 | const content = '{ "birthDate": "1989-08-16T10:20:30.123Z" }'; 61 | 62 | // Native implementation will be used 63 | const object = JSON.parse(content, (key, value) => ( 64 | (key.endsWith('Date') ? new Date(value) : value) 65 | )); 66 | 67 | assert(object.birthDate instanceof Date); 68 | ``` 69 | 70 | ### Using reviver with source text access 71 | 72 | ```ts 73 | import type { ReviverFunc } from '@ton.js/json-parse-polyfill'; 74 | 75 | interface DocumentType { 76 | valueBN: bigint; 77 | } 78 | 79 | const content = '{ "valueBN": 12345678901234567890 }'; 80 | 81 | // Custom implementation will be used 82 | const reviver: ReviverFunc = (key, value, context) => ( 83 | (key.endsWith('BN') ? BigInt(context.source) : value) 84 | ); 85 | 86 | const object = ( 87 | JSON.parse(content, reviver as any) 88 | ); 89 | 90 | assert.equal(typeof object.valueBN, 'bigint'); 91 | ``` 92 | 93 | ### Using key path 94 | 95 | ```ts 96 | import type { ReviverFunc } from '@ton.js/json-parse-polyfill'; 97 | 98 | interface DocumentType { 99 | foo: { 100 | bar: { 101 | value: bigint; 102 | }; 103 | }; 104 | } 105 | 106 | const content = '{ "foo": { "bar": { "value": 12345678901234567890 } } }'; 107 | 108 | const reviver: ReviverFunc = (key, value, context) => ( 109 | ((context.keys.join('.') === 'foo.bar.value') 110 | ? BigInt(context.source) 111 | : value 112 | ) 113 | ); 114 | 115 | // Custom implementation will be used 116 | const object = ( 117 | JSON.parse(content, reviver as any) 118 | ); 119 | 120 | assert.equal(typeof object.foo.bar.value, 'bigint'); 121 | ``` 122 | -------------------------------------------------------------------------------- /readme/src/usage-ponyfill.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Normal usage (ponyfill) 4 | 5 | > Ponyfill is a side effects free package that provides 6 | > an alternative implementation and doesn't affect the native 7 | > behavior of the system. 8 | 9 | Install the package first: 10 | 11 | ```sh 12 | npm install --save @ton.js/json-parser 13 | ``` 14 | 15 | Please see the [examples project][examples-ponyfill] 16 | for the complete examples. 17 | 18 | ### Simple parsing 19 | 20 | ```ts 21 | import { parseJson } from '@ton.js/json-parser'; 22 | 23 | interface DocumentType { 24 | // … 25 | } 26 | 27 | const object = parseJson('{ … }'); 28 | // object type will be: DocumentType 29 | ``` 30 | 31 | ### Using native reviver 32 | 33 | ```ts 34 | interface DocumentType { 35 | birthDate: Date; 36 | } 37 | 38 | const content = '{ "birthDate": "1989-08-16T10:20:30.123Z" }'; 39 | 40 | const object = parseJson(content, (key, value) => ( 41 | (key.endsWith('Date') ? new Date(value) : value) 42 | )); 43 | 44 | assert(object.birthDate instanceof Date); 45 | ``` 46 | 47 | ### Using reviver with source text access 48 | 49 | ```ts 50 | interface DocumentType { 51 | valueBN: bigint; 52 | } 53 | 54 | const content = '{ "valueBN": 12345678901234567890 }'; 55 | 56 | const object = parseJson(content, (key, value, context) => ( 57 | (key.endsWith('BN') ? BigInt(context.source) : value) 58 | )); 59 | 60 | assert.equal(typeof object.valueBN, 'bigint'); 61 | ``` 62 | 63 | ### Using key path 64 | 65 | ```ts 66 | interface DocumentType { 67 | foo: { 68 | bar: { 69 | value: bigint; 70 | }; 71 | }; 72 | } 73 | 74 | const content = '{ "foo": { "bar": { "value": 12345678901234567890 } } }'; 75 | 76 | const object = parseJson(content, (key, value, context) => ( 77 | (context.keys.join('.') === 'foo.bar.value' ? BigInt(context.source) : value) 78 | )); 79 | 80 | assert.equal(typeof object.foo.bar.value, 'bigint'); 81 | ``` 82 | 83 | ### Throwing on prototypes 84 | 85 | In order to prevent parsed object prototype override the 86 | JSON parser will automatically skip `__proto__` and 87 | `constructor.prototype` properties. However, you can use 88 | the `throwOnProto: true` option to make this behavior more 89 | explicit — the parser will throw an error instead: 90 | 91 | ```ts 92 | const content = '{ "foo": true, "__proto__": {} }'; 93 | 94 | try { 95 | parseJson(content, undefined, { 96 | throwOnProto: true, 97 | }); 98 | 99 | } catch (error: any) { 100 | // Forbidden object property name: "__proto__" 101 | console.log(error); 102 | 103 | } 104 | ``` 105 | -------------------------------------------------------------------------------- /readme/src/usage-rules.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Parsing by rules 4 | 5 | A dedicated package is provided to support JSON 6 | deserialization using a special rules syntax. This 7 | simplifies writing custom revivers for your data types. 8 | 9 | 10 | ### Prerequisites 11 | 12 | > This package requires a `JSON.parse` implementation that 13 | supports TC39 [source access proposal][tc39-proposal] and 14 | additional features. You can either 15 | [install the polyfill](#usage-polyfill) 16 | or specify the parser function directly via the options. 17 | 18 | Install the package: 19 | 20 | ```sh 21 | npm install --save @ton.js/json-parser-rules 22 | ``` 23 | 24 | ### Example 25 | 26 | Please see the [parse by rules example project][examples-rules] 27 | for the complete examples. 28 | 29 | ```ts 30 | import '@ton.js/json-parse-polyfill'; 31 | 32 | import { strict as assert } from 'node:assert'; 33 | 34 | import type { Reviver } from '@ton.js/json-parser-rules'; 35 | import { parseJsonSchema } from '@ton.js/json-parser-rules'; 36 | 37 | 38 | //===============// 39 | // JSON DOCUMENT // 40 | //===============// 41 | 42 | const content = (` 43 | { 44 | "foo": { 45 | "hex": { 46 | "value": "416C6C20796F75206E656564206973206C6F7665" 47 | }, 48 | "bar": { 49 | "baz": { 50 | "valueBig": 11145678901234567890, 51 | "hex": "466f72204169757221" 52 | } 53 | }, 54 | "feeBig": 22245678901234567890, 55 | "myArray": [0, 1, "2023-01-20T19:30:45.904Z", 3, { 56 | "hex": { 57 | "value": "616c6c20796f75722062617365206172652062656c6f6e6720746f207573" 58 | } 59 | }] 60 | } 61 | } 62 | `); 63 | 64 | 65 | //==================// 66 | // RESULT INTERFACE // 67 | //==================// 68 | 69 | interface ParseResult { 70 | foo: { 71 | hex: HexBag; 72 | bar: { 73 | baz: { 74 | valueBig: bigint; 75 | hex: string; 76 | }; 77 | }; 78 | feeBig: bigint; 79 | myArray: [number, number, Date, number, { 80 | hex: HexBag; 81 | }]; 82 | }; 83 | } 84 | 85 | interface HexBag { 86 | value: string; 87 | } 88 | 89 | 90 | //==========// 91 | // REVIVERS // 92 | //==========// 93 | 94 | const bigIntReviver: Reviver = ( 95 | context => BigInt(context.source) 96 | ); 97 | 98 | const dateReviver: Reviver = ( 99 | context => new Date(context.value) 100 | ); 101 | 102 | const hexReviver: Reviver = ( 103 | context => Buffer.from(context.value, 'hex').toString() 104 | ); 105 | 106 | 107 | //==================// 108 | // PARSING BY RULES // 109 | //==================// 110 | 111 | const object = parseJsonByRules(content, { 112 | rules: [{ 113 | pattern: '**.*Big', 114 | reviver: bigIntReviver, 115 | }, { 116 | pattern: [ 117 | '**.hex.value', 118 | 'foo.bar.baz.hex', 119 | ], 120 | reviver: hexReviver, 121 | }, { 122 | pattern: 'foo.myArray.2', 123 | reviver: dateReviver, 124 | }], 125 | }); 126 | 127 | 128 | //=================// 129 | // TESTING RESULTS // 130 | //=================// 131 | 132 | assert.equal(typeof object.foo.bar.baz.valueBig, 'bigint'); 133 | assert.equal(object.foo.bar.baz.valueBig, 11145678901234567890n); 134 | 135 | assert.equal(typeof object.foo.feeBig, 'bigint'); 136 | assert.equal(object.foo.feeBig, 22245678901234567890n); 137 | 138 | assert.equal( 139 | object.foo.hex.value, 140 | 'All you need is love' 141 | ); 142 | 143 | assert.equal( 144 | object.foo.bar.baz.hex, 145 | 'For Aiur!' 146 | ); 147 | 148 | assert.equal( 149 | object.foo.myArray[4].hex.value, 150 | 'all your base are belong to us' 151 | ); 152 | 153 | assert.equal( 154 | object.foo.myArray[2].toISOString(), 155 | '2023-01-20T19:30:45.904Z' 156 | ); 157 | ``` 158 | 159 | If you don't want to use the polyfill you can do this instead: 160 | 161 | ```ts 162 | import { parseJson } from '@ton.js/json-parser'; 163 | import { parseJsonByRules } from '@ton.js/json-parser-rules'; 164 | 165 | const result = parseJsonByRules('{ … }', { 166 | rules: [], 167 | parser: parseJson, 168 | }); 169 | ``` 170 | 171 | 172 | ### API 173 | 174 | > `parseJsonByRules(source, options): Type;` 175 | 176 | Parses the specified JSON document according to the provided 177 | deserialization rules. 178 | 179 | | Property | Type | Description | 180 | |--------------------|-----------------|-----------------------------------------------------| 181 | | `* source` | `string` | The JSON document to parse. | 182 | | `* options` | `Options` | An options object. | 183 | | `* options.rules` | `ReviverRule[]` | A list of parsing rules. | 184 | | ` options.parser` | `ParserFunc` | An optional `JSON.parse` compatible implementation. | 185 | 186 | > `ReviverRule` 187 | 188 | Specifies the reviver function that needs to be applied for 189 | the specified patterns. 190 | 191 | | Property | Type | Description | 192 | |-------------|-------------------------|-----------------------------------------------------------------------| 193 | | `* pattern` | `string` or `string[]` | A pattern or a list of patterns to match against parsing object keys. | 194 | | `* reviver` | `Reviver` | A reviver function to apply when the pattern is getting matched. | 195 | 196 | > `Reviver = (context: ReviverContext) => any` 197 | 198 | Reviver is a callback function that is getting called to 199 | deserialize specific key-value pairs from the JSON document. 200 | It must return the deserialized value. 201 | 202 | > `ReviverContext` 203 | 204 | Reviver function will return a context object with the 205 | following information that can be used to deserialize the 206 | document data. 207 | 208 | | Property | Type | Description | 209 | |----------|----------|---------------------------------------| 210 | | `value` | `any` | A parsed value. | 211 | | `source` | `string` | An original source text of the value. | 212 | | `path` | `string` | A path of keys delimited by dots. | 213 | 214 | 215 | ### Rule format 216 | 217 | The following rule formats are supported: 218 | 219 | | Pattern | Description | 220 | | -------------------- |----------------------------------------------------------------------------------------------------| 221 | | `foo.bar.baz` | A direct key path that is getting matched exactly. | 222 | | `foo.*.bar` | The `*` part will match anything except the dot, e.g.: `foo.baz.bar`, `foo.qux.bar` | 223 | | `foo.date*` | This will match any keys under the `foo` that start with `date`, e.g. `foo.dateCreated` | 224 | | `foo.*Date` | This will match any keys under the `foo` that end with `Date`, e.g. `foo.expirationDate` | 225 | | `**` | This will apply to all the keys | 226 | | `**.foo` | This will match all the `foo` keys nested in the object, e.g. `my.deeply.nested.foo` or `foo` | 227 | | `foo.bar.**` | This will match every key under the `foo.bar` path, e.g. `foo.bar.qux.quux` | 228 | | `foo.*.bar.**.*Date` | This will match all the keys ending with `Date` under the `foo.X.bar`, where `X` could be anything | 229 | -------------------------------------------------------------------------------- /readme/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../tsconfig.common.json", 4 | "compilerOptions": { 5 | "module": "ESNext", 6 | "resolveJsonModule": true, 7 | "typeRoots": [ 8 | "node_modules/@types", 9 | "types" 10 | ] 11 | }, 12 | "files": [ 13 | "build.ts" 14 | ], 15 | "ts-node": { 16 | "esm": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /readme/types/untyped/index.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare module 'untyped' { 3 | 4 | const content = any; 5 | 6 | export default content; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.common.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@tsconfig/node-lts-strictest-esm/tsconfig.json", 4 | "compilerOptions": { 5 | "target": "ES2020", 6 | "lib": [ 7 | "ES2020" 8 | ], 9 | "sourceMap": true, 10 | "noUnusedParameters": false, 11 | "noUnusedLocals": false 12 | }, 13 | "files": [] 14 | } 15 | --------------------------------------------------------------------------------