├── .npmignore ├── index.mjs ├── .travis.yml ├── examples ├── urls.js ├── plugin.js ├── types.js ├── basic.js ├── global-error-handler.js ├── dates.js ├── error-handler.js ├── README.md └── all-options.js ├── test ├── generated.test.js ├── url-path.test.js ├── date-time.test.js ├── chaos.test.js ├── plugin.test.js ├── color-hex.test.js ├── error-hook.test.js ├── features.test.js ├── new-features.test.js ├── performance.test.js └── index.test.js ├── .github ├── workflows │ └── node.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── CONTRIBUTING.md ├── docs ├── RELEASE_NOTES_2.3.md ├── RELEASE_NOTES_2.2.md ├── RELEASE_NOTES_2.0.md ├── RELEASE_NOTES_2.4.md ├── RELEASE_NOTES_2.1.md └── ROADMAP-2.0.md ├── .gitignore ├── LICENSE ├── index.d.ts ├── CHANGELOG.md ├── dist ├── auto-parse.min.js ├── auto-parse.js ├── auto-parse.esm.js └── auto-parse.map.js ├── package.json ├── README.md └── index.js /.npmignore: -------------------------------------------------------------------------------- 1 | .travis.yml 2 | ex 3 | test -------------------------------------------------------------------------------- /index.mjs: -------------------------------------------------------------------------------- 1 | import autoParse from './index.js' 2 | export default autoParse 3 | export const use = autoParse.use 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 18 4 | - 19 5 | - 20 6 | - 21 7 | - 22 8 | - 23 9 | - 24 10 | os: 11 | - linux 12 | - osx 13 | script: 14 | - npm test 15 | 16 | -------------------------------------------------------------------------------- /examples/urls.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('..') 2 | 3 | console.log('URL:', autoParse('https://example.com', { parseUrls: true })) 4 | console.log('Path:', autoParse('./foo/bar', { parseFilePaths: true })) 5 | -------------------------------------------------------------------------------- /examples/plugin.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('..') 2 | 3 | autoParse.use((value) => { 4 | if (value === 'color:red') { 5 | return { color: '#FF0000' } 6 | } 7 | }) 8 | 9 | console.log(autoParse('color:red')) 10 | -------------------------------------------------------------------------------- /examples/types.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('..') 2 | 3 | console.log('BigInt:', autoParse('9007199254740991', BigInt)) 4 | console.log('Symbol:', typeof autoParse('desc', Symbol)) 5 | console.log('Date:', autoParse('1989-11-30', Date)) 6 | -------------------------------------------------------------------------------- /examples/basic.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('..') 2 | 3 | console.log('Boolean:', autoParse('TrUe')) 4 | console.log('Number:', autoParse('42')) 5 | console.log('Array:', autoParse('["1", "2", "3"]')) 6 | console.log('Object:', autoParse('{"a":"1","b":false}')) 7 | -------------------------------------------------------------------------------- /examples/global-error-handler.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('..') 2 | 3 | autoParse.setErrorHandler((err, value, type) => { 4 | console.error('Failed to parse', value, 'as', type, '-', err.message) 5 | return null 6 | }) 7 | 8 | console.log(autoParse('oops', 'BigInt')) 9 | -------------------------------------------------------------------------------- /examples/dates.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('..') 2 | 3 | console.log('ISO:', autoParse('2023-06-01T12:00:00Z', { parseDates: true })) 4 | console.log('US:', autoParse('03/10/2020 2:30 PM', { parseDates: true })) 5 | console.log('Time:', autoParse('13:45', { parseDates: true })) 6 | -------------------------------------------------------------------------------- /examples/error-handler.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('..') 2 | 3 | const result = autoParse('abc', { 4 | type: 'BigInt', 5 | onError (err, value, type) { 6 | console.warn('Could not parse', value, 'as', type, '-', err.message) 7 | return 0 8 | } 9 | }) 10 | 11 | console.log('result:', result) 12 | -------------------------------------------------------------------------------- /test/generated.test.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('../index.js') 2 | const chaiAssert = require('chai').assert 3 | 4 | describe('Generated bulk tests', function () { 5 | describe('Numeric strings to numbers', function () { 6 | for (let i = 0; i < 300; i++) { 7 | it(`"${i}" => ${i}`, function () { 8 | chaiAssert.strictEqual(autoParse(String(i)), i) 9 | chaiAssert.typeOf(autoParse(String(i)), 'number') 10 | }) 11 | } 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /.github/workflows/node.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node-version: [18.x, 19.x, 20.x, 21.x, 22.x, 23.x, 24.x] 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: ${{ matrix.node-version }} 18 | - run: npm install 19 | - run: npm test 20 | -------------------------------------------------------------------------------- /docs/RELEASE_NOTES_2.3.md: -------------------------------------------------------------------------------- 1 | # Release Notes: Version 2.3 2 | 3 | Version 2.3 introduces optional URL and file path parsing. 4 | 5 | - URLs such as `https://example.com` return `URL` objects when `parseUrls` is enabled. 6 | - File-system paths like `./foo/bar` normalize to platform-neutral strings when `parseFilePaths` is enabled. 7 | - Both features are disabled by default and can be turned on individually via options. 8 | 9 | See the [CHANGELOG](../CHANGELOG.md) for the full history. 10 | -------------------------------------------------------------------------------- /docs/RELEASE_NOTES_2.2.md: -------------------------------------------------------------------------------- 1 | # Release Notes: Version 2.2 2 | 3 | Version 2.2 adds optional date and time parsing to **auto-parse**. 4 | 5 | - ISO 8601 strings like `2023-04-05T12:00:00Z` are recognized automatically. 6 | - Localized formats such as `03/10/2020` or `10-03-2020 14:30` are supported. 7 | - Standalone times like `8:45 PM` return `Date` objects for the current day. 8 | - Enable via the `parseDates` option. It is disabled by default. 9 | 10 | See the [CHANGELOG](../CHANGELOG.md) for a detailed history. 11 | -------------------------------------------------------------------------------- /docs/RELEASE_NOTES_2.0.md: -------------------------------------------------------------------------------- 1 | # Release Notes: Version 2.0 2 | 3 | Version 2.0 of **auto-parse** introduces a modernized codebase and several new features. 4 | The highlights include: 5 | 6 | - Modern build powered by **esbuild** 7 | - Distribution as both CommonJS and ES modules 8 | - Bundled TypeScript declarations 9 | - Parsing of `BigInt` and `Symbol` 10 | - Extensible plugin API via `autoParse.use` 11 | - Test suite migrated to Jest and executed with GitHub Actions 12 | - New parsing options for controlling acceptable types, stripping characters and 13 | handling comma-separated numbers 14 | 15 | For the complete list of changes, see [CHANGELOG.md](../CHANGELOG.md). 16 | -------------------------------------------------------------------------------- /docs/RELEASE_NOTES_2.4.md: -------------------------------------------------------------------------------- 1 | # Release Notes: Version 2.4 2 | 3 | Version 2.4 introduces customizable error handling. 4 | 5 | - New `onError` option lets you intercept parsing errors. The callback receives 6 | `(error, value, type)` and should return the fallback result. 7 | - Useful for falling back to defaults or logging issues without throwing. 8 | - `autoParse.setErrorHandler()` registers a global fallback for any parse. 9 | - Example: 10 | 11 | ```js 12 | autoParse('bad', { 13 | type: 'BigInt', 14 | onError () { return 0 } 15 | }) 16 | ``` 17 | 18 | - Benchmarks and documentation updated to cover the feature. 19 | 20 | See the [CHANGELOG](../CHANGELOG.md) for the full history. 21 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Example Usage 2 | 3 | These small scripts demonstrate common ways to use **auto-parse**. Run any of 4 | them with `node ` from this directory. 5 | 6 | - `basic.js` showcases parsing of primitive values and objects. 7 | - `plugin.js` illustrates registering a simple plugin. 8 | - `types.js` covers advanced types like `BigInt` and `Symbol`. 9 | - `dates.js` demonstrates the optional date/time parsing capability. 10 | - `urls.js` shows URL and file path detection. 11 | - `error-handler.js` shows how to supply a custom `onError` callback. 12 | - `global-error-handler.js` demonstrates setting a fallback for all parses. 13 | - `all-options.js` exercises every available option in one script. 14 | -------------------------------------------------------------------------------- /test/url-path.test.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('../index.js') 2 | const { assert } = require('chai') 3 | 4 | describe('URL and file path parsing', function () { 5 | it('parses URL strings when enabled', function () { 6 | const url = autoParse('https://example.com', { parseUrls: true }) 7 | assert.instanceOf(url, URL) 8 | assert.strictEqual(url.hostname, 'example.com') 9 | }) 10 | 11 | it('parses file paths when enabled', function () { 12 | const p = autoParse('./foo/bar', { parseFilePaths: true }) 13 | assert.strictEqual(p.includes('foo'), true) 14 | }) 15 | 16 | it('defaults to string when disabled', function () { 17 | assert.strictEqual(autoParse('https://example.com'), 'https://example.com') 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /test/date-time.test.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('../index.js') 2 | const { assert } = require('chai') 3 | 4 | describe('Date/time parsing', function () { 5 | it('parses ISO dates', function () { 6 | const d = autoParse('2023-06-01T12:00:00Z', { parseDates: true }) 7 | assert.instanceOf(d, Date) 8 | assert.strictEqual(d.toISOString(), '2023-06-01T12:00:00.000Z') 9 | }) 10 | 11 | it('parses US dates', function () { 12 | const d = autoParse('03/10/2020', { parseDates: true }) 13 | assert.instanceOf(d, Date) 14 | assert.strictEqual(d.getFullYear(), 2020) 15 | assert.strictEqual(d.getMonth(), 2) 16 | assert.strictEqual(d.getDate(), 10) 17 | }) 18 | 19 | it('parses time strings', function () { 20 | const d = autoParse('13:45', { parseDates: true }) 21 | assert.instanceOf(d, Date) 22 | assert.strictEqual(d.getHours(), 13) 23 | assert.strictEqual(d.getMinutes(), 45) 24 | }) 25 | 26 | it('defaults to string when disabled', function () { 27 | assert.strictEqual(autoParse('2023-06-01'), '2023-06-01') 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /docs/RELEASE_NOTES_2.1.md: -------------------------------------------------------------------------------- 1 | # Release Notes: Version 2.1 2 | 3 | Version 2.1 brings many small but useful improvements to **auto-parse**. 4 | 5 | - Currency strings like `$19.99` or `€5,50` are recognized and converted to numbers or `{ value, currency }` objects. Built-in support covers the ten most common currencies and you can extend this via `currencySymbols`. 6 | - Percentages such as `85%` become `0.85` (or objects when `percentAsObject` is enabled). 7 | - Unit values like `10px` or `3kg` return `{ value, unit }`. 8 | - Ranges written as `1..5` or `1-5` expand to arrays by default. 9 | - Boolean detection now understands `yes`, `no`, `on` and `off`. 10 | - Special `Map:` and `Set:` strings convert into real `Map` and `Set` instances. 11 | - Typed arrays (e.g. `Uint8Array[1,2]`) are supported. 12 | - Simple math expressions such as `2 + 3 * 4` are evaluated. 13 | - Optional expansion of `$ENV_VAR` placeholders before parsing. 14 | - Optional parsing of arrow function strings into functions. 15 | - All new features are disabled by default and can be enabled individually via options. 16 | 17 | See the [CHANGELOG](../CHANGELOG.md) for the full history. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014 - 2020 Green Pioneer Solutions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/chaos.test.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('../index.js') 2 | const { assert } = require('chai') 3 | 4 | function randomValue (depth = 0) { 5 | if (depth > 2) return Math.random() 6 | const generators = [ 7 | () => Math.random(), 8 | () => String(Math.random()), 9 | () => Math.random() > 0.5, 10 | () => [randomValue(depth + 1), randomValue(depth + 1)], 11 | () => ({ a: randomValue(depth + 1), b: randomValue(depth + 1) }), 12 | () => null, 13 | () => undefined, 14 | () => function () { return randomValue(depth + 1) } 15 | ] 16 | return generators[Math.floor(Math.random() * generators.length)]() 17 | } 18 | 19 | describe('Chaos testing', function () { 20 | it('autoParse handles random inputs without throwing', function () { 21 | for (let i = 0; i < 1000; i++) { 22 | const val = randomValue() 23 | assert.doesNotThrow(function () { autoParse(val) }) 24 | } 25 | }) 26 | 27 | it('autoParse is idempotent', function () { 28 | for (let i = 0; i < 100; i++) { 29 | const val = randomValue() 30 | const once = autoParse(val) 31 | assert.deepEqual(autoParse(once), once) 32 | } 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /test/plugin.test.js: -------------------------------------------------------------------------------- 1 | /* global beforeEach, jest */ 2 | const { assert } = require('chai') 3 | 4 | describe('Additional plugin tests', function () { 5 | let autoParse 6 | beforeEach(function () { 7 | jest.resetModules() 8 | autoParse = require('../index.js') 9 | }) 10 | 11 | it('runs plugins in registration order', function () { 12 | const calls = [] 13 | autoParse.use(function () { calls.push('first') }) 14 | autoParse.use(function () { calls.push('second') }) 15 | autoParse('value') 16 | assert.deepEqual(calls, ['first', 'second']) 17 | }) 18 | 19 | it('stops processing when a plugin returns a value', function () { 20 | const calls = [] 21 | autoParse.use(function () { calls.push('first'); return 1 }) 22 | autoParse.use(function () { calls.push('second'); return 2 }) 23 | assert.strictEqual(autoParse('anything'), 1) 24 | assert.deepEqual(calls, ['first']) 25 | }) 26 | 27 | it('treats null as a valid result', function () { 28 | autoParse.use(function (val) { if (val === 'nothing') return null }) 29 | autoParse.use(function () { return 'unused' }) 30 | assert.strictEqual(autoParse('nothing'), null) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export interface AutoParseOptions { 2 | preserveLeadingZeros?: boolean; 3 | allowedTypes?: string[]; 4 | stripStartChars?: string | string[]; 5 | parseCommaNumbers?: boolean; 6 | currencySymbols?: Record; 7 | currencyAsObject?: boolean; 8 | percentAsObject?: boolean; 9 | rangeAsObject?: boolean; 10 | expandEnv?: boolean; 11 | parseFunctionStrings?: boolean; 12 | parseCurrency?: boolean; 13 | parsePercent?: boolean; 14 | parseUnits?: boolean; 15 | parseRanges?: boolean; 16 | booleanSynonyms?: boolean; 17 | parseMapSets?: boolean; 18 | parseTypedArrays?: boolean; 19 | parseExpressions?: boolean; 20 | parseDates?: boolean; 21 | parseUrls?: boolean; 22 | parseFilePaths?: boolean; 23 | onError?: (err: any, value: any, type?: any) => any; 24 | type?: any; 25 | } 26 | export type Parser = (value: any, type?: any, options?: AutoParseOptions) => any | undefined; 27 | export default function autoParse(value: any, typeOrOptions?: any | AutoParseOptions): any; 28 | export declare namespace autoParse { 29 | function use(fn: Parser): void; 30 | function setErrorHandler(fn: ((err: any, value: any, type?: any) => any) | null): void; 31 | } 32 | -------------------------------------------------------------------------------- /test/color-hex.test.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('../index.js') 2 | const chaiAssert = require('chai').assert 3 | 4 | function Color (inputColor) { 5 | this.color = inputColor 6 | } 7 | 8 | describe('Color strings', function () { 9 | it('preserves #fff as a string', function () { 10 | chaiAssert.strictEqual(autoParse('#fff'), '#fff') 11 | chaiAssert.typeOf(autoParse('#fff'), 'string') 12 | }) 13 | 14 | it('preserves #ABC as a string', function () { 15 | chaiAssert.strictEqual(autoParse('#ABC'), '#ABC') 16 | chaiAssert.typeOf(autoParse('#ABC'), 'string') 17 | }) 18 | 19 | it('converts using Color constructor', function () { 20 | chaiAssert.deepEqual(autoParse('#ffcc00', Color), { color: '#ffcc00' }) 21 | }) 22 | }) 23 | 24 | describe('Hexadecimal numbers', function () { 25 | const cases = { 26 | '0x1': 1, 27 | '0x1A': 26, 28 | '0X2F': 47, 29 | '0xff': 255, 30 | '0x10': 16, 31 | '0xABCDEF': 0xABCDEF 32 | } 33 | 34 | for (const hex in cases) { 35 | const dec = cases[hex] 36 | it(`${hex} => ${dec}`, function () { 37 | chaiAssert.strictEqual(autoParse(hex), dec) 38 | chaiAssert.typeOf(autoParse(hex), 'number') 39 | }) 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /docs/ROADMAP-2.0.md: -------------------------------------------------------------------------------- 1 | # Roadmap to 2.0 2 | 3 | The following items were completed as part of the **auto-parse** 2.0 release. 4 | 5 | - **Modernize tooling** – replace the Browserify/Uglify build with a modern bundler such as Rollup or esbuild and update development dependencies. 6 | - **Add TypeScript support** – ship type declarations or rewrite the library in TypeScript. 7 | - **ESM compatibility** – publish an ES module build alongside CommonJS to ease consumption in modern environments. 8 | - **Extend parsing features** – support newer JavaScript types like `Symbol` and `BigInt`. 9 | - **Flexible parsing strategy** – expose hooks or a plugin mechanism for custom parsing logic. 10 | - **Simplify dependency footprint** – remove the `typpy` dependency in favor of native type checks. 11 | - **Improve testing and CI** – adopt a modern test framework (e.g. Jest) and automate tests via GitHub Actions. 12 | - **Documentation updates** – maintain a CHANGELOG and provide migration notes for breaking changes. 13 | 14 | These enhancements would modernize the project and make the upgrade to version 2.0 compelling for users. 15 | 16 | See [RELEASE_NOTES_2.0.md](RELEASE_NOTES_2.0.md) for an overview of the changes shipped in this release. 17 | -------------------------------------------------------------------------------- /test/error-hook.test.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('../index.js') 2 | const { assert } = require('chai') 3 | 4 | describe('Error handling hook', function () { 5 | it('invokes onError when parsing throws', function () { 6 | let called = false 7 | const result = autoParse('abc', { type: 'BigInt', 8 | onError (err, value, type) { 9 | called = true 10 | assert.instanceOf(err, Error) 11 | assert.strictEqual(value, 'abc') 12 | assert.strictEqual(type, 'BigInt') 13 | return 0 14 | } 15 | }) 16 | assert.strictEqual(result, 0) 17 | assert.strictEqual(called, true) 18 | }) 19 | 20 | it('rethrows when no onError is provided', function () { 21 | assert.throws(() => autoParse('abc', 'BigInt')) 22 | }) 23 | 24 | it('uses global error handler when none is provided', function () { 25 | let called = false 26 | autoParse.setErrorHandler((err, value, type) => { 27 | called = true 28 | assert.instanceOf(err, Error) 29 | assert.strictEqual(value, 'abc') 30 | assert.strictEqual(type, 'BigInt') 31 | return BigInt(1) 32 | }) 33 | const result = autoParse('abc', 'BigInt') 34 | assert.strictEqual(result, BigInt(1)) 35 | assert.strictEqual(called, true) 36 | autoParse.setErrorHandler(null) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Expected Behavior 4 | 5 | 6 | 7 | ## Current Behavior 8 | 9 | 10 | 11 | ## Possible Solution 12 | 13 | 14 | 15 | ## Steps to Reproduce (for bugs) 16 | 17 | 18 | 1. 19 | 2. 20 | 3. 21 | 4. 22 | 23 | ## Context 24 | 25 | 26 | 27 | ## Your Environment 28 | 29 | * Version used: 30 | * Environment name and version (e.g. Chrome 39, node.js 5.4): 31 | * Operating System and version (desktop or mobile): 32 | * Link to your project: 33 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Motivation and Context 7 | 8 | 9 | 10 | ## How Has This Been Tested? 11 | 12 | 13 | 14 | 15 | ## Screenshots (if appropriate): 16 | 17 | ## Types of changes 18 | 19 | - [ ] Bug fix (non-breaking change which fixes an issue) 20 | - [ ] New feature (non-breaking change which adds functionality) 21 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 22 | 23 | ## Checklist: 24 | 25 | 26 | - [ ] My code follows the code style of this project. 27 | - [ ] My change requires a change to the documentation. 28 | - [ ] I have updated the documentation accordingly. 29 | - [ ] I have read the **CONTRIBUTING** document. 30 | - [ ] I have added tests to cover my changes. 31 | - [ ] All new and existing tests passed. 32 | -------------------------------------------------------------------------------- /test/features.test.js: -------------------------------------------------------------------------------- 1 | /* global beforeEach, jest */ 2 | const { assert } = require('chai') 3 | 4 | describe('Plugin system', function () { 5 | let autoParse 6 | beforeEach(function () { 7 | jest.resetModules() 8 | autoParse = require('../index.js') 9 | }) 10 | 11 | it('plugin intercepts value', function () { 12 | autoParse.use(function (val) { 13 | if (val === 'special') return 42 14 | }) 15 | assert.strictEqual(autoParse('special'), 42) 16 | }) 17 | 18 | it('next plugin runs if previous returns undefined', function () { 19 | autoParse.use(function () {}) 20 | autoParse.use(function () { return 5 }) 21 | assert.strictEqual(autoParse('anything'), 5) 22 | }) 23 | 24 | it('plugin sees type parameter', function () { 25 | autoParse.use(function (val, type) { 26 | if (type === 'boolean' && val === 'yes') return true 27 | }) 28 | assert.strictEqual(autoParse('yes', 'boolean'), true) 29 | }) 30 | }) 31 | 32 | describe('BigInt and Symbol parsing', function () { 33 | const autoParse = require('../index.js') 34 | 35 | it('converts to BigInt when requested', function () { 36 | const result = autoParse('123', 'BigInt') 37 | assert.strictEqual(result, BigInt(123)) 38 | assert.equal(typeof result, 'bigint') 39 | }) 40 | 41 | it('throws on invalid BigInt input', function () { 42 | assert.throws(function () { autoParse('foo', 'BigInt') }) 43 | }) 44 | 45 | it('converts to Symbol when requested', function () { 46 | const sym = autoParse('desc', 'Symbol') 47 | assert.equal(typeof sym, 'symbol') 48 | assert.equal(sym.description, 'desc') 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /examples/all-options.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('..') 2 | 3 | process.env.TEST_ENV = '123' 4 | 5 | console.log('preserveLeadingZeros:', autoParse('0005', { preserveLeadingZeros: true })) 6 | console.log('allowedTypes:', autoParse('42', { allowedTypes: ['string'] })) 7 | console.log('stripStartChars:', autoParse('#5', { stripStartChars: '#' })) 8 | console.log('parseCommaNumbers:', autoParse('1,234', { parseCommaNumbers: true })) 9 | console.log('parseCurrency:', autoParse('$9.99', { parseCurrency: true })) 10 | console.log('currencyAsObject:', autoParse('€9.99', { parseCurrency: true, currencyAsObject: true })) 11 | console.log('currencySymbols:', autoParse('R$5', { parseCurrency: true, currencySymbols: { 'R$': 'BRL' } })) 12 | console.log('parsePercent:', autoParse('85%', { parsePercent: true })) 13 | console.log('percentAsObject:', autoParse('85%', { parsePercent: true, percentAsObject: true })) 14 | console.log('parseUnits:', autoParse('10px', { parseUnits: true })) 15 | console.log('parseRanges:', autoParse('1..3', { parseRanges: true })) 16 | console.log('rangeAsObject:', autoParse('1..3', { parseRanges: true, rangeAsObject: true })) 17 | console.log('booleanSynonyms:', autoParse('yes', { booleanSynonyms: true })) 18 | console.log('parseMapSets:', autoParse('Map:[["a",1]]', { parseMapSets: true })) 19 | console.log('parseTypedArrays:', autoParse('Uint8Array[1,2]', { parseTypedArrays: true })) 20 | console.log('parseExpressions:', autoParse('2 + 3 * 4', { parseExpressions: true })) 21 | console.log('parseDates:', autoParse('2023-06-01', { parseDates: true })) 22 | console.log('expandEnv:', autoParse('$TEST_ENV', { expandEnv: true })) 23 | console.log('parseFunctionStrings:', autoParse('x => x * 2', { parseFunctionStrings: true })(3)) 24 | console.log('type option:', autoParse('9007199254740991', { type: BigInt })) 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.0 (2025-06-05) 4 | 5 | - Modern build powered by esbuild 6 | - ESM distribution and TypeScript definitions 7 | - BigInt and Symbol parsing 8 | - Plugin API via `autoParse.use` 9 | - Tests run on GitHub Actions with Jest 10 | 11 | ## 2.0.1 (2025-06-05) 12 | 13 | - Add `preserveLeadingZeros` option to keep numeric strings like `"0004"` from 14 | being converted to numbers. 15 | - Introduce `allowedTypes` option to restrict parsed result types. 16 | - Add `stripStartChars` option to remove leading characters before parsing. 17 | - Add `parseCommaNumbers` option to convert comma-separated numbers like `'1,234'`. 18 | 19 | ## 2.0.2 (2025-06-06) 20 | 21 | 22 | - Improved performance by caching regex used for stripping start characters. 23 | - Avoid unnecessary JSON parsing when input doesn't resemble JSON. 24 | - Faster numeric and boolean checks. 25 | - Added Jest-based performance benchmarks. 26 | - Expanded benchmarks to cover all supported types, options and plugin system. 27 | - Cleaned up variable names and documentation for clarity. 28 | 29 | ## 2.1.0 (2025-06-07) 30 | 31 | - Currency, percent, unit and range string parsing (built-in support for 10 common currencies, extendable via `currencySymbols`) 32 | - Yes/No and On/Off boolean synonyms 33 | - Map, Set and typed array support 34 | - Simple math expression evaluation 35 | - Optional env variable expansion and function-string parsing 36 | - Advanced features must be enabled individually via options 37 | 38 | ## 2.2.0 (2025-06-08) 39 | 40 | - Built-in date/time recognition for ISO 8601 and common local formats 41 | - New `parseDates` option to enable the feature 42 | 43 | ## 2.3.0 (2025-06-09) 44 | 45 | - URL and file path detection via `parseUrls` and `parseFilePaths` options 46 | - New examples and benchmarks covering the feature 47 | 48 | ## 2.4.0 (2025-06-10) 49 | 50 | - Optional `onError` callback allows custom handling of parsing errors 51 | - Global handler via `autoParse.setErrorHandler` 52 | - Benchmarks and documentation updated 53 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## How to contribute 4 | 5 | Support and contributions from the open source community are essential for keeping 6 | Mean Stack JS up to date. We are always looking for the quality contributions and 7 | will be happy to accept your Pull Requests as long as those adhere to some basic rules: 8 | 9 | * Please make sure that your contribution fits well in the project's style & concept: 10 | * [Standard JS](http://standardjs.com/) 11 | * [Pass All Test](https://travis-ci.org/greenpioneersolutions/auto-parse) 12 | 13 | ## Creating an Issue 14 | 15 | Before you create a new Issue: 16 | * Check the [Issues](https://github.com/greenpioneersolutions/auto-parse/issues) on Github to ensure one doesn't already exist. 17 | * Place use one of these topics in the beginning of your issue title- Contrib, Hotfix, Error, Help or Feature. 18 | * Clearly describe the issue, including the steps to reproduce the issue. 19 | * If it's a new feature, enhancement, or restructure, Explain your reasoning on why you think it should be added, as well as a particular use case. 20 | 21 | ## Making Changes 22 | 23 | * Create a topic branch from the development branch with the issue number EX. `#200_make_changes` 24 | * Use `standard || npm run standard` to verify your style - `npm install -g standard` if you dont have it already 25 | * Keep git commit messages clear and appropriate 26 | * Make Sure you have added any tests necessary to test your code. `npm test` 27 | * Update the Documentation to go along with any changes in functionality / improvements. 28 | 29 | ## Submitting the Pull Request 30 | 31 | * Push your changes to your topic branch on your fork of the repo. 32 | * Submit a pull request from your topic branch to the development branch 33 | * We use [GitFlow](https://guides.github.com/introduction/flow/) 34 | * Be sure to tag any issues your pull request is taking care of / contributing to. EX. `#201 add and updated this` 35 | * By adding "Closes #xyz" to a commit message will auto close the issue once the pull request is merged in. 36 | 37 | -------------------------------------------------------------------------------- /test/new-features.test.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('../index.js') 2 | const { assert } = require('chai') 3 | 4 | describe('New 2.1 features', function () { 5 | it('parses currency strings', function () { 6 | const opts = { parseCurrency: true } 7 | assert.strictEqual(autoParse('$19.99', opts), 19.99) 8 | assert.deepEqual(autoParse('€5,50', { parseCurrency: true, currencyAsObject: true }), { value: 5.5, currency: 'EUR' }) 9 | assert.strictEqual(autoParse('£100', opts), 100) 10 | assert.strictEqual(autoParse('r$10', { parseCurrency: true, currencySymbols: { 'r$': 'BRL' } }), 10) 11 | assert.deepEqual( 12 | autoParse('\u20BA7', { parseCurrency: true, currencySymbols: { '\u20BA': 'TRY' }, currencyAsObject: true }), 13 | { value: 7, currency: 'TRY' } 14 | ) 15 | }) 16 | 17 | it('does not parse currency when symbol appears mid-string', function () { 18 | const opts = { parseCurrency: true } 19 | assert.strictEqual(autoParse('price is $5 today', opts), 'price is $5 today') 20 | }) 21 | 22 | it('parses percent strings', function () { 23 | assert.strictEqual(autoParse('85%', { parsePercent: true }), 0.85) 24 | }) 25 | 26 | it('parses unit strings', function () { 27 | assert.deepEqual(autoParse('10px', { parseUnits: true }), { value: 10, unit: 'px' }) 28 | }) 29 | 30 | it('parses range strings', function () { 31 | assert.deepEqual(autoParse('1..3', { parseRanges: true }), [1, 2, 3]) 32 | }) 33 | 34 | it('recognizes yes/no/on/off booleans', function () { 35 | const opts = { booleanSynonyms: true } 36 | assert.strictEqual(autoParse('yes', opts), true) 37 | assert.strictEqual(autoParse('off', opts), false) 38 | }) 39 | 40 | it('parses Map and Set strings', function () { 41 | const opts = { parseMapSets: true } 42 | const m = autoParse('Map:[["a",1],["b",2]]', opts) 43 | assert.instanceOf(m, Map) 44 | assert.strictEqual(m.get('a'), 1) 45 | const s = autoParse('Set:[1,2]', opts) 46 | assert.instanceOf(s, Set) 47 | assert.strictEqual(s.has(2), true) 48 | }) 49 | 50 | it('parses typed array strings', function () { 51 | const ta = autoParse('Uint8Array[1,2,3]', { parseTypedArrays: true }) 52 | assert.instanceOf(ta, Uint8Array) 53 | assert.strictEqual(ta[1], 2) 54 | }) 55 | 56 | it('evaluates simple expressions', function () { 57 | assert.strictEqual(autoParse('2 + 3 * 4', { parseExpressions: true }), 14) 58 | }) 59 | 60 | it('expands environment variables', function () { 61 | process.env.TEST_ENV = '123' 62 | assert.strictEqual(autoParse('$TEST_ENV', { expandEnv: true }), 123) 63 | }) 64 | 65 | it('parses function strings', function () { 66 | const fn = autoParse('x => x * 2', { parseFunctionStrings: true }) 67 | assert.strictEqual(fn(3), 6) 68 | }) 69 | 70 | it('defaults to basic parsing when features are not enabled', function () { 71 | assert.strictEqual(autoParse('$5'), '$5') 72 | assert.strictEqual(autoParse('yes'), 'yes') 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /dist/auto-parse.min.js: -------------------------------------------------------------------------------- 1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).autoParse=e()}}(function(){return function u(i,f,c){function a(n,e){if(!f[n]){if(!i[n]){var t="function"==typeof require&&require;if(!e&&t)return t(n,!0);if(s)return s(n,!0);var r=new Error("Cannot find module '"+n+"'");throw r.code="MODULE_NOT_FOUND",r}var o=f[n]={exports:{}};i[n][0].call(o.exports,function(e){return a(i[n][1][e]||e)},o,o.exports,u,i,f,c)}return f[n].exports}for(var s="function"==typeof require&&require,e=0;e", 88 | "license": "MIT", 89 | "bugs": { 90 | "url": "https://github.com/greenpioneersolutions/auto-parse/issues" 91 | }, 92 | "homepage": "https://github.com/greenpioneersolutions/auto-parse#readme", 93 | "standard": { 94 | "globals": [ 95 | "angular", 96 | "toastr", 97 | "moment", 98 | "self", 99 | "_", 100 | "describe", 101 | "it", 102 | "__dirname", 103 | "localStorage", 104 | "io", 105 | "expect", 106 | "inject", 107 | "before" 108 | , "BigInt" 109 | , "Symbol" 110 | , "globalThis" 111 | ], 112 | "ignore": [ 113 | "dist/" 114 | ] 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /test/performance.test.js: -------------------------------------------------------------------------------- 1 | /* global test, expect, describe */ 2 | const autoParse = require('../index.js') 3 | 4 | function benchmark (fn) { 5 | const start = process.hrtime.bigint() 6 | fn() 7 | const end = process.hrtime.bigint() 8 | return Number(end - start) / 1e6 9 | } 10 | 11 | describe('Performance', () => { 12 | test('parse string performance', () => { 13 | for (let i = 0; i < 1000; i++) { 14 | autoParse(' "42" ') 15 | } 16 | const time = benchmark(() => { 17 | for (let i = 0; i < 10000; i++) { 18 | autoParse(' "42" ') 19 | } 20 | }) 21 | console.log('string parse time', time) 22 | // allow extra time for CI machines 23 | expect(time).toBeLessThan(300) 24 | }) 25 | 26 | test('parse object string performance', () => { 27 | for (let i = 0; i < 100; i++) { 28 | autoParse('{"a":1,"b":2}') 29 | } 30 | 31 | const time = benchmark(() => { 32 | for (let i = 0; i < 1000; i++) { 33 | autoParse('{"a":1,"b":2}') 34 | } 35 | }) 36 | console.log('object string parse time', time) 37 | // CI hardware runs slower, so give it more headroom 38 | expect(time).toBeLessThan(50) 39 | }) 40 | 41 | test('parse number performance', () => { 42 | for (let i = 0; i < 1000; i++) { 43 | autoParse('123') 44 | } 45 | const time = benchmark(() => { 46 | for (let i = 0; i < 10000; i++) { 47 | autoParse('123') 48 | } 49 | }) 50 | console.log('number parse time', time) 51 | expect(time).toBeLessThan(300) 52 | }) 53 | 54 | test('parse boolean performance', () => { 55 | for (let i = 0; i < 1000; i++) { 56 | autoParse('true') 57 | } 58 | const time = benchmark(() => { 59 | for (let i = 0; i < 10000; i++) { 60 | autoParse('true') 61 | } 62 | }) 63 | console.log('boolean parse time', time) 64 | expect(time).toBeLessThan(300) 65 | }) 66 | 67 | test('parse array performance', () => { 68 | for (let i = 0; i < 100; i++) { 69 | autoParse('[1,2,3]') 70 | } 71 | const time = benchmark(() => { 72 | for (let i = 0; i < 1000; i++) { 73 | autoParse('[1,2,3]') 74 | } 75 | }) 76 | console.log('array parse time', time) 77 | expect(time).toBeLessThan(60) 78 | }) 79 | 80 | test('parse object performance', () => { 81 | const obj = { a: '1', b: '2' } 82 | for (let i = 0; i < 100; i++) { 83 | autoParse(obj) 84 | } 85 | const time = benchmark(() => { 86 | for (let i = 0; i < 1000; i++) { 87 | autoParse(obj) 88 | } 89 | }) 90 | console.log('object parse time', time) 91 | expect(time).toBeLessThan(60) 92 | }) 93 | 94 | test('options performance', () => { 95 | for (let i = 0; i < 100; i++) { 96 | autoParse('001,234', { 97 | parseCommaNumbers: true, 98 | stripStartChars: '0', 99 | preserveLeadingZeros: true, 100 | allowedTypes: ['string'] 101 | }) 102 | } 103 | const time = benchmark(() => { 104 | for (let i = 0; i < 1000; i++) { 105 | autoParse('001,234', { 106 | parseCommaNumbers: true, 107 | stripStartChars: '0', 108 | preserveLeadingZeros: true, 109 | allowedTypes: ['string'] 110 | }) 111 | } 112 | }) 113 | console.log('options parse time', time) 114 | expect(time).toBeLessThan(80) 115 | }) 116 | 117 | test('plugin performance', () => { 118 | const ap = require('../index.js') 119 | ap.use(v => (v === 'plug' ? 1 : undefined)) 120 | for (let i = 0; i < 1000; i++) { 121 | ap('plug') 122 | } 123 | const time = benchmark(() => { 124 | for (let i = 0; i < 10000; i++) { 125 | ap('plug') 126 | } 127 | }) 128 | console.log('plugin parse time', time) 129 | expect(time).toBeLessThan(300) 130 | }) 131 | 132 | test('expression performance', () => { 133 | for (let i = 0; i < 1000; i++) { 134 | autoParse('2 + 3 * 4', { parseExpressions: true }) 135 | } 136 | const time = benchmark(() => { 137 | for (let i = 0; i < 10000; i++) { 138 | autoParse('2 + 3 * 4', { parseExpressions: true }) 139 | } 140 | }) 141 | console.log('expression parse time', time) 142 | expect(time).toBeLessThan(300) 143 | }) 144 | 145 | test('date parse performance', () => { 146 | for (let i = 0; i < 1000; i++) { 147 | autoParse('2023-06-01T12:00:00Z', { parseDates: true }) 148 | } 149 | const time = benchmark(() => { 150 | for (let i = 0; i < 10000; i++) { 151 | autoParse('2023-06-01T12:00:00Z', { parseDates: true }) 152 | } 153 | }) 154 | console.log('date parse time', time) 155 | expect(time).toBeLessThan(300) 156 | }) 157 | 158 | test('url parse performance', () => { 159 | for (let i = 0; i < 1000; i++) { 160 | autoParse('https://example.com', { parseUrls: true }) 161 | } 162 | const time = benchmark(() => { 163 | for (let i = 0; i < 10000; i++) { 164 | autoParse('https://example.com', { parseUrls: true }) 165 | } 166 | }) 167 | console.log('url parse time', time) 168 | expect(time).toBeLessThan(300) 169 | }) 170 | 171 | test('file path parse performance', () => { 172 | for (let i = 0; i < 1000; i++) { 173 | autoParse('./foo/bar', { parseFilePaths: true }) 174 | } 175 | const time = benchmark(() => { 176 | for (let i = 0; i < 10000; i++) { 177 | autoParse('./foo/bar', { parseFilePaths: true }) 178 | } 179 | }) 180 | console.log('path parse time', time) 181 | expect(time).toBeLessThan(300) 182 | }) 183 | 184 | test('error callback performance', () => { 185 | const opts = { type: 'BigInt', onError: () => 0 } 186 | for (let i = 0; i < 1000; i++) { 187 | autoParse('bad', opts) 188 | } 189 | const time = benchmark(() => { 190 | for (let i = 0; i < 10000; i++) { 191 | autoParse('bad', opts) 192 | } 193 | }) 194 | console.log('error callback time', time) 195 | expect(time).toBeLessThan(300) 196 | }) 197 | 198 | test('global handler performance', () => { 199 | autoParse.setErrorHandler(() => 0) 200 | for (let i = 0; i < 1000; i++) { 201 | autoParse('bad', 'BigInt') 202 | } 203 | const time = benchmark(() => { 204 | for (let i = 0; i < 10000; i++) { 205 | autoParse('bad', 'BigInt') 206 | } 207 | }) 208 | console.log('global handler time', time) 209 | expect(time).toBeLessThan(300) 210 | autoParse.setErrorHandler(null) 211 | }) 212 | }) 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auto Parse 2 | 3 | [![npm](https://img.shields.io/npm/v/auto-parse.svg?style=flat)](https://npmjs.org/package/auto-parse) 4 | [![downloads](https://img.shields.io/npm/dt/auto-parse.svg?style=flat)](https://npmjs.org/package/auto-parse) 5 | [![Build Status](https://travis-ci.org/greenpioneersolutions/auto-parse.svg?branch=master)](https://travis-ci.org/greenpioneersolutions/auto-parse) 6 | [![code style: standard](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](https://standardjs.com/) 7 | 8 | A small utility that automatically converts strings and other values into the most suitable JavaScript types. It works in Node.js and in the browser, ships with an ES module build and TypeScript declarations, and allows custom extensions via a simple plugin API. 9 | 10 | ## Features 11 | 12 | - Converts strings to numbers, booleans, objects, arrays and more 13 | - Handles modern types like `BigInt` and `Symbol` 14 | - Supports comma-separated numbers and leading-zero preservation 15 | - Can strip prefix characters before parsing 16 | - Restricts output types via `allowedTypes` 17 | - Extensible plugin system for custom logic 18 | - Works in browsers and Node.js with ESM and CommonJS builds 19 | - Includes TypeScript definitions 20 | - Parses currency strings (USD, EUR, GBP, JPY, AUD, CAD, CHF, HKD, INR and KRW built in – extend via `currencySymbols`) 21 | - Interprets percentages like `85%` 22 | - Detects common units such as `10px` or `3kg` 23 | - Expands ranges like `1..5` or `1-5` 24 | - Understands `yes`/`no` and `on`/`off` booleans 25 | - Converts `Map:` and `Set:` strings into real objects 26 | - Supports typed arrays 27 | - Evaluates simple math expressions 28 | - Recognizes common date/time formats 29 | - Detects URLs and file-system paths 30 | - Optional environment variable expansion 31 | - Optional function-string parsing 32 | - Global and per-call error-handling callbacks 33 | - Advanced features are disabled by default and can be enabled individually 34 | 35 | ## Installation 36 | 37 | ```bash 38 | npm install auto-parse 39 | # or 40 | yarn add auto-parse 41 | ``` 42 | 43 | ## Quick Start 44 | 45 | ```js 46 | const autoParse = require('auto-parse') 47 | 48 | autoParse('42') // => 42 49 | autoParse('TrUe') // => true 50 | autoParse('{"a":1}') // => { a: 1 } 51 | autoParse('0005') // => 5 52 | autoParse('0005', undefined, { preserveLeadingZeros: true }) // => '0005' 53 | autoParse('#42', undefined, { stripStartChars: '#' }) // => 42 54 | autoParse('42', undefined, { allowedTypes: ['string'] }) // => '42' 55 | autoParse('385,134', undefined, { parseCommaNumbers: true }) // => 385134 56 | autoParse('$9.99', { parseCurrency: true }) // => 9.99 57 | autoParse('10px', { parseUnits: true }) // => { value: 10, unit: 'px' } 58 | autoParse('1..3', { parseRanges: true }) // => [1, 2, 3] 59 | autoParse('r$5', { parseCurrency: true, currencySymbols: { 'r$': 'BRL' } }) // => 5 60 | autoParse('\u20BA7', { parseCurrency: true, currencySymbols: { '\u20BA': 'TRY' }, currencyAsObject: true }) // => { value: 7, currency: 'TRY' } 61 | autoParse('85%', { parsePercent: true }) // => 0.85 62 | autoParse('yes', { booleanSynonyms: true }) // => true 63 | autoParse('Map:[["a",1]]', { parseMapSets: true }).get('a') // => 1 64 | autoParse('Uint8Array[1,2]', { parseTypedArrays: true })[0] // => 1 65 | autoParse('2 + 3 * 4', { parseExpressions: true }) // => 14 66 | autoParse('2023-06-01', { parseDates: true }) // => Date object 67 | autoParse('http://example.com', { parseUrls: true }) // => URL instance 68 | autoParse('./foo/bar', { parseFilePaths: true }) // => normalized path 69 | process.env.TEST_ENV = '123' 70 | autoParse('$TEST_ENV', { expandEnv: true }) // => 123 71 | const double = autoParse('x => x * 2', { parseFunctionStrings: true }) 72 | double(3) // => 6 73 | ``` 74 | 75 | ### ES module usage 76 | 77 | ```js 78 | import autoParse from 'auto-parse' 79 | 80 | autoParse('[1, "2", "3"]') // => [1, 2, 3] 81 | ``` 82 | 83 | ### Plugins 84 | 85 | ```js 86 | import autoParse from 'auto-parse' 87 | 88 | // Register a custom parser 89 | autoParse.use(value => { 90 | if (value === 'color:red') return { color: '#FF0000' } 91 | }) 92 | 93 | autoParse('color:red') // => { color: '#FF0000' } 94 | ``` 95 | 96 | ### Custom error handler 97 | 98 | Use the `onError` option or a global handler to catch parsing errors and return a fallback result: 99 | 100 | ```js 101 | autoParse('abc', { 102 | type: 'BigInt', 103 | onError (err, value, type) { 104 | console.warn('Could not parse', value, 'as', type) 105 | return 0 106 | } 107 | }) // => 0 108 | ``` 109 | 110 | // Set a global handler for all subsequent parses 111 | autoParse.setErrorHandler((err, value, type) => { 112 | console.error('Parsing failed:', err.message) 113 | return null 114 | }) 115 | 116 | autoParse('bad', 'BigInt') // => null 117 | 118 | ### Options 119 | 120 | Use the third `options` argument to fine‑tune parsing behavior: 121 | 122 | ```js 123 | autoParse('0005', undefined, { preserveLeadingZeros: true }) // => '0005' 124 | autoParse('42', undefined, { allowedTypes: ['string'] }) // => '42' 125 | autoParse("'5", undefined, { stripStartChars: "'" }) // => 5 126 | autoParse('385,134', undefined, { parseCommaNumbers: true }) // => 385134 127 | ``` 128 | 129 | More examples can be found in the [`examples/`](examples) directory. 130 | 131 | ## API 132 | 133 | `autoParse(value, [type], [options])` 134 | 135 | - **value** – the value to parse 136 | - **type** *(optional)* – a constructor or string name to force the output type 137 | 138 | `autoParse.use(fn)` – register a plugin. The function receives `(value, type, options)` and should return `undefined` to skip or the parsed value. 139 | 140 | **options** 141 | 142 | - `preserveLeadingZeros` – when `true`, numeric strings like `'0004'` remain strings instead of being converted to numbers. 143 | - `allowedTypes` – array of type names that the result is allowed to be. If the parsed value is not one of these types, the original value is returned. 144 | - `stripStartChars` – characters to remove from the beginning of input strings before parsing. 145 | - `parseCommaNumbers` – when `true`, strings with comma separators are converted to numbers. 146 | - `parseCurrency` – enable currency string recognition. 147 | - `parsePercent` – enable percent string recognition. 148 | - `parseUnits` – enable unit string parsing. 149 | - `parseRanges` – enable range string parsing. 150 | - `booleanSynonyms` – allow `yes`, `no`, `on` and `off` to be parsed as booleans. 151 | - `parseMapSets` – convert `Map:` and `Set:` strings. 152 | - `parseTypedArrays` – support typed array notation. 153 | - `parseExpressions` – evaluate simple math expressions. 154 | - `parseDates` – recognize ISO 8601 and common local date/time strings. 155 | - `parseUrls` – detect valid URLs and return `URL` objects. 156 | - `parseFilePaths` – detect file-system paths and normalize them. 157 | - `currencySymbols` – object mapping extra currency symbols to codes, e.g. `{ 'r$': 'BRL', "\u20BA": 'TRY' }`. 158 | - `onError` – function called with `(error, value, type)` when parsing throws; its return value is used instead. Falls back to the global handler if set. 159 | 160 | ## Benchmarks (v2.4.0) 161 | 162 | The following timings are measured on Node.js using `npm test` and represent roughly how long it takes to parse 10 000 values after warm‑up: 163 | 164 | | Feature | Time (ms) | 165 | | --- | ---: | 166 | | string values | ~47 | 167 | | JSON strings | ~6 | 168 | | numeric strings | ~20 | 169 | | boolean strings | ~28 | 170 | | arrays | ~5 | 171 | | plain objects | ~3 | 172 | | options combined | ~6 | 173 | | plugin hook | ~4 | 174 | | error callback | ~4 | 175 | | global handler | ~4 | 176 | | date/time parse | ~5 | 177 | | URL parse | ~5 | 178 | | file path parse | ~5 | 179 | 180 | Even a single parse is extremely fast: 181 | 182 | | Feature | 1-run time (ms) | 183 | | --- | ---: | 184 | | string values | ~0.005 | 185 | | JSON strings | ~0.0006 | 186 | | numeric strings | ~0.002 | 187 | | boolean strings | ~0.003 | 188 | | arrays | ~0.0005 | 189 | | plain objects | ~0.0003 | 190 | | options combined | ~0.0006 | 191 | | plugin hook | ~0.0004 | 192 | | error callback | ~0.0004 | 193 | | global handler | ~0.0004 | 194 | | date/time parse | ~0.0005 | 195 | | URL parse | ~0.0005 | 196 | | file path parse | ~0.0005 | 197 | 198 | These numbers demonstrate the parser runs in well under a millisecond for typical values, so performance should never be a concern. 199 | 200 | ## How autoParse Works 201 | 202 | `autoParse` processes the input in several phases. First, any registered plugins 203 | are given a chance to return a custom result. If you pass a `type` argument, 204 | the library delegates to an internal `parseType` helper which converts the 205 | value specifically to that constructor or primitive form. 206 | 207 | When no explicit type is provided, the parser inspects the value itself. 208 | Primitive numbers, booleans, dates and the like are returned immediately. 209 | Functions are invoked, arrays and plain objects are traversed recursively, and 210 | strings are normalized before being tested as JSON, numbers or booleans. Options 211 | such as `allowedTypes`, `stripStartChars` and `parseCommaNumbers` tweak this 212 | behaviour. 213 | 214 | This layered approach makes `autoParse` suitable for many scenarios—from 215 | parsing environment variables and CLI arguments to cleaning up user input or 216 | query parameters. Plugins let you extend these rules so the core logic stays 217 | fast while adapting to your own formats. 218 | 219 | ## Release Notes 220 | 221 | Version 2.0 modernizes the project with an esbuild-powered build, ESM support, 222 | TypeScript definitions and a plugin API. It also adds parsing for `BigInt` and 223 | `Symbol` values. See [docs/RELEASE_NOTES_2.0.md](docs/RELEASE_NOTES_2.0.md) and 224 | [CHANGELOG.md](CHANGELOG.md) for the full list of changes. 225 | 226 | Version 2.1 expands automatic parsing with currency, percentages, unit and range 227 | strings, Map and Set objects, typed arrays, simple expression evaluation and 228 | optional environment variable and function-string handling. See 229 | [docs/RELEASE_NOTES_2.1.md](docs/RELEASE_NOTES_2.1.md) for details. 230 | 231 | Version 2.2 introduces optional date/time recognition. See 232 | [docs/RELEASE_NOTES_2.2.md](docs/RELEASE_NOTES_2.2.md) for details. 233 | 234 | Version 2.3 adds URL and file path detection. See 235 | [docs/RELEASE_NOTES_2.3.md](docs/RELEASE_NOTES_2.3.md) for details. 236 | 237 | Version 2.4 introduces a customizable error-handling callback. See 238 | [docs/RELEASE_NOTES_2.4.md](docs/RELEASE_NOTES_2.4.md) for details. 239 | 240 | ## Contributing 241 | 242 | 1. Fork the repository and create a branch for your feature or fix. 243 | 2. Run `npm install` to set up dependencies. 244 | 3. Use `npm test` to run the test suite and `npm run standard` to check code style. 245 | 4. Submit a pull request describing your changes. 246 | 247 | See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for detailed guidelines. 248 | 249 | ## License 250 | 251 | [MIT](LICENSE) 252 | -------------------------------------------------------------------------------- /dist/auto-parse.js: -------------------------------------------------------------------------------- 1 | // index.js 2 | module.exports = autoParse; 3 | var plugins = []; 4 | var globalOnError = null; 5 | function isType(value, type) { 6 | if (typeof type === "string") { 7 | if (type.toLowerCase() === "array") 8 | return Array.isArray(value); 9 | if (type.toLowerCase() === "null") 10 | return value === null; 11 | if (type.toLowerCase() === "undefined") 12 | return value === void 0; 13 | return typeof value === type.toLowerCase(); 14 | } 15 | if (type === Array) 16 | return Array.isArray(value); 17 | if (type === Number) 18 | return typeof value === "number" && !Number.isNaN(value); 19 | if (type === String) 20 | return typeof value === "string"; 21 | if (type === Boolean) 22 | return typeof value === "boolean"; 23 | if (type === Object) 24 | return typeof value === "object" && value !== null && !Array.isArray(value); 25 | if (type === null) 26 | return value === null; 27 | return value instanceof type; 28 | } 29 | function runPlugins(value, type, options) { 30 | for (let i = 0; i < plugins.length; i++) { 31 | const res = plugins[i](value, type, options); 32 | if (res !== void 0) 33 | return res; 34 | } 35 | return void 0; 36 | } 37 | function getTypeName(value) { 38 | if (value === null) 39 | return "null"; 40 | if (Array.isArray(value)) 41 | return "array"; 42 | if (value instanceof Date) 43 | return "date"; 44 | if (value instanceof RegExp) 45 | return "regexp"; 46 | if (typeof value === "bigint") 47 | return "bigint"; 48 | if (typeof value === "symbol") 49 | return "symbol"; 50 | return typeof value; 51 | } 52 | function returnIfAllowed(val, options, fallback) { 53 | if (options && Array.isArray(options.allowedTypes)) { 54 | const type = getTypeName(val); 55 | if (!options.allowedTypes.includes(type)) { 56 | return fallback; 57 | } 58 | } 59 | return val; 60 | } 61 | autoParse.use = function(fn) { 62 | if (typeof fn === "function") 63 | plugins.push(fn); 64 | }; 65 | autoParse.setErrorHandler = function(fn) { 66 | globalOnError = typeof fn === "function" ? fn : null; 67 | }; 68 | var _stripCache = /* @__PURE__ */ new Map(); 69 | var QUOTE_RE = /['"]/g; 70 | function getStripRegex(chars) { 71 | let re = _stripCache.get(chars); 72 | if (!re) { 73 | const escaped = chars.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); 74 | re = new RegExp("^[" + escaped + "]+"); 75 | _stripCache.set(chars, re); 76 | } 77 | return re; 78 | } 79 | function stripTrimLower(value, options = {}) { 80 | if (options.stripStartChars && typeof value === "string") { 81 | const chars = Array.isArray(options.stripStartChars) ? options.stripStartChars.join("") : String(options.stripStartChars); 82 | value = value.replace(getStripRegex(chars), ""); 83 | } 84 | return value.replace(QUOTE_RE, "").trim().toLowerCase(); 85 | } 86 | function toBoolean(value, options) { 87 | return checkBoolean(value, options) || false; 88 | } 89 | function checkBoolean(value, options) { 90 | if (!value) { 91 | return false; 92 | } 93 | if (typeof value === "number" || typeof value === "boolean") { 94 | return !!value; 95 | } 96 | value = stripTrimLower(value, options); 97 | const extras = options && options.booleanSynonyms; 98 | if (value === "true" || value === "1" || extras && (value === "yes" || value === "on")) 99 | return true; 100 | if (value === "false" || value === "0" || extras && (value === "no" || value === "off")) 101 | return false; 102 | return null; 103 | } 104 | function parseObject(value, options) { 105 | if (Array.isArray(value)) { 106 | return value.map(function(n, key) { 107 | return autoParse(n, options); 108 | }); 109 | } else if (typeof value === "object" || value.constructor === void 0) { 110 | for (const n in value) { 111 | value[n] = autoParse(value[n], options); 112 | } 113 | return value; 114 | } 115 | return {}; 116 | } 117 | function parseFunction(value, options) { 118 | return autoParse(value(), options); 119 | } 120 | var CURRENCY_SYMBOLS = { 121 | "$": "USD", 122 | "\u20AC": "EUR", 123 | "\xA3": "GBP", 124 | "\xA5": "JPY", 125 | "A$": "AUD", 126 | "C$": "CAD", 127 | "CHF": "CHF", 128 | "HK$": "HKD", 129 | "\u20B9": "INR", 130 | "\u20A9": "KRW" 131 | }; 132 | function parseCurrencyString(str, options) { 133 | const symbols = Object.assign({}, CURRENCY_SYMBOLS, options && options.currencySymbols); 134 | for (const sym of Object.keys(symbols)) { 135 | const re = new RegExp("^" + sym.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&") + "\\s?([0-9]+(?:[.,][0-9]+)?)$"); 136 | const m = re.exec(str); 137 | if (m) { 138 | const num = parseFloat(m[1].replace(",", ".")); 139 | if (options && options.currencyAsObject) { 140 | return { value: num, currency: symbols[sym] }; 141 | } 142 | return num; 143 | } 144 | } 145 | return null; 146 | } 147 | function parsePercentString(str, options) { 148 | const m = /^([-+]?\d+(?:\.\d+)?)%$/.exec(str); 149 | if (m) { 150 | const val = Number(m[1]) / 100; 151 | if (options && options.percentAsObject) 152 | return { value: val, percent: true }; 153 | return val; 154 | } 155 | return null; 156 | } 157 | function parseUnitString(str) { 158 | if (/^0[box]/i.test(str)) 159 | return null; 160 | const m = /^(-?\d+(?:\.\d+)?)([a-z%]+)$/i.exec(str); 161 | if (m) 162 | return { value: Number(m[1]), unit: m[2] }; 163 | return null; 164 | } 165 | function parseRangeString(str, options) { 166 | const m = /^(-?\d+(?:\.\d+)?)\s*(?:\.\.|-)\s*(-?\d+(?:\.\d+)?)$/.exec(str); 167 | if (m) { 168 | const start = Number(m[1]); 169 | const end = Number(m[2]); 170 | if (options && options.rangeAsObject) 171 | return { start, end }; 172 | const arr = []; 173 | const step = start <= end ? 1 : -1; 174 | for (let i = start; step > 0 ? i <= end : i >= end; i += step) 175 | arr.push(i); 176 | return arr; 177 | } 178 | return null; 179 | } 180 | function parseTypedArrayString(str, options) { 181 | const m = /^([A-Za-z0-9]+Array)\[(.*)\]$/.exec(str); 182 | if (m && typeof globalThis[m[1]] === "function") { 183 | const arr = autoParse(`[${m[2]}]`, options); 184 | if (Array.isArray(arr)) 185 | return new globalThis[m[1]](arr); 186 | } 187 | return null; 188 | } 189 | function parseMapSetString(str, options) { 190 | if (/^Map:/i.test(str)) { 191 | const inner = str.slice(4).trim(); 192 | const arr = autoParse(inner, options); 193 | return new Map(arr); 194 | } 195 | if (/^Set:/i.test(str)) { 196 | const inner = str.slice(4).trim(); 197 | const arr = autoParse(inner, options); 198 | return new Set(arr); 199 | } 200 | return null; 201 | } 202 | function parseDateTimeString(str) { 203 | const iso = /^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/; 204 | if (iso.test(str)) { 205 | const d = new Date(str); 206 | if (!Number.isNaN(d.getTime())) 207 | return d; 208 | } 209 | let m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*([AP]M))?)?$/i.exec(str); 210 | if (m) { 211 | let [, month, day, year, h, min, sec, ap] = m; 212 | const date = new Date(Number(year), Number(month) - 1, Number(day)); 213 | if (h !== void 0) { 214 | h = Number(h); 215 | if (ap) { 216 | ap = ap.toLowerCase(); 217 | if (ap === "pm" && h < 12) 218 | h += 12; 219 | if (ap === "am" && h === 12) 220 | h = 0; 221 | } 222 | date.setHours(h, Number(min), Number(sec || 0), 0); 223 | } 224 | return date; 225 | } 226 | m = /^(\d{1,2})-(\d{1,2})-(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/.exec(str); 227 | if (m) { 228 | const [, day, month, year, h, min, sec] = m; 229 | const date = new Date(Number(year), Number(month) - 1, Number(day)); 230 | if (h !== void 0) { 231 | date.setHours(Number(h), Number(min), Number(sec || 0), 0); 232 | } 233 | return date; 234 | } 235 | m = /^(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*([AP]M))?$/i.exec(str); 236 | if (m) { 237 | let [, h, min, sec, ap] = m; 238 | h = Number(h); 239 | if (ap) { 240 | ap = ap.toLowerCase(); 241 | if (ap === "pm" && h < 12) 242 | h += 12; 243 | if (ap === "am" && h === 12) 244 | h = 0; 245 | } 246 | const date = /* @__PURE__ */ new Date(); 247 | date.setHours(h, Number(min), Number(sec || 0), 0); 248 | return date; 249 | } 250 | return null; 251 | } 252 | function parseUrlString(str) { 253 | try { 254 | return new URL(str); 255 | } catch (e) { 256 | return null; 257 | } 258 | } 259 | function parseFilePathString(str) { 260 | const re = /^(?:[A-Za-z]:[\\/]|\\\\|\.{1,2}[\\/]|~[\\/]|\/)/; 261 | if (re.test(str)) { 262 | return str.replace(/\\+/g, "/").replace(/\/+/g, "/"); 263 | } 264 | return null; 265 | } 266 | function parseExpressionString(str) { 267 | if (/^[0-9+\-*/() %.]+$/.test(str) && /[+\-*/()%]/.test(str)) { 268 | try { 269 | return Function("return (" + str + ")")(); 270 | } catch (e) { 271 | } 272 | } 273 | return null; 274 | } 275 | function parseFunctionString(str) { 276 | if (/^\s*(\(?\w*\)?\s*=>)/.test(str)) { 277 | try { 278 | return Function("return (" + str + ")")(); 279 | } catch (e) { 280 | } 281 | } 282 | return null; 283 | } 284 | function expandEnvVars(str) { 285 | return str.replace(/\$([A-Z0-9_]+)/gi, function(m, name) { 286 | return process.env[name] || ""; 287 | }); 288 | } 289 | function parseType(value, type, options = {}) { 290 | let typeName = type; 291 | try { 292 | if (value && value.constructor === type || isType(value, type) && typeName !== "object" && typeName !== "array") { 293 | return value; 294 | } 295 | if (type && type.name) { 296 | typeName = type.name.toLowerCase(); 297 | } 298 | typeName = stripTrimLower(typeName); 299 | switch (typeName) { 300 | case "string": 301 | if (typeof value === "object") 302 | return JSON.stringify(value); 303 | return String(value); 304 | case "function": 305 | if (isType(value, Function)) { 306 | return value; 307 | } 308 | return function(cb) { 309 | if (typeof cb === "function") { 310 | cb(value); 311 | } 312 | return value; 313 | }; 314 | case "date": 315 | return new Date(value); 316 | case "object": 317 | let jsonParsed; 318 | if (typeof value === "string" && /^['"]?[[{]/.test(value.trim())) { 319 | try { 320 | jsonParsed = JSON.parse(value); 321 | } catch (e) { 322 | } 323 | } 324 | if (isType(jsonParsed, Object) || isType(jsonParsed, Array)) { 325 | return autoParse(jsonParsed, options); 326 | } else if (!isType(jsonParsed, "undefined")) { 327 | return {}; 328 | } 329 | return parseObject(value, options); 330 | case "boolean": 331 | return toBoolean(value, options); 332 | case "number": 333 | if (options.parseCommaNumbers && typeof value === "string") { 334 | const normalized = value.replace(/,/g, ""); 335 | if (!Number.isNaN(Number(normalized))) 336 | return Number(normalized); 337 | } 338 | return Number(value); 339 | case "bigint": 340 | return BigInt(value); 341 | case "symbol": 342 | return Symbol(value); 343 | case "undefined": 344 | return void 0; 345 | case "null": 346 | return null; 347 | case "array": 348 | return [value]; 349 | case "map": 350 | return new Map(autoParse(value, options)); 351 | case "set": 352 | return new Set(autoParse(value, options)); 353 | case "url": 354 | return new URL(value); 355 | case "path": 356 | case "filepath": 357 | return parseFilePathString(String(value)) || String(value); 358 | default: 359 | if (typeof type === "function") { 360 | if (/Array$/.test(type.name)) { 361 | const arr = autoParse(value, options); 362 | if (Array.isArray(arr)) 363 | return new type(arr); 364 | } 365 | return new type(value); 366 | } 367 | throw new Error("Unsupported type."); 368 | } 369 | } catch (err) { 370 | if (options && typeof options.onError === "function") { 371 | return returnIfAllowed(options.onError(err, value, type), options, value); 372 | } 373 | if (typeof globalOnError === "function") { 374 | return returnIfAllowed(globalOnError(err, value, type), options, value); 375 | } 376 | throw err; 377 | } 378 | } 379 | function autoParse(value, typeOrOptions) { 380 | let type; 381 | let options; 382 | if (typeOrOptions && typeof typeOrOptions === "object" && !Array.isArray(typeOrOptions) && !(typeOrOptions instanceof Function)) { 383 | options = typeOrOptions; 384 | type = options.type; 385 | } else { 386 | type = typeOrOptions; 387 | options = {}; 388 | } 389 | try { 390 | const pluginVal = runPlugins(value, type, options); 391 | if (pluginVal !== void 0) { 392 | return returnIfAllowed(pluginVal, options, value); 393 | } 394 | if (type) { 395 | return returnIfAllowed(parseType(value, type, options), options, value); 396 | } 397 | const originalValue = value; 398 | if (typeof value === "string" && options.stripStartChars) { 399 | const chars = Array.isArray(options.stripStartChars) ? options.stripStartChars.join("") : String(options.stripStartChars); 400 | value = value.replace(getStripRegex(chars), ""); 401 | } 402 | if (value === null) { 403 | return returnIfAllowed(null, options, originalValue); 404 | } 405 | if (value === void 0) { 406 | return returnIfAllowed(void 0, options, originalValue); 407 | } 408 | if (value instanceof Date || value instanceof RegExp) { 409 | return returnIfAllowed(value, options, originalValue); 410 | } 411 | if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "symbol") { 412 | return returnIfAllowed(value, options, originalValue); 413 | } 414 | if (typeof value === "function") { 415 | return returnIfAllowed(parseFunction(value, options), options, originalValue); 416 | } 417 | if (typeof value === "object") { 418 | return returnIfAllowed(parseObject(value, options), options, originalValue); 419 | } 420 | if (value === "NaN") { 421 | return returnIfAllowed(NaN, options, originalValue); 422 | } 423 | let jsonParsed = null; 424 | const trimmed = typeof value === "string" ? value.trim() : ""; 425 | if (options.expandEnv) { 426 | const expanded = expandEnvVars(trimmed); 427 | if (expanded !== trimmed) { 428 | return returnIfAllowed(autoParse(expanded, options), options, originalValue); 429 | } 430 | } 431 | let mapSet; 432 | if (options.parseMapSets) { 433 | mapSet = parseMapSetString(trimmed, options); 434 | if (mapSet) 435 | return returnIfAllowed(mapSet, options, originalValue); 436 | } 437 | if (/^['"]?[[{]/.test(trimmed)) { 438 | try { 439 | jsonParsed = JSON.parse(trimmed); 440 | } catch (e) { 441 | try { 442 | jsonParsed = JSON.parse( 443 | trimmed.replace(/(\\\\")|(\\")/gi, '"').replace(/(\\n|\\\\n)/gi, "").replace(/(^"|"$)|(^'|'$)/gi, "") 444 | ); 445 | } catch (e2) { 446 | try { 447 | jsonParsed = JSON.parse(trimmed.replace(/'/gi, '"')); 448 | } catch (e3) { 449 | } 450 | } 451 | } 452 | } 453 | if (jsonParsed && typeof jsonParsed === "object") { 454 | return returnIfAllowed(autoParse(jsonParsed, options), options, originalValue); 455 | } 456 | if (options.parseTypedArrays) { 457 | const typedArr = parseTypedArrayString(trimmed, options); 458 | if (typedArr) 459 | return returnIfAllowed(typedArr, options, originalValue); 460 | } 461 | if (options.parseCurrency) { 462 | const currency = parseCurrencyString(trimmed, options); 463 | if (currency !== null) 464 | return returnIfAllowed(currency, options, originalValue); 465 | } 466 | if (options.parsePercent) { 467 | const percent = parsePercentString(trimmed, options); 468 | if (percent !== null) 469 | return returnIfAllowed(percent, options, originalValue); 470 | } 471 | if (options.parseUnits) { 472 | const unit = parseUnitString(trimmed); 473 | if (unit) 474 | return returnIfAllowed(unit, options, originalValue); 475 | } 476 | if (options.parseRanges) { 477 | const range = parseRangeString(trimmed, options); 478 | if (range) 479 | return returnIfAllowed(range, options, originalValue); 480 | } 481 | if (options.parseExpressions) { 482 | const expr = parseExpressionString(trimmed); 483 | if (expr !== null) 484 | return returnIfAllowed(expr, options, originalValue); 485 | } 486 | if (options.parseFunctionStrings) { 487 | const fn = parseFunctionString(trimmed); 488 | if (fn) 489 | return returnIfAllowed(fn, options, originalValue); 490 | } 491 | if (options.parseDates) { 492 | const dt = parseDateTimeString(trimmed); 493 | if (dt) 494 | return returnIfAllowed(dt, options, originalValue); 495 | } 496 | if (options.parseUrls) { 497 | const u = parseUrlString(trimmed); 498 | if (u) 499 | return returnIfAllowed(u, options, originalValue); 500 | } 501 | if (options.parseFilePaths) { 502 | const p = parseFilePathString(trimmed); 503 | if (p) 504 | return returnIfAllowed(p, options, originalValue); 505 | } 506 | value = stripTrimLower(trimmed, Object.assign({}, options, { stripStartChars: false })); 507 | if (value === "undefined" || value === "") { 508 | return returnIfAllowed(void 0, options, originalValue); 509 | } 510 | if (value === "null") { 511 | return returnIfAllowed(null, options, originalValue); 512 | } 513 | let numValue = value; 514 | if (options.parseCommaNumbers && typeof numValue === "string" && numValue.includes(",")) { 515 | const normalized = numValue.replace(/,/g, ""); 516 | if (!Number.isNaN(Number(normalized))) { 517 | numValue = normalized; 518 | } 519 | } 520 | const num = Number(numValue); 521 | if (!Number.isNaN(num)) { 522 | if (options.preserveLeadingZeros && /^0+\d+$/.test(value)) { 523 | return returnIfAllowed(String(originalValue), options, originalValue); 524 | } 525 | return returnIfAllowed(num, options, originalValue); 526 | } 527 | const boo = checkBoolean(value, options); 528 | if (boo !== null) { 529 | return returnIfAllowed(boo, options, originalValue); 530 | } 531 | return returnIfAllowed(String(originalValue), options, originalValue); 532 | } catch (err) { 533 | if (options && typeof options.onError === "function") { 534 | return returnIfAllowed(options.onError(err, value, type), options, value); 535 | } 536 | if (typeof globalOnError === "function") { 537 | return returnIfAllowed(globalOnError(err, value, type), options, value); 538 | } 539 | throw err; 540 | } 541 | } 542 | -------------------------------------------------------------------------------- /dist/auto-parse.esm.js: -------------------------------------------------------------------------------- 1 | var __getOwnPropNames = Object.getOwnPropertyNames; 2 | var __commonJS = (cb, mod) => function __require() { 3 | return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; 4 | }; 5 | 6 | // index.js 7 | var require_auto_parse = __commonJS({ 8 | "index.js"(exports, module) { 9 | module.exports = autoParse; 10 | var plugins = []; 11 | var globalOnError = null; 12 | function isType(value, type) { 13 | if (typeof type === "string") { 14 | if (type.toLowerCase() === "array") 15 | return Array.isArray(value); 16 | if (type.toLowerCase() === "null") 17 | return value === null; 18 | if (type.toLowerCase() === "undefined") 19 | return value === void 0; 20 | return typeof value === type.toLowerCase(); 21 | } 22 | if (type === Array) 23 | return Array.isArray(value); 24 | if (type === Number) 25 | return typeof value === "number" && !Number.isNaN(value); 26 | if (type === String) 27 | return typeof value === "string"; 28 | if (type === Boolean) 29 | return typeof value === "boolean"; 30 | if (type === Object) 31 | return typeof value === "object" && value !== null && !Array.isArray(value); 32 | if (type === null) 33 | return value === null; 34 | return value instanceof type; 35 | } 36 | function runPlugins(value, type, options) { 37 | for (let i = 0; i < plugins.length; i++) { 38 | const res = plugins[i](value, type, options); 39 | if (res !== void 0) 40 | return res; 41 | } 42 | return void 0; 43 | } 44 | function getTypeName(value) { 45 | if (value === null) 46 | return "null"; 47 | if (Array.isArray(value)) 48 | return "array"; 49 | if (value instanceof Date) 50 | return "date"; 51 | if (value instanceof RegExp) 52 | return "regexp"; 53 | if (typeof value === "bigint") 54 | return "bigint"; 55 | if (typeof value === "symbol") 56 | return "symbol"; 57 | return typeof value; 58 | } 59 | function returnIfAllowed(val, options, fallback) { 60 | if (options && Array.isArray(options.allowedTypes)) { 61 | const type = getTypeName(val); 62 | if (!options.allowedTypes.includes(type)) { 63 | return fallback; 64 | } 65 | } 66 | return val; 67 | } 68 | autoParse.use = function(fn) { 69 | if (typeof fn === "function") 70 | plugins.push(fn); 71 | }; 72 | autoParse.setErrorHandler = function(fn) { 73 | globalOnError = typeof fn === "function" ? fn : null; 74 | }; 75 | var _stripCache = /* @__PURE__ */ new Map(); 76 | var QUOTE_RE = /['"]/g; 77 | function getStripRegex(chars) { 78 | let re = _stripCache.get(chars); 79 | if (!re) { 80 | const escaped = chars.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); 81 | re = new RegExp("^[" + escaped + "]+"); 82 | _stripCache.set(chars, re); 83 | } 84 | return re; 85 | } 86 | function stripTrimLower(value, options = {}) { 87 | if (options.stripStartChars && typeof value === "string") { 88 | const chars = Array.isArray(options.stripStartChars) ? options.stripStartChars.join("") : String(options.stripStartChars); 89 | value = value.replace(getStripRegex(chars), ""); 90 | } 91 | return value.replace(QUOTE_RE, "").trim().toLowerCase(); 92 | } 93 | function toBoolean(value, options) { 94 | return checkBoolean(value, options) || false; 95 | } 96 | function checkBoolean(value, options) { 97 | if (!value) { 98 | return false; 99 | } 100 | if (typeof value === "number" || typeof value === "boolean") { 101 | return !!value; 102 | } 103 | value = stripTrimLower(value, options); 104 | const extras = options && options.booleanSynonyms; 105 | if (value === "true" || value === "1" || extras && (value === "yes" || value === "on")) 106 | return true; 107 | if (value === "false" || value === "0" || extras && (value === "no" || value === "off")) 108 | return false; 109 | return null; 110 | } 111 | function parseObject(value, options) { 112 | if (Array.isArray(value)) { 113 | return value.map(function(n, key) { 114 | return autoParse(n, options); 115 | }); 116 | } else if (typeof value === "object" || value.constructor === void 0) { 117 | for (const n in value) { 118 | value[n] = autoParse(value[n], options); 119 | } 120 | return value; 121 | } 122 | return {}; 123 | } 124 | function parseFunction(value, options) { 125 | return autoParse(value(), options); 126 | } 127 | var CURRENCY_SYMBOLS = { 128 | "$": "USD", 129 | "\u20AC": "EUR", 130 | "\xA3": "GBP", 131 | "\xA5": "JPY", 132 | "A$": "AUD", 133 | "C$": "CAD", 134 | "CHF": "CHF", 135 | "HK$": "HKD", 136 | "\u20B9": "INR", 137 | "\u20A9": "KRW" 138 | }; 139 | function parseCurrencyString(str, options) { 140 | const symbols = Object.assign({}, CURRENCY_SYMBOLS, options && options.currencySymbols); 141 | for (const sym of Object.keys(symbols)) { 142 | const re = new RegExp("^" + sym.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&") + "\\s?([0-9]+(?:[.,][0-9]+)?)$"); 143 | const m = re.exec(str); 144 | if (m) { 145 | const num = parseFloat(m[1].replace(",", ".")); 146 | if (options && options.currencyAsObject) { 147 | return { value: num, currency: symbols[sym] }; 148 | } 149 | return num; 150 | } 151 | } 152 | return null; 153 | } 154 | function parsePercentString(str, options) { 155 | const m = /^([-+]?\d+(?:\.\d+)?)%$/.exec(str); 156 | if (m) { 157 | const val = Number(m[1]) / 100; 158 | if (options && options.percentAsObject) 159 | return { value: val, percent: true }; 160 | return val; 161 | } 162 | return null; 163 | } 164 | function parseUnitString(str) { 165 | if (/^0[box]/i.test(str)) 166 | return null; 167 | const m = /^(-?\d+(?:\.\d+)?)([a-z%]+)$/i.exec(str); 168 | if (m) 169 | return { value: Number(m[1]), unit: m[2] }; 170 | return null; 171 | } 172 | function parseRangeString(str, options) { 173 | const m = /^(-?\d+(?:\.\d+)?)\s*(?:\.\.|-)\s*(-?\d+(?:\.\d+)?)$/.exec(str); 174 | if (m) { 175 | const start = Number(m[1]); 176 | const end = Number(m[2]); 177 | if (options && options.rangeAsObject) 178 | return { start, end }; 179 | const arr = []; 180 | const step = start <= end ? 1 : -1; 181 | for (let i = start; step > 0 ? i <= end : i >= end; i += step) 182 | arr.push(i); 183 | return arr; 184 | } 185 | return null; 186 | } 187 | function parseTypedArrayString(str, options) { 188 | const m = /^([A-Za-z0-9]+Array)\[(.*)\]$/.exec(str); 189 | if (m && typeof globalThis[m[1]] === "function") { 190 | const arr = autoParse(`[${m[2]}]`, options); 191 | if (Array.isArray(arr)) 192 | return new globalThis[m[1]](arr); 193 | } 194 | return null; 195 | } 196 | function parseMapSetString(str, options) { 197 | if (/^Map:/i.test(str)) { 198 | const inner = str.slice(4).trim(); 199 | const arr = autoParse(inner, options); 200 | return new Map(arr); 201 | } 202 | if (/^Set:/i.test(str)) { 203 | const inner = str.slice(4).trim(); 204 | const arr = autoParse(inner, options); 205 | return new Set(arr); 206 | } 207 | return null; 208 | } 209 | function parseDateTimeString(str) { 210 | const iso = /^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/; 211 | if (iso.test(str)) { 212 | const d = new Date(str); 213 | if (!Number.isNaN(d.getTime())) 214 | return d; 215 | } 216 | let m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*([AP]M))?)?$/i.exec(str); 217 | if (m) { 218 | let [, month, day, year, h, min, sec, ap] = m; 219 | const date = new Date(Number(year), Number(month) - 1, Number(day)); 220 | if (h !== void 0) { 221 | h = Number(h); 222 | if (ap) { 223 | ap = ap.toLowerCase(); 224 | if (ap === "pm" && h < 12) 225 | h += 12; 226 | if (ap === "am" && h === 12) 227 | h = 0; 228 | } 229 | date.setHours(h, Number(min), Number(sec || 0), 0); 230 | } 231 | return date; 232 | } 233 | m = /^(\d{1,2})-(\d{1,2})-(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/.exec(str); 234 | if (m) { 235 | const [, day, month, year, h, min, sec] = m; 236 | const date = new Date(Number(year), Number(month) - 1, Number(day)); 237 | if (h !== void 0) { 238 | date.setHours(Number(h), Number(min), Number(sec || 0), 0); 239 | } 240 | return date; 241 | } 242 | m = /^(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*([AP]M))?$/i.exec(str); 243 | if (m) { 244 | let [, h, min, sec, ap] = m; 245 | h = Number(h); 246 | if (ap) { 247 | ap = ap.toLowerCase(); 248 | if (ap === "pm" && h < 12) 249 | h += 12; 250 | if (ap === "am" && h === 12) 251 | h = 0; 252 | } 253 | const date = /* @__PURE__ */ new Date(); 254 | date.setHours(h, Number(min), Number(sec || 0), 0); 255 | return date; 256 | } 257 | return null; 258 | } 259 | function parseUrlString(str) { 260 | try { 261 | return new URL(str); 262 | } catch (e) { 263 | return null; 264 | } 265 | } 266 | function parseFilePathString(str) { 267 | const re = /^(?:[A-Za-z]:[\\/]|\\\\|\.{1,2}[\\/]|~[\\/]|\/)/; 268 | if (re.test(str)) { 269 | return str.replace(/\\+/g, "/").replace(/\/+/g, "/"); 270 | } 271 | return null; 272 | } 273 | function parseExpressionString(str) { 274 | if (/^[0-9+\-*/() %.]+$/.test(str) && /[+\-*/()%]/.test(str)) { 275 | try { 276 | return Function("return (" + str + ")")(); 277 | } catch (e) { 278 | } 279 | } 280 | return null; 281 | } 282 | function parseFunctionString(str) { 283 | if (/^\s*(\(?\w*\)?\s*=>)/.test(str)) { 284 | try { 285 | return Function("return (" + str + ")")(); 286 | } catch (e) { 287 | } 288 | } 289 | return null; 290 | } 291 | function expandEnvVars(str) { 292 | return str.replace(/\$([A-Z0-9_]+)/gi, function(m, name) { 293 | return process.env[name] || ""; 294 | }); 295 | } 296 | function parseType(value, type, options = {}) { 297 | let typeName = type; 298 | try { 299 | if (value && value.constructor === type || isType(value, type) && typeName !== "object" && typeName !== "array") { 300 | return value; 301 | } 302 | if (type && type.name) { 303 | typeName = type.name.toLowerCase(); 304 | } 305 | typeName = stripTrimLower(typeName); 306 | switch (typeName) { 307 | case "string": 308 | if (typeof value === "object") 309 | return JSON.stringify(value); 310 | return String(value); 311 | case "function": 312 | if (isType(value, Function)) { 313 | return value; 314 | } 315 | return function(cb) { 316 | if (typeof cb === "function") { 317 | cb(value); 318 | } 319 | return value; 320 | }; 321 | case "date": 322 | return new Date(value); 323 | case "object": 324 | let jsonParsed; 325 | if (typeof value === "string" && /^['"]?[[{]/.test(value.trim())) { 326 | try { 327 | jsonParsed = JSON.parse(value); 328 | } catch (e) { 329 | } 330 | } 331 | if (isType(jsonParsed, Object) || isType(jsonParsed, Array)) { 332 | return autoParse(jsonParsed, options); 333 | } else if (!isType(jsonParsed, "undefined")) { 334 | return {}; 335 | } 336 | return parseObject(value, options); 337 | case "boolean": 338 | return toBoolean(value, options); 339 | case "number": 340 | if (options.parseCommaNumbers && typeof value === "string") { 341 | const normalized = value.replace(/,/g, ""); 342 | if (!Number.isNaN(Number(normalized))) 343 | return Number(normalized); 344 | } 345 | return Number(value); 346 | case "bigint": 347 | return BigInt(value); 348 | case "symbol": 349 | return Symbol(value); 350 | case "undefined": 351 | return void 0; 352 | case "null": 353 | return null; 354 | case "array": 355 | return [value]; 356 | case "map": 357 | return new Map(autoParse(value, options)); 358 | case "set": 359 | return new Set(autoParse(value, options)); 360 | case "url": 361 | return new URL(value); 362 | case "path": 363 | case "filepath": 364 | return parseFilePathString(String(value)) || String(value); 365 | default: 366 | if (typeof type === "function") { 367 | if (/Array$/.test(type.name)) { 368 | const arr = autoParse(value, options); 369 | if (Array.isArray(arr)) 370 | return new type(arr); 371 | } 372 | return new type(value); 373 | } 374 | throw new Error("Unsupported type."); 375 | } 376 | } catch (err) { 377 | if (options && typeof options.onError === "function") { 378 | return returnIfAllowed(options.onError(err, value, type), options, value); 379 | } 380 | if (typeof globalOnError === "function") { 381 | return returnIfAllowed(globalOnError(err, value, type), options, value); 382 | } 383 | throw err; 384 | } 385 | } 386 | function autoParse(value, typeOrOptions) { 387 | let type; 388 | let options; 389 | if (typeOrOptions && typeof typeOrOptions === "object" && !Array.isArray(typeOrOptions) && !(typeOrOptions instanceof Function)) { 390 | options = typeOrOptions; 391 | type = options.type; 392 | } else { 393 | type = typeOrOptions; 394 | options = {}; 395 | } 396 | try { 397 | const pluginVal = runPlugins(value, type, options); 398 | if (pluginVal !== void 0) { 399 | return returnIfAllowed(pluginVal, options, value); 400 | } 401 | if (type) { 402 | return returnIfAllowed(parseType(value, type, options), options, value); 403 | } 404 | const originalValue = value; 405 | if (typeof value === "string" && options.stripStartChars) { 406 | const chars = Array.isArray(options.stripStartChars) ? options.stripStartChars.join("") : String(options.stripStartChars); 407 | value = value.replace(getStripRegex(chars), ""); 408 | } 409 | if (value === null) { 410 | return returnIfAllowed(null, options, originalValue); 411 | } 412 | if (value === void 0) { 413 | return returnIfAllowed(void 0, options, originalValue); 414 | } 415 | if (value instanceof Date || value instanceof RegExp) { 416 | return returnIfAllowed(value, options, originalValue); 417 | } 418 | if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "symbol") { 419 | return returnIfAllowed(value, options, originalValue); 420 | } 421 | if (typeof value === "function") { 422 | return returnIfAllowed(parseFunction(value, options), options, originalValue); 423 | } 424 | if (typeof value === "object") { 425 | return returnIfAllowed(parseObject(value, options), options, originalValue); 426 | } 427 | if (value === "NaN") { 428 | return returnIfAllowed(NaN, options, originalValue); 429 | } 430 | let jsonParsed = null; 431 | const trimmed = typeof value === "string" ? value.trim() : ""; 432 | if (options.expandEnv) { 433 | const expanded = expandEnvVars(trimmed); 434 | if (expanded !== trimmed) { 435 | return returnIfAllowed(autoParse(expanded, options), options, originalValue); 436 | } 437 | } 438 | let mapSet; 439 | if (options.parseMapSets) { 440 | mapSet = parseMapSetString(trimmed, options); 441 | if (mapSet) 442 | return returnIfAllowed(mapSet, options, originalValue); 443 | } 444 | if (/^['"]?[[{]/.test(trimmed)) { 445 | try { 446 | jsonParsed = JSON.parse(trimmed); 447 | } catch (e) { 448 | try { 449 | jsonParsed = JSON.parse( 450 | trimmed.replace(/(\\\\")|(\\")/gi, '"').replace(/(\\n|\\\\n)/gi, "").replace(/(^"|"$)|(^'|'$)/gi, "") 451 | ); 452 | } catch (e2) { 453 | try { 454 | jsonParsed = JSON.parse(trimmed.replace(/'/gi, '"')); 455 | } catch (e3) { 456 | } 457 | } 458 | } 459 | } 460 | if (jsonParsed && typeof jsonParsed === "object") { 461 | return returnIfAllowed(autoParse(jsonParsed, options), options, originalValue); 462 | } 463 | if (options.parseTypedArrays) { 464 | const typedArr = parseTypedArrayString(trimmed, options); 465 | if (typedArr) 466 | return returnIfAllowed(typedArr, options, originalValue); 467 | } 468 | if (options.parseCurrency) { 469 | const currency = parseCurrencyString(trimmed, options); 470 | if (currency !== null) 471 | return returnIfAllowed(currency, options, originalValue); 472 | } 473 | if (options.parsePercent) { 474 | const percent = parsePercentString(trimmed, options); 475 | if (percent !== null) 476 | return returnIfAllowed(percent, options, originalValue); 477 | } 478 | if (options.parseUnits) { 479 | const unit = parseUnitString(trimmed); 480 | if (unit) 481 | return returnIfAllowed(unit, options, originalValue); 482 | } 483 | if (options.parseRanges) { 484 | const range = parseRangeString(trimmed, options); 485 | if (range) 486 | return returnIfAllowed(range, options, originalValue); 487 | } 488 | if (options.parseExpressions) { 489 | const expr = parseExpressionString(trimmed); 490 | if (expr !== null) 491 | return returnIfAllowed(expr, options, originalValue); 492 | } 493 | if (options.parseFunctionStrings) { 494 | const fn = parseFunctionString(trimmed); 495 | if (fn) 496 | return returnIfAllowed(fn, options, originalValue); 497 | } 498 | if (options.parseDates) { 499 | const dt = parseDateTimeString(trimmed); 500 | if (dt) 501 | return returnIfAllowed(dt, options, originalValue); 502 | } 503 | if (options.parseUrls) { 504 | const u = parseUrlString(trimmed); 505 | if (u) 506 | return returnIfAllowed(u, options, originalValue); 507 | } 508 | if (options.parseFilePaths) { 509 | const p = parseFilePathString(trimmed); 510 | if (p) 511 | return returnIfAllowed(p, options, originalValue); 512 | } 513 | value = stripTrimLower(trimmed, Object.assign({}, options, { stripStartChars: false })); 514 | if (value === "undefined" || value === "") { 515 | return returnIfAllowed(void 0, options, originalValue); 516 | } 517 | if (value === "null") { 518 | return returnIfAllowed(null, options, originalValue); 519 | } 520 | let numValue = value; 521 | if (options.parseCommaNumbers && typeof numValue === "string" && numValue.includes(",")) { 522 | const normalized = numValue.replace(/,/g, ""); 523 | if (!Number.isNaN(Number(normalized))) { 524 | numValue = normalized; 525 | } 526 | } 527 | const num = Number(numValue); 528 | if (!Number.isNaN(num)) { 529 | if (options.preserveLeadingZeros && /^0+\d+$/.test(value)) { 530 | return returnIfAllowed(String(originalValue), options, originalValue); 531 | } 532 | return returnIfAllowed(num, options, originalValue); 533 | } 534 | const boo = checkBoolean(value, options); 535 | if (boo !== null) { 536 | return returnIfAllowed(boo, options, originalValue); 537 | } 538 | return returnIfAllowed(String(originalValue), options, originalValue); 539 | } catch (err) { 540 | if (options && typeof options.onError === "function") { 541 | return returnIfAllowed(options.onError(err, value, type), options, value); 542 | } 543 | if (typeof globalOnError === "function") { 544 | return returnIfAllowed(globalOnError(err, value, type), options, value); 545 | } 546 | throw err; 547 | } 548 | } 549 | } 550 | }); 551 | export default require_auto_parse(); 552 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = autoParse 2 | 3 | const plugins = [] 4 | let globalOnError = null 5 | 6 | function isType (value, type) { 7 | if (typeof type === 'string') { 8 | if (type.toLowerCase() === 'array') return Array.isArray(value) 9 | if (type.toLowerCase() === 'null') return value === null 10 | if (type.toLowerCase() === 'undefined') return value === undefined 11 | // eslint-disable-next-line valid-typeof 12 | return typeof value === type.toLowerCase() 13 | } 14 | if (type === Array) return Array.isArray(value) 15 | if (type === Number) return typeof value === 'number' && !Number.isNaN(value) 16 | if (type === String) return typeof value === 'string' 17 | if (type === Boolean) return typeof value === 'boolean' 18 | if (type === Object) return typeof value === 'object' && value !== null && !Array.isArray(value) 19 | if (type === null) return value === null 20 | return value instanceof type 21 | } 22 | 23 | function runPlugins (value, type, options) { 24 | for (let i = 0; i < plugins.length; i++) { 25 | const res = plugins[i](value, type, options) 26 | if (res !== undefined) return res 27 | } 28 | return undefined 29 | } 30 | 31 | function getTypeName (value) { 32 | if (value === null) return 'null' 33 | if (Array.isArray(value)) return 'array' 34 | if (value instanceof Date) return 'date' 35 | if (value instanceof RegExp) return 'regexp' 36 | // eslint-disable-next-line valid-typeof 37 | if (typeof value === 'bigint') return 'bigint' 38 | // eslint-disable-next-line valid-typeof 39 | if (typeof value === 'symbol') return 'symbol' 40 | return typeof value 41 | } 42 | 43 | function returnIfAllowed (val, options, fallback) { 44 | if (options && Array.isArray(options.allowedTypes)) { 45 | const type = getTypeName(val) 46 | if (!options.allowedTypes.includes(type)) { 47 | return fallback 48 | } 49 | } 50 | return val 51 | } 52 | 53 | autoParse.use = function (fn) { 54 | if (typeof fn === 'function') plugins.push(fn) 55 | } 56 | 57 | autoParse.setErrorHandler = function (fn) { 58 | globalOnError = typeof fn === 'function' ? fn : null 59 | } 60 | 61 | /** 62 | * 63 | * @name stripTrimLower 64 | * @function 65 | * @param {Value} value strip trim & lower case the string 66 | * @return {Value} parsed string 67 | * 68 | */ 69 | const _stripCache = new Map() 70 | const QUOTE_RE = /['"]/g 71 | function getStripRegex (chars) { 72 | let re = _stripCache.get(chars) 73 | if (!re) { 74 | const escaped = chars.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') 75 | re = new RegExp('^[' + escaped + ']+') 76 | _stripCache.set(chars, re) 77 | } 78 | return re 79 | } 80 | 81 | function stripTrimLower (value, options = {}) { 82 | if (options.stripStartChars && typeof value === 'string') { 83 | const chars = Array.isArray(options.stripStartChars) 84 | ? options.stripStartChars.join('') 85 | : String(options.stripStartChars) 86 | value = value.replace(getStripRegex(chars), '') 87 | } 88 | return value.replace(QUOTE_RE, '').trim().toLowerCase() 89 | } 90 | /** 91 | * 92 | * @name toBoolean 93 | * @function 94 | * @param {Value} value parse to boolean 95 | * @return {Boolean} parsed boolean 96 | * 97 | */ 98 | function toBoolean (value, options) { 99 | return checkBoolean(value, options) || false 100 | } 101 | /** 102 | * 103 | * @name checkBoolean 104 | * @function 105 | * @param {Value} value is any value 106 | * @return {Boolean} is a boolean value 107 | * 108 | */ 109 | function checkBoolean (value, options) { 110 | if (!value) { 111 | return false 112 | } 113 | if (typeof value === 'number' || typeof value === 'boolean') { 114 | return !!value 115 | } 116 | value = stripTrimLower(value, options) 117 | const extras = options && options.booleanSynonyms 118 | if (value === 'true' || value === '1' || (extras && (value === 'yes' || value === 'on'))) return true 119 | if (value === 'false' || value === '0' || (extras && (value === 'no' || value === 'off'))) return false 120 | return null 121 | } 122 | /** 123 | * 124 | * @name parseObject 125 | * @function 126 | * @param {Value} value parse object 127 | * @return {Value} parsed object 128 | * 129 | */ 130 | function parseObject (value, options) { 131 | if (Array.isArray(value)) { 132 | return value.map(function (n, key) { 133 | return autoParse(n, options) 134 | }) 135 | } else if (typeof value === 'object' || value.constructor === undefined) { 136 | for (const n in value) { 137 | value[n] = autoParse(value[n], options) 138 | } 139 | return value 140 | } 141 | return {} 142 | } 143 | /** 144 | * 145 | * @name parseFunction 146 | * @function 147 | * @param {Value} value function 148 | * @return {Value} returned value from the called value function 149 | * 150 | */ 151 | function parseFunction (value, options) { 152 | return autoParse(value(), options) 153 | } 154 | 155 | const CURRENCY_SYMBOLS = { 156 | '$': 'USD', 157 | '€': 'EUR', 158 | '£': 'GBP', 159 | '¥': 'JPY', 160 | 'A$': 'AUD', 161 | 'C$': 'CAD', 162 | 'CHF': 'CHF', 163 | 'HK$': 'HKD', 164 | '₹': 'INR', 165 | '₩': 'KRW' 166 | } 167 | 168 | function parseCurrencyString (str, options) { 169 | const symbols = Object.assign({}, CURRENCY_SYMBOLS, options && options.currencySymbols) 170 | for (const sym of Object.keys(symbols)) { 171 | const re = new RegExp('^' + sym.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') + '\\s?([0-9]+(?:[.,][0-9]+)?)$') 172 | const m = re.exec(str) 173 | if (m) { 174 | const num = parseFloat(m[1].replace(',', '.')) 175 | if (options && options.currencyAsObject) { 176 | return { value: num, currency: symbols[sym] } 177 | } 178 | return num 179 | } 180 | } 181 | return null 182 | } 183 | 184 | function parsePercentString (str, options) { 185 | const m = /^([-+]?\d+(?:\.\d+)?)%$/.exec(str) 186 | if (m) { 187 | const val = Number(m[1]) / 100 188 | if (options && options.percentAsObject) return { value: val, percent: true } 189 | return val 190 | } 191 | return null 192 | } 193 | 194 | function parseUnitString (str) { 195 | if (/^0[box]/i.test(str)) return null 196 | const m = /^(-?\d+(?:\.\d+)?)([a-z%]+)$/i.exec(str) 197 | if (m) return { value: Number(m[1]), unit: m[2] } 198 | return null 199 | } 200 | 201 | function parseRangeString (str, options) { 202 | const m = /^(-?\d+(?:\.\d+)?)\s*(?:\.\.|-)\s*(-?\d+(?:\.\d+)?)$/.exec(str) 203 | if (m) { 204 | const start = Number(m[1]) 205 | const end = Number(m[2]) 206 | if (options && options.rangeAsObject) return { start, end } 207 | const arr = [] 208 | const step = start <= end ? 1 : -1 209 | for (let i = start; step > 0 ? i <= end : i >= end; i += step) arr.push(i) 210 | return arr 211 | } 212 | return null 213 | } 214 | 215 | function parseTypedArrayString (str, options) { 216 | const m = /^([A-Za-z0-9]+Array)\[(.*)\]$/.exec(str) 217 | if (m && typeof globalThis[m[1]] === 'function') { 218 | const arr = autoParse(`[${m[2]}]`, options) 219 | if (Array.isArray(arr)) return new globalThis[m[1]](arr) 220 | } 221 | return null 222 | } 223 | 224 | function parseMapSetString (str, options) { 225 | if (/^Map:/i.test(str)) { 226 | const inner = str.slice(4).trim() 227 | const arr = autoParse(inner, options) 228 | return new Map(arr) 229 | } 230 | if (/^Set:/i.test(str)) { 231 | const inner = str.slice(4).trim() 232 | const arr = autoParse(inner, options) 233 | return new Set(arr) 234 | } 235 | return null 236 | } 237 | 238 | function parseDateTimeString (str) { 239 | const iso = /^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/ 240 | if (iso.test(str)) { 241 | const d = new Date(str) 242 | if (!Number.isNaN(d.getTime())) return d 243 | } 244 | let m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*([AP]M))?)?$/i.exec(str) 245 | if (m) { 246 | let [, month, day, year, h, min, sec, ap] = m 247 | const date = new Date(Number(year), Number(month) - 1, Number(day)) 248 | if (h !== undefined) { 249 | h = Number(h) 250 | if (ap) { 251 | ap = ap.toLowerCase() 252 | if (ap === 'pm' && h < 12) h += 12 253 | if (ap === 'am' && h === 12) h = 0 254 | } 255 | date.setHours(h, Number(min), Number(sec || 0), 0) 256 | } 257 | return date 258 | } 259 | m = /^(\d{1,2})-(\d{1,2})-(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/.exec(str) 260 | if (m) { 261 | const [, day, month, year, h, min, sec] = m 262 | const date = new Date(Number(year), Number(month) - 1, Number(day)) 263 | if (h !== undefined) { 264 | date.setHours(Number(h), Number(min), Number(sec || 0), 0) 265 | } 266 | return date 267 | } 268 | m = /^(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*([AP]M))?$/i.exec(str) 269 | if (m) { 270 | let [, h, min, sec, ap] = m 271 | h = Number(h) 272 | if (ap) { 273 | ap = ap.toLowerCase() 274 | if (ap === 'pm' && h < 12) h += 12 275 | if (ap === 'am' && h === 12) h = 0 276 | } 277 | const date = new Date() 278 | date.setHours(h, Number(min), Number(sec || 0), 0) 279 | return date 280 | } 281 | return null 282 | } 283 | 284 | function parseUrlString (str) { 285 | try { 286 | return new URL(str) 287 | } catch (e) { 288 | return null 289 | } 290 | } 291 | 292 | function parseFilePathString (str) { 293 | const re = /^(?:[A-Za-z]:[\\/]|\\\\|\.{1,2}[\\/]|~[\\/]|\/)/ 294 | if (re.test(str)) { 295 | return str.replace(/\\+/g, '/').replace(/\/+/g, '/') 296 | } 297 | return null 298 | } 299 | 300 | function parseExpressionString (str) { 301 | if (/^[0-9+\-*/() %.]+$/.test(str) && /[+\-*/()%]/.test(str)) { 302 | try { 303 | // eslint-disable-next-line no-new-func 304 | return Function('return (' + str + ')')() 305 | } catch (e) {} 306 | } 307 | return null 308 | } 309 | 310 | function parseFunctionString (str) { 311 | if (/^\s*(\(?\w*\)?\s*=>)/.test(str)) { 312 | try { 313 | // eslint-disable-next-line no-new-func 314 | return Function('return (' + str + ')')() 315 | } catch (e) {} 316 | } 317 | return null 318 | } 319 | 320 | function expandEnvVars (str) { 321 | return str.replace(/\$([A-Z0-9_]+)/gi, function (m, name) { 322 | return process.env[name] || '' 323 | }) 324 | } 325 | /** 326 | * 327 | * @name parseType 328 | * @function 329 | * @param {Value} value inputed value 330 | * @param {Type} type inputed type 331 | * @return {Value} parsed type 332 | * 333 | */ 334 | function parseType (value, type, options = {}) { 335 | let typeName = type 336 | try { 337 | /** 338 | * Currently they send a string - handle String or Number or Boolean? 339 | */ 340 | if ((value && value.constructor === type) || (isType(value, type) && typeName !== 'object' && typeName !== 'array')) { 341 | return value 342 | } 343 | /** 344 | * Convert the constructor into a string 345 | */ 346 | if (type && type.name) { 347 | typeName = type.name.toLowerCase() 348 | } 349 | 350 | typeName = stripTrimLower(typeName) 351 | switch (typeName) { 352 | case 'string': 353 | if (typeof value === 'object') return JSON.stringify(value) 354 | return String(value) 355 | case 'function': 356 | if (isType(value, Function)) { 357 | return value 358 | } 359 | return function (cb) { 360 | if (typeof cb === 'function') { 361 | cb(value) 362 | } 363 | return value 364 | } 365 | case 'date': 366 | return new Date(value) 367 | case 'object': 368 | let jsonParsed 369 | if (typeof value === 'string' && /^['"]?[[{]/.test(value.trim())) { 370 | try { 371 | jsonParsed = JSON.parse(value) 372 | } catch (e) {} 373 | } 374 | if (isType(jsonParsed, Object) || isType(jsonParsed, Array)) { 375 | return autoParse(jsonParsed, options) 376 | } else if (!isType(jsonParsed, 'undefined')) { 377 | return {} 378 | } 379 | return parseObject(value, options) 380 | case 'boolean': 381 | return toBoolean(value, options) 382 | case 'number': 383 | if (options.parseCommaNumbers && typeof value === 'string') { 384 | const normalized = value.replace(/,/g, '') 385 | if (!Number.isNaN(Number(normalized))) return Number(normalized) 386 | } 387 | return Number(value) 388 | case 'bigint': 389 | return BigInt(value) 390 | case 'symbol': 391 | return Symbol(value) 392 | case 'undefined': 393 | return undefined 394 | case 'null': 395 | return null 396 | case 'array': 397 | return [value] 398 | case 'map': 399 | return new Map(autoParse(value, options)) 400 | case 'set': 401 | return new Set(autoParse(value, options)) 402 | case 'url': 403 | return new URL(value) 404 | case 'path': 405 | case 'filepath': 406 | return parseFilePathString(String(value)) || String(value) 407 | default: 408 | if (typeof type === 'function') { 409 | if (/Array$/.test(type.name)) { 410 | const arr = autoParse(value, options) 411 | // eslint-disable-next-line new-cap 412 | if (Array.isArray(arr)) return new type(arr) 413 | } 414 | return new type(value) // eslint-disable-line new-cap 415 | } 416 | throw new Error('Unsupported type.') 417 | } 418 | } catch (err) { 419 | if (options && typeof options.onError === 'function') { 420 | return returnIfAllowed(options.onError(err, value, type), options, value) 421 | } 422 | if (typeof globalOnError === 'function') { 423 | return returnIfAllowed(globalOnError(err, value, type), options, value) 424 | } 425 | throw err 426 | } 427 | } 428 | /** 429 | * autoParse 430 | * auto-parse any value you happen to send in 431 | * (String, Number, Boolean, Array, Object, Function, undefined and null). 432 | * You send it we will try to find a way to parse it. 433 | * We now support sending in a string of what type 434 | * (e.g. "boolean") or constructor (e.g. Boolean) 435 | * 436 | * Usage: 437 | * 438 | * ```js 439 | * autoParse({}) // => "object" 440 | * autoParse('42'); // => 42 441 | * autoParse.get('[]'); // => [] 442 | * ``` 443 | * 444 | * @name autoParse 445 | * @function 446 | * @param {Value} input The input value. 447 | * @param {Constructor|String} target The target type. 448 | * @return {String|Function|Date|Object|Boolean|Number|Undefined|Null|Array} 449 | */ 450 | function autoParse (value, typeOrOptions) { 451 | let type 452 | let options 453 | if (typeOrOptions && typeof typeOrOptions === 'object' && !Array.isArray(typeOrOptions) && !(typeOrOptions instanceof Function)) { 454 | options = typeOrOptions 455 | type = options.type 456 | } else { 457 | type = typeOrOptions 458 | options = {} 459 | } 460 | try { 461 | const pluginVal = runPlugins(value, type, options) 462 | if (pluginVal !== undefined) { 463 | return returnIfAllowed(pluginVal, options, value) 464 | } 465 | if (type) { 466 | return returnIfAllowed(parseType(value, type, options), options, value) 467 | } 468 | const originalValue = value 469 | if (typeof value === 'string' && options.stripStartChars) { 470 | const chars = Array.isArray(options.stripStartChars) 471 | ? options.stripStartChars.join('') 472 | : String(options.stripStartChars) 473 | value = value.replace(getStripRegex(chars), '') 474 | } 475 | /** 476 | * PRE RULE - check for null be cause null can be typeof object which can through off parsing 477 | */ 478 | if (value === null) { 479 | return returnIfAllowed(null, options, originalValue) 480 | } 481 | /** 482 | * TYPEOF SECTION - Use to check and do specific things based off of know the type 483 | * Check against undefined 484 | */ 485 | if (value === void 0) { 486 | return returnIfAllowed(undefined, options, originalValue) 487 | } 488 | if (value instanceof Date || value instanceof RegExp) { 489 | return returnIfAllowed(value, options, originalValue) 490 | } 491 | // eslint-disable-next-line valid-typeof 492 | if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint' || typeof value === 'symbol') { 493 | return returnIfAllowed(value, options, originalValue) 494 | } 495 | if (typeof value === 'function') { 496 | return returnIfAllowed(parseFunction(value, options), options, originalValue) 497 | } 498 | if (typeof value === 'object') { 499 | return returnIfAllowed(parseObject(value, options), options, originalValue) 500 | } 501 | /** 502 | * STRING SECTION - If we made it this far that means it is a string that we must do something with to parse 503 | */ 504 | if (value === 'NaN') { 505 | return returnIfAllowed(NaN, options, originalValue) 506 | } 507 | let jsonParsed = null 508 | const trimmed = typeof value === 'string' ? value.trim() : '' 509 | if (options.expandEnv) { 510 | const expanded = expandEnvVars(trimmed) 511 | if (expanded !== trimmed) { 512 | return returnIfAllowed(autoParse(expanded, options), options, originalValue) 513 | } 514 | } 515 | let mapSet 516 | if (options.parseMapSets) { 517 | mapSet = parseMapSetString(trimmed, options) 518 | if (mapSet) return returnIfAllowed(mapSet, options, originalValue) 519 | } 520 | if (/^['"]?[[{]/.test(trimmed)) { 521 | try { 522 | jsonParsed = JSON.parse(trimmed) 523 | } catch (e) { 524 | try { 525 | jsonParsed = JSON.parse( 526 | trimmed.replace(/(\\\\")|(\\")/gi, '"').replace(/(\\n|\\\\n)/gi, '').replace(/(^"|"$)|(^'|'$)/gi, '') 527 | ) 528 | } catch (e) { 529 | try { 530 | jsonParsed = JSON.parse(trimmed.replace(/'/gi, '"')) 531 | } catch (e) {} 532 | } 533 | } 534 | } 535 | if (jsonParsed && typeof jsonParsed === 'object') { 536 | return returnIfAllowed(autoParse(jsonParsed, options), options, originalValue) 537 | } 538 | if (options.parseTypedArrays) { 539 | const typedArr = parseTypedArrayString(trimmed, options) 540 | if (typedArr) return returnIfAllowed(typedArr, options, originalValue) 541 | } 542 | if (options.parseCurrency) { 543 | const currency = parseCurrencyString(trimmed, options) 544 | if (currency !== null) return returnIfAllowed(currency, options, originalValue) 545 | } 546 | if (options.parsePercent) { 547 | const percent = parsePercentString(trimmed, options) 548 | if (percent !== null) return returnIfAllowed(percent, options, originalValue) 549 | } 550 | if (options.parseUnits) { 551 | const unit = parseUnitString(trimmed) 552 | if (unit) return returnIfAllowed(unit, options, originalValue) 553 | } 554 | if (options.parseRanges) { 555 | const range = parseRangeString(trimmed, options) 556 | if (range) return returnIfAllowed(range, options, originalValue) 557 | } 558 | if (options.parseExpressions) { 559 | const expr = parseExpressionString(trimmed) 560 | if (expr !== null) return returnIfAllowed(expr, options, originalValue) 561 | } 562 | if (options.parseFunctionStrings) { 563 | const fn = parseFunctionString(trimmed) 564 | if (fn) return returnIfAllowed(fn, options, originalValue) 565 | } 566 | if (options.parseDates) { 567 | const dt = parseDateTimeString(trimmed) 568 | if (dt) return returnIfAllowed(dt, options, originalValue) 569 | } 570 | if (options.parseUrls) { 571 | const u = parseUrlString(trimmed) 572 | if (u) return returnIfAllowed(u, options, originalValue) 573 | } 574 | if (options.parseFilePaths) { 575 | const p = parseFilePathString(trimmed) 576 | if (p) return returnIfAllowed(p, options, originalValue) 577 | } 578 | value = stripTrimLower(trimmed, Object.assign({}, options, { stripStartChars: false })) 579 | if (value === 'undefined' || value === '') { 580 | return returnIfAllowed(undefined, options, originalValue) 581 | } 582 | if (value === 'null') { 583 | return returnIfAllowed(null, options, originalValue) 584 | } 585 | /** 586 | * Order Matter because if it is a one or zero boolean will come back with a awnser too. if you want it to be a boolean you must specify 587 | */ 588 | let numValue = value 589 | if (options.parseCommaNumbers && typeof numValue === 'string' && numValue.includes(',')) { 590 | const normalized = numValue.replace(/,/g, '') 591 | if (!Number.isNaN(Number(normalized))) { 592 | numValue = normalized 593 | } 594 | } 595 | const num = Number(numValue) 596 | if (!Number.isNaN(num)) { 597 | if (options.preserveLeadingZeros && /^0+\d+$/.test(value)) { 598 | return returnIfAllowed(String(originalValue), options, originalValue) 599 | } 600 | return returnIfAllowed(num, options, originalValue) 601 | } 602 | const boo = checkBoolean(value, options) 603 | if (boo !== null) { 604 | return returnIfAllowed(boo, options, originalValue) 605 | } 606 | /** 607 | * DEFAULT SECTION - bascially if we catch nothing we assume that you just have a string 608 | */ 609 | // if string - convert to "" 610 | return returnIfAllowed(String(originalValue), options, originalValue) 611 | } catch (err) { 612 | if (options && typeof options.onError === 'function') { 613 | return returnIfAllowed(options.onError(err, value, type), options, value) 614 | } 615 | if (typeof globalOnError === 'function') { 616 | return returnIfAllowed(globalOnError(err, value, type), options, value) 617 | } 618 | throw err 619 | } 620 | } 621 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | const autoParse = require('../index.js') 2 | const chaiAssert = require('chai').assert 3 | 4 | function Color (inputColor) { 5 | this.color = inputColor 6 | } 7 | 8 | function parse (ret) { 9 | // Test case from here - https://github.com/greenpioneersolutions/auto-parse/issues/16 10 | return Object.keys(ret).sort().reduce((result, key) => { 11 | const value = ret[key] 12 | result[key] = value 13 | return result 14 | }, Object.create(null)) 15 | } 16 | 17 | describe('Auto Parse', function () { 18 | describe('Strings', function () { 19 | it('Green Pioneer', function () { 20 | chaiAssert.equal(autoParse('Green Pioneer'), 'Green Pioneer') 21 | chaiAssert.typeOf(autoParse('Green Pioneer'), 'string') 22 | }) 23 | it('Green Pioneer Solutions', function () { 24 | chaiAssert.equal(autoParse('Green Pioneer Solutions'), 'Green Pioneer Solutions') 25 | chaiAssert.typeOf(autoParse('Green Pioneer Solutions'), 'string') 26 | }) 27 | it('True or False', function () { 28 | chaiAssert.equal(autoParse('True or False'), 'True or False') 29 | chaiAssert.typeOf(autoParse('True or False'), 'string') 30 | }) 31 | it('123 Numbers', function () { 32 | chaiAssert.equal(autoParse('123 Numbers'), '123 Numbers') 33 | chaiAssert.typeOf(autoParse('123 Numbers'), 'string') 34 | }) 35 | it('$group', function () { 36 | chaiAssert.equal(autoParse('$group'), '$group') 37 | chaiAssert.typeOf(autoParse('$group'), 'string') 38 | }) 39 | it('a11y', function () { 40 | chaiAssert.equal(autoParse('a11y'), 'a11y') 41 | chaiAssert.typeOf(autoParse('a11y'), 'string') 42 | }) 43 | it('$group:test', function () { 44 | chaiAssert.equal(autoParse('$group:test'), '$group:test') 45 | chaiAssert.typeOf(autoParse('$group:test'), 'string') 46 | }) 47 | it('got a question?', function () { 48 | chaiAssert.equal(autoParse('got a question?'), 'got a question?') 49 | chaiAssert.typeOf(autoParse('got a question?'), 'string') 50 | }) 51 | }) 52 | describe('Number', function () { 53 | it('hexadecimals String to Number', function () { 54 | chaiAssert.equal(autoParse('0xFF'), 255) 55 | }) 56 | it('dots String to Number', function () { 57 | chaiAssert.equal(autoParse('.42'), 0.42) 58 | }) 59 | it('octals String to Number', function () { 60 | chaiAssert.equal(autoParse('0o123'), 83) 61 | }) 62 | it('binary number String to Number', function () { 63 | chaiAssert.equal(autoParse('0b1101'), 13) 64 | }) 65 | it('Exponent String to Number', function () { 66 | chaiAssert.equal(autoParse('7e3'), 7000) 67 | }) 68 | it('26 String to Number', function () { 69 | chaiAssert.equal(autoParse('26'), 26) 70 | chaiAssert.typeOf(autoParse('26'), 'number') 71 | }) 72 | it('1 String to Number', function () { 73 | chaiAssert.equal(autoParse('1'), 1) 74 | chaiAssert.typeOf(autoParse('1'), 'number') 75 | }) 76 | it('0 String to Number', function () { 77 | chaiAssert.equal(autoParse('0'), 0) 78 | chaiAssert.typeOf(autoParse('0'), 'number') 79 | }) 80 | it('preserves leading zeros when requested', function () { 81 | chaiAssert.equal( 82 | autoParse('0000035', { preserveLeadingZeros: true }), 83 | '0000035' 84 | ) 85 | chaiAssert.typeOf( 86 | autoParse('0000035', { preserveLeadingZeros: true }), 87 | 'string' 88 | ) 89 | }) 90 | it('respects allowedTypes option', function () { 91 | chaiAssert.equal( 92 | autoParse('42', { allowedTypes: ['string'] }), 93 | '42' 94 | ) 95 | chaiAssert.typeOf( 96 | autoParse('42', { allowedTypes: ['string'] }), 97 | 'string' 98 | ) 99 | }) 100 | it('allows numbers when included in allowedTypes', function () { 101 | chaiAssert.strictEqual(autoParse('42', { allowedTypes: ['number'] }), 42) 102 | chaiAssert.typeOf(autoParse('42', { allowedTypes: ['number'] }), 'number') 103 | }) 104 | it('returns original when parsed type not allowed', function () { 105 | chaiAssert.strictEqual(autoParse('true', { allowedTypes: ['number'] }), 'true') 106 | }) 107 | it('supports arrays and objects in allowedTypes', function () { 108 | chaiAssert.deepEqual(autoParse('[1,2]', { allowedTypes: ['array'] }), [1, 2]) 109 | chaiAssert.deepEqual(autoParse('{"a":1}', { allowedTypes: ['object'] }), { a: 1 }) 110 | }) 111 | it('strips starting characters before parsing', function () { 112 | chaiAssert.equal( 113 | autoParse('#123', { stripStartChars: '#' }), 114 | 123 115 | ) 116 | }) 117 | it('strips multiple characters', function () { 118 | chaiAssert.equal(autoParse('$$$7', { stripStartChars: '$' }), 7) 119 | chaiAssert.equal(autoParse("'42", { stripStartChars: "'" }), 42) 120 | }) 121 | it('strips from a set of characters', function () { 122 | chaiAssert.equal( 123 | autoParse('@@100', { stripStartChars: ['@', '#'] }), 124 | 100 125 | ) 126 | chaiAssert.equal( 127 | autoParse('#@50', { stripStartChars: ['@', '#'] }), 128 | 50 129 | ) 130 | }) 131 | it('parses numbers with commas when enabled', function () { 132 | chaiAssert.equal( 133 | autoParse('385,134', { parseCommaNumbers: true }), 134 | 385134 135 | ) 136 | }) 137 | it('handles multi-comma numbers', function () { 138 | chaiAssert.equal( 139 | autoParse('1,234,567', { parseCommaNumbers: true }), 140 | 1234567 141 | ) 142 | chaiAssert.equal( 143 | autoParse('10,000,000.01', { parseCommaNumbers: true }), 144 | 10000000.01 145 | ) 146 | }) 147 | it('ignores comma parsing when disabled', function () { 148 | chaiAssert.strictEqual(autoParse('1,234,567'), '1,234,567') 149 | }) 150 | it('26 Number to Number', function () { 151 | chaiAssert.equal(autoParse(26), 26) 152 | chaiAssert.typeOf(autoParse(26), 'number') 153 | }) 154 | it('1 Number to Number', function () { 155 | chaiAssert.equal(autoParse(1), 1) 156 | chaiAssert.typeOf(autoParse(1), 'number') 157 | }) 158 | it('0 Number to Number', function () { 159 | chaiAssert.equal(autoParse(0), 0) 160 | chaiAssert.typeOf(autoParse(0), 'number') 161 | }) 162 | }) 163 | describe('Boolean', function () { 164 | it('true Boolean to Boolean', function () { 165 | chaiAssert.equal(autoParse(true), true) 166 | chaiAssert.typeOf(autoParse(true), 'boolean') 167 | }) 168 | it('false Boolean to Boolean', function () { 169 | chaiAssert.equal(autoParse(false), false) 170 | chaiAssert.typeOf(autoParse(false), 'boolean') 171 | }) 172 | it('true String to Boolean', function () { 173 | chaiAssert.equal(autoParse('true'), true) 174 | chaiAssert.typeOf(autoParse('true'), 'boolean') 175 | }) 176 | it('false String to Boolean', function () { 177 | chaiAssert.equal(autoParse('false'), false) 178 | chaiAssert.typeOf(autoParse('false'), 'boolean') 179 | }) 180 | it('True String to Boolean', function () { 181 | chaiAssert.equal(autoParse('True'), true) 182 | chaiAssert.typeOf(autoParse('True'), 'boolean') 183 | }) 184 | it('TrUe String o Boolean', function () { 185 | chaiAssert.equal(autoParse('TrUe'), true) 186 | chaiAssert.typeOf(autoParse('TrUe'), 'boolean') 187 | }) 188 | }) 189 | describe('Array', function () { 190 | it(`"['2332','2343','2343','2342','3233']"`, function () { 191 | const data = "['2332','2343','2343','2342','3233']" 192 | chaiAssert.equal(autoParse(data)[0], 2332) 193 | chaiAssert.typeOf(autoParse(data)[0], 'number') 194 | chaiAssert.equal(autoParse(data)[1], 2343) 195 | chaiAssert.typeOf(autoParse(data)[1], 'number') 196 | chaiAssert.equal(autoParse(data)[2], 2343) 197 | chaiAssert.typeOf(autoParse(data)[2], 'number') 198 | chaiAssert.equal(autoParse(data)[3], 2342) 199 | chaiAssert.typeOf(autoParse(data)[3], 'number') 200 | chaiAssert.equal(autoParse(data)[4], 3233) 201 | chaiAssert.typeOf(autoParse(data)[4], 'number') 202 | }) 203 | it(`'["80", 92, "23", "TruE",false]'`, function () { 204 | const data = `'["80", 92, "23", "TruE",false]'` 205 | chaiAssert.equal(autoParse(data)[0], 80) 206 | chaiAssert.typeOf(autoParse(data)[0], 'number') 207 | chaiAssert.equal(autoParse(data)[1], 92) 208 | chaiAssert.typeOf(autoParse(data)[1], 'number') 209 | chaiAssert.equal(autoParse(data)[2], 23) 210 | chaiAssert.typeOf(autoParse(data)[2], 'number') 211 | chaiAssert.equal(autoParse(data)[3], true) 212 | chaiAssert.typeOf(autoParse(data)[3], 'boolean') 213 | chaiAssert.equal(autoParse(data)[4], false) 214 | chaiAssert.typeOf(autoParse(data)[4], 'boolean') 215 | }) 216 | it(`'["80", 92, "23", "TruE",false]'`, function () { 217 | const data = '["80", 92, "23", "TruE",false]' 218 | chaiAssert.equal(autoParse(data)[0], 80) 219 | chaiAssert.typeOf(autoParse(data)[0], 'number') 220 | chaiAssert.equal(autoParse(data)[1], 92) 221 | chaiAssert.typeOf(autoParse(data)[1], 'number') 222 | chaiAssert.equal(autoParse(data)[2], 23) 223 | chaiAssert.typeOf(autoParse(data)[2], 'number') 224 | chaiAssert.equal(autoParse(data)[3], true) 225 | chaiAssert.typeOf(autoParse(data)[3], 'boolean') 226 | chaiAssert.equal(autoParse(data)[4], false) 227 | chaiAssert.typeOf(autoParse(data)[4], 'boolean') 228 | }) 229 | it(`"['80', 92, '23', 'TruE',false]"`, function () { 230 | const data = "['80', 92, '23', 'TruE',false]" 231 | chaiAssert.equal(autoParse(data)[0], 80) 232 | chaiAssert.typeOf(autoParse(data)[0], 'number') 233 | chaiAssert.equal(autoParse(data)[1], 92) 234 | chaiAssert.typeOf(autoParse(data)[1], 'number') 235 | chaiAssert.equal(autoParse(data)[2], 23) 236 | chaiAssert.typeOf(autoParse(data)[2], 'number') 237 | chaiAssert.equal(autoParse(data)[3], true) 238 | chaiAssert.typeOf(autoParse(data)[3], 'boolean') 239 | chaiAssert.equal(autoParse(data)[4], false) 240 | chaiAssert.typeOf(autoParse(data)[4], 'boolean') 241 | }) 242 | it('`["80", 92, "23", "TruE",false]`', function () { 243 | const data = `["80", 92, "23", "TruE", false]` 244 | chaiAssert.equal(autoParse(data)[0], 80) 245 | chaiAssert.typeOf(autoParse(data)[0], 'number') 246 | chaiAssert.equal(autoParse(data)[1], 92) 247 | chaiAssert.typeOf(autoParse(data)[1], 'number') 248 | chaiAssert.equal(autoParse(data)[2], 23) 249 | chaiAssert.typeOf(autoParse(data)[2], 'number') 250 | chaiAssert.equal(autoParse(data)[3], true) 251 | chaiAssert.typeOf(autoParse(data)[3], 'boolean') 252 | chaiAssert.equal(autoParse(data)[4], false) 253 | chaiAssert.typeOf(autoParse(data)[4], 'boolean') 254 | }) 255 | it('["80", 92, "23", "TruE",false]', function () { 256 | const data = ['80', 92, '23', 'TruE', false] 257 | chaiAssert.equal(autoParse(data)[0], 80) 258 | chaiAssert.typeOf(autoParse(data)[0], 'number') 259 | chaiAssert.equal(autoParse(data)[1], 92) 260 | chaiAssert.typeOf(autoParse(data)[1], 'number') 261 | chaiAssert.equal(autoParse(data)[2], 23) 262 | chaiAssert.typeOf(autoParse(data)[2], 'number') 263 | chaiAssert.equal(autoParse(data)[3], true) 264 | chaiAssert.typeOf(autoParse(data)[3], 'boolean') 265 | chaiAssert.equal(autoParse(data)[4], false) 266 | chaiAssert.typeOf(autoParse(data)[4], 'boolean') 267 | }) 268 | }) 269 | describe('Object', function () { 270 | const data = { 271 | name: 'jason', 272 | age: '50', 273 | admin: 'true', 274 | parents: [ 275 | { 276 | name: 'Alice', 277 | age: '75', 278 | dead: 'false' 279 | }, 280 | { 281 | name: 'Bob', 282 | age: '80', 283 | dead: 'true' 284 | } 285 | ], 286 | grade: ['80', '90', '100'] 287 | } 288 | it(JSON.stringify(data), function () { 289 | chaiAssert.equal(autoParse(data).name, 'jason') 290 | chaiAssert.typeOf(autoParse(data).name, 'string') 291 | chaiAssert.equal(autoParse(data).age, 50) 292 | chaiAssert.typeOf(autoParse(data).age, 'number') 293 | chaiAssert.equal(autoParse(data).admin, true) 294 | chaiAssert.typeOf(autoParse(data).admin, 'boolean') 295 | chaiAssert.equal(autoParse(data).grade[0], 80) 296 | chaiAssert.typeOf(autoParse(data).grade[0], 'number') 297 | chaiAssert.equal(autoParse(data).grade[1], 90) 298 | chaiAssert.typeOf(autoParse(data).grade[1], 'number') 299 | chaiAssert.equal(autoParse(data).grade[2], 100) 300 | chaiAssert.typeOf(autoParse(data).grade[2], 'number') 301 | chaiAssert.typeOf(autoParse(data).grade[2], 'number') 302 | chaiAssert.equal(autoParse(data).parents[0].name, 'Alice') 303 | chaiAssert.equal(autoParse(data).parents[0].age, 75) 304 | }) 305 | it('parsing Object.create(null) Objects', function () { 306 | const value = autoParse(parse({ order: 'asc', orderBy: '1', filterOn: 'true' })) 307 | chaiAssert.deepEqual(value, { order: 'asc', orderBy: 1, filterOn: true }) 308 | chaiAssert.typeOf(value.order, 'string') 309 | chaiAssert.typeOf(value.orderBy, 'number') 310 | chaiAssert.typeOf(value.filterOn, 'boolean') 311 | }) 312 | }) 313 | describe('Date', function () { 314 | it('new Date', function () { 315 | const value = new Date // eslint-disable-line 316 | chaiAssert.equal(autoParse(value), value) 317 | chaiAssert.instanceOf(autoParse(value), Date) 318 | }) 319 | it('new Date()', function () { 320 | const value = new Date() 321 | chaiAssert.equal(autoParse(value), value) 322 | chaiAssert.instanceOf(autoParse(value), Date) 323 | }) 324 | it('new Date()', function () { 325 | const value = new Date('1989-12-01') 326 | chaiAssert.deepEqual(autoParse(value), value) 327 | chaiAssert.instanceOf(autoParse(value), Date) 328 | }) 329 | }) 330 | describe('Regex', function () { 331 | it('/\w+/', function () { // eslint-disable-line 332 | const regex1 = /\w+/ 333 | chaiAssert.equal(autoParse(regex1), regex1) 334 | chaiAssert.instanceOf(autoParse(regex1), RegExp) 335 | }) 336 | it('new RegExp("\\w+")', function () { 337 | const regex2 = new RegExp('\\w+') 338 | chaiAssert.equal(autoParse(regex2), regex2) 339 | chaiAssert.instanceOf(autoParse(regex2), RegExp) 340 | }) 341 | }) 342 | describe('Function', function () { 343 | it('return "9" to 9', function () { 344 | const data = function () { 345 | return '9' 346 | } 347 | chaiAssert.equal(autoParse(data), 9) 348 | chaiAssert.typeOf(autoParse(data), 'number') 349 | }) 350 | it('return "jason" to "jason"', function () { 351 | const data = function () { 352 | return 'jason' 353 | } 354 | chaiAssert.equal(autoParse(data), 'jason') 355 | chaiAssert.typeOf(autoParse(data), 'string') 356 | }) 357 | it('return "true" to true', function () { 358 | const data = function () { 359 | return 'true' 360 | } 361 | chaiAssert.equal(autoParse(data), true) 362 | chaiAssert.typeOf(autoParse(data), 'boolean') 363 | }) 364 | }) 365 | describe('Undefined', function () { 366 | it('undefined to undefined', function () { 367 | chaiAssert.equal(autoParse(undefined), undefined) 368 | chaiAssert.typeOf(autoParse(undefined), 'undefined') 369 | }) 370 | it('Undefined String to Undefined', function () { 371 | chaiAssert.equal(autoParse('Undefined'), undefined) 372 | chaiAssert.typeOf(autoParse('Undefined'), 'undefined') 373 | }) 374 | it(' "" ', function () { 375 | chaiAssert.equal(autoParse(''), undefined) 376 | chaiAssert.typeOf(autoParse(''), 'undefined') 377 | }) 378 | }) 379 | describe('Null', function () { 380 | it('null to Null', function () { 381 | chaiAssert.equal(autoParse('null'), null) 382 | chaiAssert.typeOf(autoParse('null'), 'null') 383 | }) 384 | it('Null String to Null', function () { 385 | chaiAssert.equal(autoParse('Null'), null) 386 | chaiAssert.typeOf(autoParse('Null'), 'null') 387 | }) 388 | }) 389 | describe('parse as json', function () { 390 | it('{}', function () { 391 | chaiAssert.deepEqual(autoParse('{}'), {}) 392 | }) 393 | it('[]', function () { 394 | chaiAssert.deepEqual(autoParse('[]'), []) 395 | }) 396 | it('["42"]', function () { 397 | chaiAssert.deepEqual(autoParse('["42"]'), [42]) 398 | }) 399 | it('{test:"{"name":"gps"}"}', function () { 400 | chaiAssert.deepEqual(autoParse({test:'{\\"name\\": \"greenpioneer\",\n \"company\": true,\n \\"customers\\": 1000}'}), { // eslint-disable-line 401 | test: { 402 | name: 'greenpioneer', 403 | company: true, 404 | customers: 1000 405 | } 406 | }) 407 | }) 408 | it('\\n', function () { 409 | chaiAssert.deepEqual(autoParse('{\\"name\\": \"greenpioneer\",\n \"company\": true,\n \\"customers\\": 1000}'), { // eslint-disable-line 410 | name: 'greenpioneer', 411 | company: true, 412 | customers: 1000 413 | }) 414 | }) 415 | it('\\"', function () { 416 | chaiAssert.deepEqual(autoParse('{\\"name\\": \"greenpioneer\",\"company\": true,\\"customers\\": 1000}'), { // eslint-disable-line 417 | name: 'greenpioneer', 418 | company: true, 419 | customers: 1000 420 | }) 421 | }) 422 | it('"{}"', function () { 423 | chaiAssert.deepEqual(autoParse('"{"name": "greenpioneer","company": true,"customers": 1000}"'), { // eslint-disable-line 424 | name: 'greenpioneer', 425 | company: true, 426 | customers: 1000 427 | }) 428 | }) 429 | }) 430 | describe('handle NaNs', function () { 431 | it('NaN', function () { 432 | chaiAssert.deepEqual(autoParse(NaN), NaN) 433 | }) 434 | it('NaN', function () { 435 | chaiAssert.deepEqual(autoParse('NaN'), NaN) 436 | }) 437 | }) 438 | describe('Convert Type', function () { 439 | it('to color', function () { 440 | chaiAssert.deepEqual(autoParse('#AAA', Color), { 441 | color: '#AAA' 442 | }) 443 | }) 444 | it('to Date', function () { 445 | chaiAssert.deepEqual(autoParse('1989-12-01', Date), new Date('1989-12-01')) 446 | chaiAssert.instanceOf(autoParse('1989-12-01', Date), Date) 447 | chaiAssert.deepEqual(autoParse('1989-12-01', 'date'), new Date('1989-12-01')) 448 | chaiAssert.instanceOf(autoParse('1989-12-01', 'date'), Date) 449 | }) 450 | it('* to String', function () { 451 | const data = function () { 452 | return '9' 453 | } 454 | chaiAssert.equal(autoParse(1234, String), '1234') 455 | chaiAssert.equal(autoParse('1234', 'String'), '1234') 456 | chaiAssert.equal(autoParse(1234, 'String'), '1234') 457 | chaiAssert.equal(autoParse('true', 'String'), 'true') 458 | chaiAssert.equal(autoParse(true, 'String'), 'true') 459 | chaiAssert.equal(autoParse([], 'String'), '[]') 460 | chaiAssert.equal(autoParse({}, 'String'), '{}') 461 | chaiAssert.equal(autoParse(data, 'String'), 'function () {\n return \'9\';\n }') 462 | }) 463 | it('function to function', function () { 464 | const data = function () { 465 | return '9' 466 | } 467 | chaiAssert.equal(autoParse(data, 'function'), data) 468 | }) 469 | it('* to object', function () { 470 | const data = { 471 | name: 'jason', 472 | age: '50', 473 | admin: 'true', 474 | parents: [ 475 | { 476 | name: 'Alice', 477 | age: '75', 478 | dead: 'false' 479 | }, 480 | { 481 | name: 'Bob', 482 | age: '80', 483 | dead: 'true' 484 | } 485 | ], 486 | grade: ['80', '90', '100'] 487 | } 488 | chaiAssert.equal(autoParse(data, 'object').name, 'jason') 489 | chaiAssert.equal(autoParse(data, Object).name, 'jason') 490 | chaiAssert.typeOf(autoParse(data, 'object').name, 'string') 491 | chaiAssert.equal(autoParse(data, 'object').age, 50) 492 | chaiAssert.typeOf(autoParse(data, 'object').age, 'number') 493 | chaiAssert.equal(autoParse(data, 'object').admin, true) 494 | chaiAssert.typeOf(autoParse(data, 'object').admin, 'boolean') 495 | chaiAssert.equal(autoParse(data, 'object').grade[0], 80) 496 | chaiAssert.typeOf(autoParse(data, 'object').grade[0], 'number') 497 | chaiAssert.equal(autoParse(data, 'object').grade[1], 90) 498 | chaiAssert.typeOf(autoParse(data, 'object').grade[1], 'number') 499 | chaiAssert.equal(autoParse(data, 'object').grade[2], 100) 500 | chaiAssert.typeOf(autoParse(data, 'object').grade[2], 'number') 501 | chaiAssert.equal(autoParse(data, 'object').parents[0].name, 'Alice') 502 | chaiAssert.equal(autoParse(data, 'object').parents[0].age, 75) 503 | chaiAssert.deepEqual(autoParse('{}', 'object'), {}) 504 | chaiAssert.deepEqual(autoParse('[]', 'object'), []) 505 | chaiAssert.deepEqual(autoParse('["42"]', 'object'), [42]) 506 | }) 507 | it('* to boolean', function () { 508 | chaiAssert.equal(autoParse(1, Boolean), true) 509 | chaiAssert.equal(autoParse(1, 'Boolean'), true) 510 | chaiAssert.equal(autoParse(0, 'Boolean'), false) 511 | chaiAssert.equal(autoParse('1', 'Boolean'), true) 512 | chaiAssert.equal(autoParse('0', 'Boolean'), false) 513 | chaiAssert.equal(autoParse('TrUe', 'Boolean'), true) 514 | chaiAssert.equal(autoParse('False', 'Boolean'), false) 515 | chaiAssert.equal(autoParse(true, 'Boolean'), true) 516 | chaiAssert.equal(autoParse(false, 'Boolean'), false) 517 | chaiAssert.equal(autoParse('foo', 'Boolean'), false) 518 | }) 519 | it('* to number', function () { 520 | chaiAssert.equal(autoParse(1, 'Number'), 1) 521 | chaiAssert.equal(autoParse(0, 'Number'), 0) 522 | chaiAssert.equal(autoParse(0, Number), 0) 523 | chaiAssert.equal(autoParse('1', Number), 1) 524 | chaiAssert.equal(autoParse('1', 'Number'), 1) 525 | chaiAssert.equal(autoParse('0', 'Number'), 0) 526 | chaiAssert.equal(autoParse('0o123', 'Number'), 83) 527 | chaiAssert.equal(autoParse('0b10', 'Number'), 2) 528 | chaiAssert.equal(autoParse('0xFF', 'Number'), 255) 529 | chaiAssert.equal(autoParse('7e3', 'Number'), 7000) 530 | chaiAssert.equal(autoParse('.42', 'Number'), 0.42) 531 | }) 532 | it('* to undefined', function () { 533 | chaiAssert.equal(autoParse(1, 'Undefined'), undefined) 534 | chaiAssert.equal(autoParse(0, 'Undefined'), undefined) 535 | chaiAssert.equal(autoParse('1', 'Undefined'), undefined) 536 | chaiAssert.equal(autoParse('0', 'Undefined'), undefined) 537 | }) 538 | it('* to null', function () { 539 | chaiAssert.equal(autoParse(1, 'Null'), null) 540 | chaiAssert.equal(autoParse(0, 'Null'), null) 541 | chaiAssert.equal(autoParse('1', 'Null'), null) 542 | chaiAssert.equal(autoParse('0', 'Null'), null) 543 | }) 544 | it('same type to same type', function () { 545 | chaiAssert.deepEqual(autoParse([42], Array), [42]) 546 | chaiAssert.deepEqual(autoParse({ name: 'greenpioneer' }, Object), { name: 'greenpioneer' }) 547 | chaiAssert.equal(autoParse(true, Boolean), true) 548 | chaiAssert.equal(autoParse('test', String), 'test') 549 | chaiAssert.equal(autoParse(1234, Number), 1234) 550 | }) 551 | }) 552 | }) 553 | -------------------------------------------------------------------------------- /dist/auto-parse.map.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.autoParse = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i "object" 161 | * autoParse('42'); // => 42 162 | * autoParse.get('[]'); // => [] 163 | * ``` 164 | * 165 | * @name autoParse 166 | * @function 167 | * @param {Value} input The input value. 168 | * @param {Constructor|String} target The target type. 169 | * @return {String|Function|Date|Object|Boolean|Number|Undefined|Null|Array} 170 | */ 171 | function autoParse (value, type) { 172 | if (type) { 173 | return parseType(value, type) 174 | } 175 | var orignalValue = value 176 | /** 177 | * PRE RULE - check for null be cause null can be typeof object which can through off parsing 178 | */ 179 | if (value === null) { 180 | return null 181 | } 182 | /** 183 | * TYPEOF SECTION - Use to check and do specific things based off of know the type 184 | * Check against undefined 185 | */ 186 | if (value === void 0) { 187 | return undefined 188 | } 189 | if (value instanceof Date || value instanceof RegExp) { 190 | return value 191 | } 192 | if (typeof value === 'number' || typeof value === 'boolean') { 193 | return value 194 | } 195 | if (typeof value === 'function') { 196 | return parseFunction(value) 197 | } 198 | if (typeof value === 'object') { 199 | return parseObject(value) 200 | } 201 | /** 202 | * STRING SECTION - If we made it this far that means it is a string that we must do something with to parse 203 | */ 204 | if (value === 'NaN') { 205 | return NaN 206 | } 207 | var jsonParsed = null 208 | try { 209 | jsonParsed = JSON.parse(value) 210 | } catch (e) { 211 | try { 212 | jsonParsed = JSON.parse( 213 | value.trim().replace(/(\\\\")|(\\")/gi, '"').replace(/(\\n|\\\\n)/gi, '').replace(/(^"|"$)|(^'|'$)/gi, '') 214 | ) 215 | } catch (e) { 216 | try { 217 | jsonParsed = JSON.parse( 218 | value.trim().replace(/'/gi, '"') 219 | ) 220 | } catch (e) {} 221 | } 222 | } 223 | if (jsonParsed && typeof jsonParsed === 'object') { 224 | return autoParse(jsonParsed) 225 | } 226 | value = stripTrimLower(value) 227 | if (value === 'undefined' || value === '') { 228 | return undefined 229 | } 230 | if (value === 'null') { 231 | return null 232 | } 233 | /** 234 | * Order Matter because if it is a one or zero boolean will come back with a awnser too. if you want it to be a boolean you must specify 235 | */ 236 | var num = Number(value) 237 | if (typpy(num, Number)) { 238 | return num 239 | } 240 | var boo = checkBoolean(value) 241 | if (typpy(boo, Boolean)) { 242 | return boo 243 | } 244 | /** 245 | * DEFAULT SECTION - bascially if we catch nothing we assume that you just have a string 246 | */ 247 | // if string - convert to "" 248 | return String(orignalValue) 249 | } 250 | 251 | },{"typpy":4}],2:[function(require,module,exports){ 252 | "use strict"; 253 | 254 | var noop6 = require("noop6"); 255 | 256 | (function () { 257 | var NAME_FIELD = "name"; 258 | 259 | if (typeof noop6.name === "string") { 260 | return; 261 | } 262 | 263 | try { 264 | Object.defineProperty(Function.prototype, NAME_FIELD, { 265 | get: function get() { 266 | var nameMatch = this.toString().trim().match(/^function\s*([^\s(]+)/); 267 | var name = nameMatch ? nameMatch[1] : ""; 268 | Object.defineProperty(this, NAME_FIELD, { value: name }); 269 | return name; 270 | } 271 | }); 272 | } catch (e) {} 273 | })(); 274 | 275 | /** 276 | * functionName 277 | * Get the function name. 278 | * 279 | * @name functionName 280 | * @function 281 | * @param {Function} input The input function. 282 | * @returns {String} The function name. 283 | */ 284 | module.exports = function functionName(input) { 285 | return input.name; 286 | }; 287 | },{"noop6":3}],3:[function(require,module,exports){ 288 | "use strict"; 289 | 290 | module.exports = function () {}; 291 | },{}],4:[function(require,module,exports){ 292 | "use strict"; 293 | 294 | require("function.name"); 295 | 296 | /** 297 | * Typpy 298 | * Gets the type of the input value or compares it 299 | * with a provided type. 300 | * 301 | * Usage: 302 | * 303 | * ```js 304 | * Typpy({}) // => "object" 305 | * Typpy(42, Number); // => true 306 | * Typpy.get([], "array"); => true 307 | * ``` 308 | * 309 | * @name Typpy 310 | * @function 311 | * @param {Anything} input The input value. 312 | * @param {Constructor|String} target The target type. 313 | * It could be a string (e.g. `"array"`) or a 314 | * constructor (e.g. `Array`). 315 | * @return {String|Boolean} It returns `true` if the 316 | * input has the provided type `target` (if was provided), 317 | * `false` if the input type does *not* have the provided type 318 | * `target` or the stringified type of the input (always lowercase). 319 | */ 320 | function Typpy(input, target) { 321 | if (arguments.length === 2) { 322 | return Typpy.is(input, target); 323 | } 324 | return Typpy.get(input, true); 325 | } 326 | 327 | /** 328 | * Typpy.is 329 | * Checks if the input value has a specified type. 330 | * 331 | * @name Typpy.is 332 | * @function 333 | * @param {Anything} input The input value. 334 | * @param {Constructor|String} target The target type. 335 | * It could be a string (e.g. `"array"`) or a 336 | * constructor (e.g. `Array`). 337 | * @return {Boolean} `true`, if the input has the same 338 | * type with the target or `false` otherwise. 339 | */ 340 | Typpy.is = function (input, target) { 341 | return Typpy.get(input, typeof target === "string") === target; 342 | }; 343 | 344 | /** 345 | * Typpy.get 346 | * Gets the type of the input value. This is used internally. 347 | * 348 | * @name Typpy.get 349 | * @function 350 | * @param {Anything} input The input value. 351 | * @param {Boolean} str A flag to indicate if the return value 352 | * should be a string or not. 353 | * @return {Constructor|String} The input value constructor 354 | * (if any) or the stringified type (always lowercase). 355 | */ 356 | Typpy.get = function (input, str) { 357 | 358 | if (typeof input === "string") { 359 | return str ? "string" : String; 360 | } 361 | 362 | if (null === input) { 363 | return str ? "null" : null; 364 | } 365 | 366 | if (undefined === input) { 367 | return str ? "undefined" : undefined; 368 | } 369 | 370 | if (input !== input) { 371 | return str ? "nan" : NaN; 372 | } 373 | 374 | return str ? input.constructor.name.toLowerCase() : input.constructor; 375 | }; 376 | 377 | module.exports = Typpy; 378 | },{"function.name":2}]},{},[1])(1) 379 | }); 380 | 381 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJpbmRleC5qcyIsIm5vZGVfbW9kdWxlcy9mdW5jdGlvbi5uYW1lL2xpYi9pbmRleC5qcyIsIm5vZGVfbW9kdWxlcy9ub29wNi9saWIvaW5kZXguanMiLCJub2RlX21vZHVsZXMvdHlwcHkvbGliL2luZGV4LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3hQQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2xDQTtBQUNBO0FBQ0E7O0FDRkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJnZW5lcmF0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlc0NvbnRlbnQiOlsiKGZ1bmN0aW9uKCl7ZnVuY3Rpb24gcihlLG4sdCl7ZnVuY3Rpb24gbyhpLGYpe2lmKCFuW2ldKXtpZighZVtpXSl7dmFyIGM9XCJmdW5jdGlvblwiPT10eXBlb2YgcmVxdWlyZSYmcmVxdWlyZTtpZighZiYmYylyZXR1cm4gYyhpLCEwKTtpZih1KXJldHVybiB1KGksITApO3ZhciBhPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIraStcIidcIik7dGhyb3cgYS5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGF9dmFyIHA9bltpXT17ZXhwb3J0czp7fX07ZVtpXVswXS5jYWxsKHAuZXhwb3J0cyxmdW5jdGlvbihyKXt2YXIgbj1lW2ldWzFdW3JdO3JldHVybiBvKG58fHIpfSxwLHAuZXhwb3J0cyxyLGUsbix0KX1yZXR1cm4gbltpXS5leHBvcnRzfWZvcih2YXIgdT1cImZ1bmN0aW9uXCI9PXR5cGVvZiByZXF1aXJlJiZyZXF1aXJlLGk9MDtpPHQubGVuZ3RoO2krKylvKHRbaV0pO3JldHVybiBvfXJldHVybiByfSkoKSIsIm1vZHVsZS5leHBvcnRzID0gYXV0b1BhcnNlXG5cbnZhciB0eXBweSA9IHJlcXVpcmUoJ3R5cHB5JylcblxuLyoqXG4gKlxuICogQG5hbWUgc3RyaXBUcmltTG93ZXJcbiAqIEBmdW5jdGlvblxuICogQHBhcmFtIHtWYWx1ZX0gdmFsdWUgc3RyaXAgdHJpbSAmIGxvd2VyIGNhc2UgdGhlIHN0cmluZ1xuICogQHJldHVybiB7VmFsdWV9IHBhcnNlZCBzdHJpbmdcbiAqXG4gKi9cbmZ1bmN0aW9uIHN0cmlwVHJpbUxvd2VyICh2YWx1ZSkge1xuICByZXR1cm4gdmFsdWUucmVwbGFjZSgvW1wiXCInJ10vaWcsICcnKS50cmltKCkudG9Mb3dlckNhc2UoKVxufVxuLyoqXG4gKlxuICogQG5hbWUgdG9Cb29sZWFuXG4gKiBAZnVuY3Rpb25cbiAqIEBwYXJhbSB7VmFsdWV9IHZhbHVlIHBhcnNlIHRvIGJvb2xlYW5cbiAqIEByZXR1cm4ge0Jvb2xlYW59IHBhcnNlZCBib29sZWFuXG4gKlxuICovXG5mdW5jdGlvbiB0b0Jvb2xlYW4gKHZhbHVlKSB7XG4gIHJldHVybiBjaGVja0Jvb2xlYW4odmFsdWUpIHx8IGZhbHNlXG59XG4vKipcbiAqXG4gKiBAbmFtZSBjaGVja0Jvb2xlYW5cbiAqIEBmdW5jdGlvblxuICogQHBhcmFtIHtWYWx1ZX0gdmFsdWUgaXMgYW55IHZhbHVlXG4gKiBAcmV0dXJuIHtCb29sZWFufSBpcyBhIGJvb2xlYW4gdmFsdWVcbiAqXG4gKi9cbmZ1bmN0aW9uIGNoZWNrQm9vbGVhbiAodmFsdWUpIHtcbiAgaWYgKCF2YWx1ZSkge1xuICAgIHJldHVybiBmYWxzZVxuICB9XG4gIGlmICh0eXBlb2YgdmFsdWUgPT09ICdudW1iZXInIHx8IHR5cGVvZiB2YWx1ZSA9PT0gJ2Jvb2xlYW4nKSB7XG4gICAgcmV0dXJuICEhdmFsdWVcbiAgfVxuICB2YWx1ZSA9IHN0cmlwVHJpbUxvd2VyKHZhbHVlKVxuICBpZiAodmFsdWUgPT09ICd0cnVlJyB8fCB2YWx1ZSA9PT0gJzEnKSByZXR1cm4gdHJ1ZVxuICBpZiAodmFsdWUgPT09ICdmYWxzZScgfHwgdmFsdWUgPT09ICcwJykgcmV0dXJuIGZhbHNlXG4gIHJldHVybiBudWxsXG59XG4vKipcbiAqXG4gKiBAbmFtZSBwYXJzZU9iamVjdFxuICogQGZ1bmN0aW9uXG4gKiBAcGFyYW0ge1ZhbHVlfSB2YWx1ZSBwYXJzZSBvYmplY3RcbiAqIEByZXR1cm4ge1ZhbHVlfSBwYXJzZWQgb2JqZWN0XG4gKlxuICovXG5mdW5jdGlvbiBwYXJzZU9iamVjdCAodmFsdWUpIHtcbiAgaWYgKHR5cHB5KHZhbHVlLCBBcnJheSkpIHtcbiAgICByZXR1cm4gdmFsdWUubWFwKGZ1bmN0aW9uIChuLCBrZXkpIHtcbiAgICAgIHJldHVybiBhdXRvUGFyc2UobilcbiAgICB9KVxuICB9IGVsc2UgaWYgKHR5cHB5KHZhbHVlLCBPYmplY3QpIHx8IHZhbHVlLmNvbnN0cnVjdG9yID09PSB1bmRlZmluZWQpIHtcbiAgICBmb3IgKHZhciBuIGluIHZhbHVlKSB7XG4gICAgICB2YWx1ZVtuXSA9IGF1dG9QYXJzZSh2YWx1ZVtuXSlcbiAgICB9XG4gICAgcmV0dXJuIHZhbHVlXG4gIH1cbiAgcmV0dXJuIHt9XG59XG4vKipcbiAqXG4gKiBAbmFtZSBwYXJzZUZ1bmN0aW9uXG4gKiBAZnVuY3Rpb25cbiAqIEBwYXJhbSB7VmFsdWV9IHZhbHVlIGZ1bmN0aW9uXG4gKiBAcmV0dXJuIHtWYWx1ZX0gcmV0dXJuZWQgdmFsdWUgZnJvbSB0aGUgY2FsbGVkIHZhbHVlIGZ1bmN0aW9uXG4gKlxuICovXG5mdW5jdGlvbiBwYXJzZUZ1bmN0aW9uICh2YWx1ZSkge1xuICByZXR1cm4gYXV0b1BhcnNlKHZhbHVlKCkpXG59XG4vKipcbiAqXG4gKiBAbmFtZSBwYXJzZVR5cGVcbiAqIEBmdW5jdGlvblxuICogQHBhcmFtIHtWYWx1ZX0gdmFsdWUgaW5wdXRlZCB2YWx1ZVxuICogQHBhcmFtIHtUeXBlfSB0eXBlICBpbnB1dGVkIHR5cGVcbiAqIEByZXR1cm4ge1ZhbHVlfSBwYXJzZWQgdHlwZVxuICpcbiAqL1xuZnVuY3Rpb24gcGFyc2VUeXBlICh2YWx1ZSwgdHlwZSkge1xuICAvKipcbiAgICogIEN1cnJlbnRseSB0aGV5IHNlbmQgYSBzdHJpbmcgLSBoYW5kbGUgU3RyaW5nIG9yIE51bWJlciBvciBCb29sZWFuP1xuICAgKi9cbiAgaWYgKCh2YWx1ZSAmJiB2YWx1ZS5jb25zdHJ1Y3RvciA9PT0gdHlwZSkgfHwgdHlwcHkodmFsdWUsIHR5cGUpKSB7XG4gICAgcmV0dXJuIHZhbHVlXG4gIH1cbiAgdmFyIHR5cGVOYW1lID0gdHlwZVxuICAvKipcbiAgICogQ29udmVydCB0aGUgY29uc3RydWN0b3IgaW50byBhIHN0cmluZ1xuICAgKi9cbiAgaWYgKHR5cGUgJiYgdHlwZS5uYW1lKSB7XG4gICAgdHlwZU5hbWUgPSB0eXBlLm5hbWUudG9Mb3dlckNhc2UoKVxuICB9XG5cbiAgdHlwZU5hbWUgPSBzdHJpcFRyaW1Mb3dlcih0eXBlTmFtZSlcbiAgc3dpdGNoICh0eXBlTmFtZSkge1xuICAgIGNhc2UgJ3N0cmluZyc6XG4gICAgICBpZiAodHlwZW9mIHZhbHVlID09PSAnb2JqZWN0JykgcmV0dXJuIEpTT04uc3RyaW5naWZ5KHZhbHVlKVxuICAgICAgcmV0dXJuIFN0cmluZyh2YWx1ZSlcbiAgICBjYXNlICdmdW5jdGlvbic6XG4gICAgICBpZiAodHlwcHkodmFsdWUsIEZ1bmN0aW9uKSkge1xuICAgICAgICByZXR1cm4gdmFsdWVcbiAgICAgIH1cbiAgICAgIHJldHVybiBmdW5jdGlvbiAoY2IpIHtcbiAgICAgICAgaWYgKHR5cGVvZiBjYiA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICAgIGNiKHZhbHVlKVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiB2YWx1ZVxuICAgICAgfVxuICAgIGNhc2UgJ2RhdGUnOlxuICAgICAgcmV0dXJuIG5ldyBEYXRlKHZhbHVlKVxuICAgIGNhc2UgJ29iamVjdCc6XG4gICAgICB2YXIganNvblBhcnNlZFxuICAgICAgdHJ5IHtcbiAgICAgICAganNvblBhcnNlZCA9IEpTT04ucGFyc2UodmFsdWUpXG4gICAgICB9IGNhdGNoIChlKSB7fVxuICAgICAgaWYgKHR5cHB5KGpzb25QYXJzZWQsIE9iamVjdCkgfHwgdHlwcHkoanNvblBhcnNlZCwgQXJyYXkpKSB7XG4gICAgICAgIHJldHVybiBhdXRvUGFyc2UoanNvblBhcnNlZClcbiAgICAgIH0gZWxzZSBpZiAoIXR5cHB5KGpzb25QYXJzZWQsICd1bmRlZmluZWQnKSkge1xuICAgICAgICByZXR1cm4ge31cbiAgICAgIH1cbiAgICAgIHJldHVybiBwYXJzZU9iamVjdCh2YWx1ZSlcbiAgICBjYXNlICdib29sZWFuJzpcbiAgICAgIHJldHVybiB0b0Jvb2xlYW4odmFsdWUpXG4gICAgY2FzZSAnbnVtYmVyJzpcbiAgICAgIHJldHVybiBOdW1iZXIodmFsdWUpXG4gICAgY2FzZSAndW5kZWZpbmVkJzpcbiAgICAgIHJldHVybiB1bmRlZmluZWRcbiAgICBjYXNlICdudWxsJzpcbiAgICAgIHJldHVybiBudWxsXG4gICAgY2FzZSAnYXJyYXknOlxuICAgICAgcmV0dXJuIFt2YWx1ZV1cbiAgICBkZWZhdWx0OlxuICAgICAgaWYgKHR5cGVvZiB0eXBlID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgIHJldHVybiBuZXcgdHlwZSh2YWx1ZSkgLy8gZXNsaW50LWRpc2FibGUtbGluZVxuICAgICAgfVxuICAgICAgdGhyb3cgbmV3IEVycm9yKCdVbnN1cHBvcnRlZCB0eXBlLicpXG4gIH1cbn1cbi8qKlxuICogYXV0b1BhcnNlXG4gKiBhdXRvLXBhcnNlIGFueSB2YWx1ZSB5b3UgaGFwcGVuIHRvIHNlbmQgaW5cbiAqIChTdHJpbmcsIE51bWJlciwgQm9vbGVhbiwgQXJyYXksIE9iamVjdCwgRnVuY3Rpb24sIHVuZGVmaW5lZCBhbmQgbnVsbCkuXG4gKiBZb3Ugc2VuZCBpdCB3ZSB3aWxsIHRyeSB0byBmaW5kIGEgd2F5IHRvIHBhcnNlIGl0LlxuICogV2Ugbm93IHN1cHBvcnQgc2VuZGluZyBpbiBhIHN0cmluZyBvZiB3aGF0IHR5cGVcbiAqIChlLmcuIFwiYm9vbGVhblwiKSBvciBjb25zdHJ1Y3RvciAoZS5nLiBCb29sZWFuKVxuICpcbiAqIFVzYWdlOlxuICpcbiAqIGBgYGpzXG4gKiBhdXRvUGFyc2Uoe30pIC8vID0+IFwib2JqZWN0XCJcbiAqIGF1dG9QYXJzZSgnNDInKTsgLy8gPT4gNDJcbiAqIGF1dG9QYXJzZS5nZXQoJ1tdJyk7IC8vID0+IFtdXG4gKiBgYGBcbiAqXG4gKiBAbmFtZSBhdXRvUGFyc2VcbiAqIEBmdW5jdGlvblxuICogQHBhcmFtIHtWYWx1ZX0gaW5wdXQgVGhlIGlucHV0IHZhbHVlLlxuICogQHBhcmFtIHtDb25zdHJ1Y3RvcnxTdHJpbmd9IHRhcmdldCBUaGUgdGFyZ2V0IHR5cGUuXG4gKiBAcmV0dXJuIHtTdHJpbmd8RnVuY3Rpb258RGF0ZXxPYmplY3R8Qm9vbGVhbnxOdW1iZXJ8VW5kZWZpbmVkfE51bGx8QXJyYXl9XG4gKi9cbmZ1bmN0aW9uIGF1dG9QYXJzZSAodmFsdWUsIHR5cGUpIHtcbiAgaWYgKHR5cGUpIHtcbiAgICByZXR1cm4gcGFyc2VUeXBlKHZhbHVlLCB0eXBlKVxuICB9XG4gIHZhciBvcmlnbmFsVmFsdWUgPSB2YWx1ZVxuICAvKipcbiAgICogIFBSRSBSVUxFIC0gY2hlY2sgZm9yIG51bGwgYmUgY2F1c2UgbnVsbCBjYW4gYmUgdHlwZW9mIG9iamVjdCB3aGljaCBjYW4gIHRocm91Z2ggb2ZmIHBhcnNpbmdcbiAgICovXG4gIGlmICh2YWx1ZSA9PT0gbnVsbCkge1xuICAgIHJldHVybiBudWxsXG4gIH1cbiAgLyoqXG4gICAqIFRZUEVPRiBTRUNUSU9OIC0gVXNlIHRvIGNoZWNrIGFuZCBkbyBzcGVjaWZpYyB0aGluZ3MgYmFzZWQgb2ZmIG9mIGtub3cgdGhlIHR5cGVcbiAgICogQ2hlY2sgYWdhaW5zdCB1bmRlZmluZWRcbiAgICovXG4gIGlmICh2YWx1ZSA9PT0gdm9pZCAwKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZFxuICB9XG4gIGlmICh2YWx1ZSBpbnN0YW5jZW9mIERhdGUgfHwgdmFsdWUgaW5zdGFuY2VvZiBSZWdFeHApIHtcbiAgICByZXR1cm4gdmFsdWVcbiAgfVxuICBpZiAodHlwZW9mIHZhbHVlID09PSAnbnVtYmVyJyB8fCB0eXBlb2YgdmFsdWUgPT09ICdib29sZWFuJykge1xuICAgIHJldHVybiB2YWx1ZVxuICB9XG4gIGlmICh0eXBlb2YgdmFsdWUgPT09ICdmdW5jdGlvbicpIHtcbiAgICByZXR1cm4gcGFyc2VGdW5jdGlvbih2YWx1ZSlcbiAgfVxuICBpZiAodHlwZW9mIHZhbHVlID09PSAnb2JqZWN0Jykge1xuICAgIHJldHVybiBwYXJzZU9iamVjdCh2YWx1ZSlcbiAgfVxuICAvKipcbiAgICogU1RSSU5HIFNFQ1RJT04gLSBJZiB3ZSBtYWRlIGl0IHRoaXMgZmFyIHRoYXQgbWVhbnMgaXQgaXMgYSBzdHJpbmcgdGhhdCB3ZSBtdXN0IGRvIHNvbWV0aGluZyB3aXRoIHRvIHBhcnNlXG4gICAqL1xuICBpZiAodmFsdWUgPT09ICdOYU4nKSB7XG4gICAgcmV0dXJuIE5hTlxuICB9XG4gIHZhciBqc29uUGFyc2VkID0gbnVsbFxuICB0cnkge1xuICAgIGpzb25QYXJzZWQgPSBKU09OLnBhcnNlKHZhbHVlKVxuICB9IGNhdGNoIChlKSB7XG4gICAgdHJ5IHtcbiAgICAgIGpzb25QYXJzZWQgPSBKU09OLnBhcnNlKFxuICAgICAgICB2YWx1ZS50cmltKCkucmVwbGFjZSgvKFxcXFxcXFxcXCIpfChcXFxcXCIpL2dpLCAnXCInKS5yZXBsYWNlKC8oXFxcXG58XFxcXFxcXFxuKS9naSwgJycpLnJlcGxhY2UoLyheXCJ8XCIkKXwoXid8JyQpL2dpLCAnJylcbiAgICAgIClcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICB0cnkge1xuICAgICAgICBqc29uUGFyc2VkID0gSlNPTi5wYXJzZShcbiAgICAgICAgICB2YWx1ZS50cmltKCkucmVwbGFjZSgvJy9naSwgJ1wiJylcbiAgICAgICAgKVxuICAgICAgfSBjYXRjaCAoZSkge31cbiAgICB9XG4gIH1cbiAgaWYgKGpzb25QYXJzZWQgJiYgdHlwZW9mIGpzb25QYXJzZWQgPT09ICdvYmplY3QnKSB7XG4gICAgcmV0dXJuIGF1dG9QYXJzZShqc29uUGFyc2VkKVxuICB9XG4gIHZhbHVlID0gc3RyaXBUcmltTG93ZXIodmFsdWUpXG4gIGlmICh2YWx1ZSA9PT0gJ3VuZGVmaW5lZCcgfHwgdmFsdWUgPT09ICcnKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZFxuICB9XG4gIGlmICh2YWx1ZSA9PT0gJ251bGwnKSB7XG4gICAgcmV0dXJuIG51bGxcbiAgfVxuICAvKipcbiAgICogT3JkZXIgTWF0dGVyIGJlY2F1c2UgaWYgaXQgaXMgYSBvbmUgb3IgemVybyBib29sZWFuIHdpbGwgY29tZSBiYWNrIHdpdGggYSBhd25zZXIgdG9vLiBpZiB5b3Ugd2FudCBpdCB0byBiZSBhIGJvb2xlYW4geW91IG11c3Qgc3BlY2lmeVxuICAgKi9cbiAgdmFyIG51bSA9IE51bWJlcih2YWx1ZSlcbiAgaWYgKHR5cHB5KG51bSwgTnVtYmVyKSkge1xuICAgIHJldHVybiBudW1cbiAgfVxuICB2YXIgYm9vID0gY2hlY2tCb29sZWFuKHZhbHVlKVxuICBpZiAodHlwcHkoYm9vLCBCb29sZWFuKSkge1xuICAgIHJldHVybiBib29cbiAgfVxuICAvKipcbiAgICogREVGQVVMVCBTRUNUSU9OIC0gYmFzY2lhbGx5IGlmIHdlIGNhdGNoIG5vdGhpbmcgd2UgYXNzdW1lIHRoYXQgeW91IGp1c3QgaGF2ZSBhIHN0cmluZ1xuICAgKi9cbiAgLy8gaWYgc3RyaW5nIC0gY29udmVydCB0byBcIlwiXG4gIHJldHVybiBTdHJpbmcob3JpZ25hbFZhbHVlKVxufVxuIiwiXCJ1c2Ugc3RyaWN0XCI7XG5cbnZhciBub29wNiA9IHJlcXVpcmUoXCJub29wNlwiKTtcblxuKGZ1bmN0aW9uICgpIHtcbiAgICB2YXIgTkFNRV9GSUVMRCA9IFwibmFtZVwiO1xuXG4gICAgaWYgKHR5cGVvZiBub29wNi5uYW1lID09PSBcInN0cmluZ1wiKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB0cnkge1xuICAgICAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoRnVuY3Rpb24ucHJvdG90eXBlLCBOQU1FX0ZJRUxELCB7XG4gICAgICAgICAgICBnZXQ6IGZ1bmN0aW9uIGdldCgpIHtcbiAgICAgICAgICAgICAgICB2YXIgbmFtZU1hdGNoID0gdGhpcy50b1N0cmluZygpLnRyaW0oKS5tYXRjaCgvXmZ1bmN0aW9uXFxzKihbXlxccyhdKykvKTtcbiAgICAgICAgICAgICAgICB2YXIgbmFtZSA9IG5hbWVNYXRjaCA/IG5hbWVNYXRjaFsxXSA6IFwiXCI7XG4gICAgICAgICAgICAgICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRoaXMsIE5BTUVfRklFTEQsIHsgdmFsdWU6IG5hbWUgfSk7XG4gICAgICAgICAgICAgICAgcmV0dXJuIG5hbWU7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH0gY2F0Y2ggKGUpIHt9XG59KSgpO1xuXG4vKipcbiAqIGZ1bmN0aW9uTmFtZVxuICogR2V0IHRoZSBmdW5jdGlvbiBuYW1lLlxuICpcbiAqIEBuYW1lIGZ1bmN0aW9uTmFtZVxuICogQGZ1bmN0aW9uXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBpbnB1dCBUaGUgaW5wdXQgZnVuY3Rpb24uXG4gKiBAcmV0dXJucyB7U3RyaW5nfSBUaGUgZnVuY3Rpb24gbmFtZS5cbiAqL1xubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiBmdW5jdGlvbk5hbWUoaW5wdXQpIHtcbiAgICByZXR1cm4gaW5wdXQubmFtZTtcbn07IiwiXCJ1c2Ugc3RyaWN0XCI7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKCkge307IiwiXCJ1c2Ugc3RyaWN0XCI7XG5cbnJlcXVpcmUoXCJmdW5jdGlvbi5uYW1lXCIpO1xuXG4vKipcbiAqIFR5cHB5XG4gKiBHZXRzIHRoZSB0eXBlIG9mIHRoZSBpbnB1dCB2YWx1ZSBvciBjb21wYXJlcyBpdFxuICogd2l0aCBhIHByb3ZpZGVkIHR5cGUuXG4gKlxuICogVXNhZ2U6XG4gKlxuICogYGBganNcbiAqIFR5cHB5KHt9KSAvLyA9PiBcIm9iamVjdFwiXG4gKiBUeXBweSg0MiwgTnVtYmVyKTsgLy8gPT4gdHJ1ZVxuICogVHlwcHkuZ2V0KFtdLCBcImFycmF5XCIpOyA9PiB0cnVlXG4gKiBgYGBcbiAqXG4gKiBAbmFtZSBUeXBweVxuICogQGZ1bmN0aW9uXG4gKiBAcGFyYW0ge0FueXRoaW5nfSBpbnB1dCBUaGUgaW5wdXQgdmFsdWUuXG4gKiBAcGFyYW0ge0NvbnN0cnVjdG9yfFN0cmluZ30gdGFyZ2V0IFRoZSB0YXJnZXQgdHlwZS5cbiAqIEl0IGNvdWxkIGJlIGEgc3RyaW5nIChlLmcuIGBcImFycmF5XCJgKSBvciBhXG4gKiBjb25zdHJ1Y3RvciAoZS5nLiBgQXJyYXlgKS5cbiAqIEByZXR1cm4ge1N0cmluZ3xCb29sZWFufSBJdCByZXR1cm5zIGB0cnVlYCBpZiB0aGVcbiAqIGlucHV0IGhhcyB0aGUgcHJvdmlkZWQgdHlwZSBgdGFyZ2V0YCAoaWYgd2FzIHByb3ZpZGVkKSxcbiAqIGBmYWxzZWAgaWYgdGhlIGlucHV0IHR5cGUgZG9lcyAqbm90KiBoYXZlIHRoZSBwcm92aWRlZCB0eXBlXG4gKiBgdGFyZ2V0YCBvciB0aGUgc3RyaW5naWZpZWQgdHlwZSBvZiB0aGUgaW5wdXQgKGFsd2F5cyBsb3dlcmNhc2UpLlxuICovXG5mdW5jdGlvbiBUeXBweShpbnB1dCwgdGFyZ2V0KSB7XG4gICAgaWYgKGFyZ3VtZW50cy5sZW5ndGggPT09IDIpIHtcbiAgICAgICAgcmV0dXJuIFR5cHB5LmlzKGlucHV0LCB0YXJnZXQpO1xuICAgIH1cbiAgICByZXR1cm4gVHlwcHkuZ2V0KGlucHV0LCB0cnVlKTtcbn1cblxuLyoqXG4gKiBUeXBweS5pc1xuICogQ2hlY2tzIGlmIHRoZSBpbnB1dCB2YWx1ZSBoYXMgYSBzcGVjaWZpZWQgdHlwZS5cbiAqXG4gKiBAbmFtZSBUeXBweS5pc1xuICogQGZ1bmN0aW9uXG4gKiBAcGFyYW0ge0FueXRoaW5nfSBpbnB1dCBUaGUgaW5wdXQgdmFsdWUuXG4gKiBAcGFyYW0ge0NvbnN0cnVjdG9yfFN0cmluZ30gdGFyZ2V0IFRoZSB0YXJnZXQgdHlwZS5cbiAqIEl0IGNvdWxkIGJlIGEgc3RyaW5nIChlLmcuIGBcImFycmF5XCJgKSBvciBhXG4gKiBjb25zdHJ1Y3RvciAoZS5nLiBgQXJyYXlgKS5cbiAqIEByZXR1cm4ge0Jvb2xlYW59IGB0cnVlYCwgaWYgdGhlIGlucHV0IGhhcyB0aGUgc2FtZVxuICogdHlwZSB3aXRoIHRoZSB0YXJnZXQgb3IgYGZhbHNlYCBvdGhlcndpc2UuXG4gKi9cblR5cHB5LmlzID0gZnVuY3Rpb24gKGlucHV0LCB0YXJnZXQpIHtcbiAgICByZXR1cm4gVHlwcHkuZ2V0KGlucHV0LCB0eXBlb2YgdGFyZ2V0ID09PSBcInN0cmluZ1wiKSA9PT0gdGFyZ2V0O1xufTtcblxuLyoqXG4gKiBUeXBweS5nZXRcbiAqIEdldHMgdGhlIHR5cGUgb2YgdGhlIGlucHV0IHZhbHVlLiBUaGlzIGlzIHVzZWQgaW50ZXJuYWxseS5cbiAqXG4gKiBAbmFtZSBUeXBweS5nZXRcbiAqIEBmdW5jdGlvblxuICogQHBhcmFtIHtBbnl0aGluZ30gaW5wdXQgVGhlIGlucHV0IHZhbHVlLlxuICogQHBhcmFtIHtCb29sZWFufSBzdHIgQSBmbGFnIHRvIGluZGljYXRlIGlmIHRoZSByZXR1cm4gdmFsdWVcbiAqIHNob3VsZCBiZSBhIHN0cmluZyBvciBub3QuXG4gKiBAcmV0dXJuIHtDb25zdHJ1Y3RvcnxTdHJpbmd9IFRoZSBpbnB1dCB2YWx1ZSBjb25zdHJ1Y3RvclxuICogKGlmIGFueSkgb3IgdGhlIHN0cmluZ2lmaWVkIHR5cGUgKGFsd2F5cyBsb3dlcmNhc2UpLlxuICovXG5UeXBweS5nZXQgPSBmdW5jdGlvbiAoaW5wdXQsIHN0cikge1xuXG4gICAgaWYgKHR5cGVvZiBpbnB1dCA9PT0gXCJzdHJpbmdcIikge1xuICAgICAgICByZXR1cm4gc3RyID8gXCJzdHJpbmdcIiA6IFN0cmluZztcbiAgICB9XG5cbiAgICBpZiAobnVsbCA9PT0gaW5wdXQpIHtcbiAgICAgICAgcmV0dXJuIHN0ciA/IFwibnVsbFwiIDogbnVsbDtcbiAgICB9XG5cbiAgICBpZiAodW5kZWZpbmVkID09PSBpbnB1dCkge1xuICAgICAgICByZXR1cm4gc3RyID8gXCJ1bmRlZmluZWRcIiA6IHVuZGVmaW5lZDtcbiAgICB9XG5cbiAgICBpZiAoaW5wdXQgIT09IGlucHV0KSB7XG4gICAgICAgIHJldHVybiBzdHIgPyBcIm5hblwiIDogTmFOO1xuICAgIH1cblxuICAgIHJldHVybiBzdHIgPyBpbnB1dC5jb25zdHJ1Y3Rvci5uYW1lLnRvTG93ZXJDYXNlKCkgOiBpbnB1dC5jb25zdHJ1Y3Rvcjtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gVHlwcHk7Il19 382 | --------------------------------------------------------------------------------