├── .DS_Store ├── .babelrc ├── .github └── workflows │ └── npm-publish.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.CN.md ├── README.md ├── TODOLIST.md ├── assets ├── demo.png └── jt.png ├── dist ├── index.cjs ├── index.mjs ├── index.umd.js └── index.umd.min.js ├── package.json ├── pnpm-lock.yaml ├── rollup.config.js ├── src ├── codegen.ts ├── contant.ts ├── index.ts ├── parse.ts ├── transform.ts ├── types.ts └── utils.ts ├── tests ├── compile.test.ts ├── parse.test.ts ├── transform.test.ts └── utils.test.ts ├── tsconfig.json ├── types ├── index.d.ts ├── parse.d.ts └── transform.d.ts └── vite.config.ts /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickChen928/json2ts/db0dfab920fa22816bd3bca7ce44e870d2668046/.DS_Store -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": false, 7 | "targets": { 8 | "browsers": ["last 1 version", 9 | "> 1%", 10 | "IE 10"] 11 | } 12 | } 13 | ] 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Deploy to NPM 5 | 6 | on: 7 | release: 8 | types: [published] 9 | 10 | jobs: 11 | publish-npm: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 16 18 | registry-url: https://registry.npmjs.org/ 19 | - run: npm publish 20 | env: 21 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.1.4](https://github.com/PatrickChen928/json2ts/compare/v0.1.3...v0.1.4) (2022-09-30) 2 | 3 | ### Bug Fixes 4 | 5 | - fix replace 6 | - fix compile number with digit 7 | 8 | ## [0.1.3](https://github.com/PatrickChen928/json2ts/compare/v0.1.2...v0.1.3) (2022-06-25) 9 | 10 | ### Bug Fixes 11 | 12 | - fix `optimizeArrayOptional` option 13 | 14 | ## [0.1.2](https://github.com/PatrickChen928/json2ts/compare/v0.1.1...v0.1.2) (2022-06-25) 15 | 16 | ### Features 17 | 18 | - add `genType` option 19 | - add `optimizeArrayOptional` option 20 | 21 | ## [0.1.1](https://github.com/PatrickChen928/json2ts/compare/v0.1.0...v0.1.1) (2022-06-21) 22 | 23 | ### Bug Fixes 24 | 25 | - fix empty array bug 26 | 27 | ## [0.1.0](https://github.com/PatrickChen928/json2ts/compare/v0.0.5...v0.1.0) (2022-03-31) 28 | 29 | ### Bug Fixes 30 | 31 | - array comment parse bug 32 | 33 | ### Features 34 | 35 | - add config `comment` to output comment 36 | 37 | ### BREAKING CHANGES 38 | 39 | - `contant.ts`: change `COMMENT_KEY` to `COMMENT_TYPE` 40 | - `options`: fix `spiltType` to `splitType` 41 | 42 | ## [0.0.5](https://github.com/PatrickChen928/json2ts/compare/v0.0.4...v0.0.5) (2022-03-31) 43 | 44 | ### Bug Fixes 45 | 46 | - array parse infinite loop bug when loose a `]` 47 | 48 | ### Code Refactoring 49 | 50 | - extract transform api `normalEntryHandle` 51 | 52 | ### Features 53 | 54 | - add config `indent` to format output 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 cyly 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 | -------------------------------------------------------------------------------- /README.CN.md: -------------------------------------------------------------------------------- 1 | # Json2ts 2 | 3 | 中文 | [English](./README.md) 4 | 5 | 使用`编译原理`,解析 Json,输出 TS 类型 6 | 7 | json-parse > transform > codegen 8 | 9 | [在线链接](https://patrickchen928.github.io/json2ts/index.html) 10 | 11 | ## vscode 插件 12 | 13 | [plugin](https://marketplace.visualstudio.com/items?itemName=cyly.json2ts-vsc&ssr=false#overview) 14 | 15 | [github](https://github.com/PatrickChen928/json2ts-vscode) 16 | 17 | ## Features 18 | 19 | - 支持层级嵌套 20 | - 支持数组解析 21 | - 支持行内和换行的注释解析 22 | - 支持 key 值无引号模式 23 | 24 | ## Install 25 | 26 | ```javascript 27 | npm i @cyly/json2ts 28 | 29 | // or 30 | 31 | yarn add @cyly/json2ts 32 | 33 | // or 34 | 35 | pnpm i @cyly/json2ts 36 | 37 | // or 38 | 39 | 40 | 41 | json2ts.json2ts(`{ name: 'apha' }`, { 42 | semicolon: true 43 | }); 44 | ``` 45 | 46 | ## Document 47 | 48 | ### json2ts 49 | 50 | ```javascript 51 | import json2ts from '@cyly/json2ts' 52 | 53 | const json = `{ 54 | "a": 123, 55 | "b": { 56 | "c": "123" 57 | }, 58 | d: [1, 2, 3] 59 | }` 60 | 61 | const result = json2ts(json, { 62 | semicolon: true, 63 | }) 64 | ``` 65 | 66 | ### options 67 | 68 | #### splitType 69 | 70 | `boolean`。默认:`true`。是否分离对象,分离的话,会将 json 内的对象作为单独的 type 类型 71 | 72 | #### parseArray 73 | 74 | `boolean`。默认:`false`。是否解析数组。默认返回 Array< any > 75 | 76 | #### required 77 | 78 | `boolean`。默认:`true`。是否都是必须。设为`false`则为:`{ a?: number}`; 79 | 80 | #### semicolon 81 | 82 | `boolean`。默认:`false`。是否使用分号结尾。设为`true`则为:`{a: number; b: string;}` 83 | 84 | #### typePrefix 85 | 86 | `string`。默认:''。命名的前缀。如设为`User`:`UserKeyName$0` 87 | 88 | #### typeSuffix 89 | 90 | `string`。默认:`Type`。命名的后缀。如设为`Temp`:`KeyName$0Temp` 91 | 92 | #### indent 93 | 94 | `number`。 Default:`2`。 输出格式化的缩进 95 | 96 | #### comment 97 | 98 | `'inline' | 'block' | false`。 默认`false`。是否输出注释 99 | 100 | #### optimizeArrayOptional 101 | 102 | `boolean`. 默认`false`。优化数组内对象的值是否可选。例如: `[{a: 1, b: 3}, {b: 2}]` will be `Array<{a: number; b?: number}>` 103 | 104 | #### genType 105 | 106 | `'type' | 'interface'`. 默认`type`. 输出 `type` 或 `interface` 107 | 108 | ### parse 109 | 110 | ```javascript 111 | import { parse } from '@cyly/json2ts' 112 | 113 | const json = `{ 114 | "a": 123, 115 | "b": { 116 | "c": "123" 117 | }, 118 | d: [1, 2, 3] 119 | }` 120 | 121 | const ast = parse(json) 122 | ``` 123 | 124 | ### traverser 125 | 126 | ```javascript 127 | import { traverser, STRING_TYPE, ARRAY_TYPE } from '@cyly/json2ts' 128 | 129 | const json = `{ 130 | "a": 123, 131 | "b": { 132 | "c": "123" 133 | }, 134 | d: [1, 2, 3] 135 | }` 136 | 137 | const ast = parse(json) 138 | 139 | traverser(ast, { 140 | [STRING_TYPE]: { 141 | entry(node, parent) {}, 142 | exit(node, parent) {}, 143 | }, 144 | [ARRAY_TYPE]: { 145 | entry(node, parent) {}, 146 | exit(node, parent) {}, 147 | }, 148 | }) 149 | ``` 150 | 151 | ```javascript 152 | 153 | { 154 | "a": 123, 155 | "b": { 156 | "c": "123" 157 | }, 158 | d: [1, 2, 3] 159 | } 160 | 161 | => 162 | 163 | { 164 | "key": "root", 165 | "type": "Root", 166 | "value": [{ 167 | "key": "a", 168 | "value": "123", 169 | "type": "number", 170 | "loc": { 171 | "start": { 172 | "offset": 1, 173 | "column": 2, 174 | "line": 1 175 | }, 176 | "end": { 177 | "offset": 8, 178 | "column": 9, 179 | "line": 1 180 | }, 181 | "source": "\"a\":123" 182 | } 183 | }, 184 | { 185 | "key": "b", 186 | "value": [{ 187 | "key": "c", 188 | "value": "123", 189 | "type": "string", 190 | "loc": { 191 | "start": { 192 | "offset": 14, 193 | "column": 15, 194 | "line": 1 195 | }, 196 | "end": { 197 | "offset": 23, 198 | "column": 24, 199 | "line": 1 200 | }, 201 | "source": "\"c\":\"123\"" 202 | } 203 | }], 204 | "type": "Object", 205 | "loc": { 206 | "start": { 207 | "offset": 9, 208 | "column": 10, 209 | "line": 1 210 | }, 211 | "end": { 212 | "offset": 24, 213 | "column": 25, 214 | "line": 1 215 | }, 216 | "source": "\"b\":{\"c\":\"123\"}" 217 | } 218 | }, 219 | { 220 | "key": "d", 221 | "value": [{ 222 | "key": "$ARRAY_ITEM$", 223 | "value": "1", 224 | "type": "number", 225 | "loc": { 226 | "start": { 227 | "offset": 30, 228 | "column": 31, 229 | "line": 1 230 | }, 231 | "end": { 232 | "offset": 31, 233 | "column": 32, 234 | "line": 1 235 | }, 236 | "source": "1" 237 | } 238 | }, 239 | { 240 | "key": "$ARRAY_ITEM$", 241 | "value": "2", 242 | "type": "number", 243 | "loc": { 244 | "start": { 245 | "offset": 32, 246 | "column": 33, 247 | "line": 1 248 | }, 249 | "end": { 250 | "offset": 33, 251 | "column": 34, 252 | "line": 1 253 | }, 254 | "source": "2" 255 | } 256 | }, 257 | { 258 | "key": "$ARRAY_ITEM$", 259 | "value": "3", 260 | "type": "number", 261 | "loc": { 262 | "start": { 263 | "offset": 34, 264 | "column": 35, 265 | "line": 1 266 | }, 267 | "end": { 268 | "offset": 35, 269 | "column": 36, 270 | "line": 1 271 | }, 272 | "source": "3" 273 | } 274 | }], 275 | "type": "Array", 276 | "loc": { 277 | "start": { 278 | "offset": 25, 279 | "column": 26, 280 | "line": 1 281 | }, 282 | "end": { 283 | "offset": 36, 284 | "column": 37, 285 | "line": 1 286 | }, 287 | "source": "\"d\":[1,2,3]" 288 | } 289 | }] 290 | } 291 | 292 | => 293 | 294 | { 295 | a: number, 296 | b: { 297 | c: string 298 | }, 299 | d: Array< number >; 300 | } 301 | 302 | ``` 303 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | Json2ts 7 |

8 |

9 | Convert JSON to ts type by compiler 10 |

11 | 12 | English | [中文](./README.CN.md) 13 | 14 | `json-parse > transform > codegen` 15 | 16 | [online link](https://patrickchen928.github.io/json2ts/index.html) 17 | 18 | ![](./assets/demo.png) 19 | 20 | ## vscode plugin 21 | 22 | [plugin](https://marketplace.visualstudio.com/items?itemName=cyly.json2ts-vsc&ssr=false#overview) 23 | 24 | [github](https://github.com/PatrickChen928/json2ts-vscode) 25 | 26 | ## Features 27 | 28 | - support level nesting 29 | - support array parse 30 | - support remark parse 31 | - more flexible. support `key` with `double quotes` or `single quotes` or `no quotes` 32 | 33 | ## Install 34 | 35 | ```javascript 36 | npm i @cyly/json2ts 37 | 38 | // or 39 | 40 | yarn add @cyly/json2ts 41 | 42 | // or 43 | 44 | pnpm i @cyly/json2ts 45 | 46 | // or 47 | 48 | 49 | 50 | json2ts.json2ts(`{ name: 'apha' }`, { 51 | semicolon: true 52 | }); 53 | ``` 54 | 55 | ## Document 56 | 57 | ### json2ts 58 | 59 | ```javascript 60 | import json2ts from '@cyly/json2ts' 61 | 62 | const json = `{ 63 | "a": 123, 64 | "b": { 65 | "c": "123" 66 | }, 67 | d: [1, 2, 3] 68 | }` 69 | 70 | const result = json2ts(json, { 71 | semicolon: true, 72 | }) 73 | ``` 74 | 75 | ### options 76 | 77 | #### splitType 78 | 79 | `boolean`. Default:`true`. split Object, if `false` ,will define new `TYPE`, then use this `TYPE` name in the result 80 | 81 | #### parseArray 82 | 83 | `boolean`. Default:`false`. parse array. `false` will return `Array< any >`, otherwise return `Array< number | string ... >` 84 | 85 | #### required 86 | 87 | `boolean`. Default:`true`. key is required or not. if `false` will return `{ a?: number}` 88 | 89 | #### semicolon 90 | 91 | `boolean`. Default:`false`. with semicolon end. if `true` will return `{ a: number; b: string; }`, otherwise will return `{ a: number b: string }` 92 | 93 | #### typePrefix 94 | 95 | `string`. Default:''. type name prefix.exp: config `User`, name will be `UserKeyName$0` 96 | 97 | #### typeSuffix 98 | 99 | `string`. Default:`Type`. type name suffix. exp: config `Temp`,name will be `KeyName$0Temp` 100 | 101 | #### indent 102 | 103 | `number`. Default:`2`. output indent format. 104 | 105 | #### comment 106 | 107 | `'inline' | 'block' | false`. Default:`false`. output with comment. 108 | 109 | #### optimizeArrayOptional 110 | 111 | `boolean`. Default:`false`. optimize Object in Array. e.g. `[{a: 1, b: 3}, {b: 2}]` will be `Array<{a: number; b?: number}>` 112 | 113 | #### genType 114 | 115 | `'type' | 'interface'`. Default:`type`. output `type` or `interface` 116 | 117 | ### parse 118 | 119 | ```javascript 120 | import { parse } from '@cyly/json2ts' 121 | 122 | const json = `{ 123 | "a": 123, 124 | "b": { 125 | "c": "123" 126 | }, 127 | d: [1, 2, 3] 128 | }` 129 | 130 | const ast = parse(json) 131 | ``` 132 | 133 | ### traverser 134 | 135 | ```javascript 136 | import { traverser, STRING_TYPE, ARRAY_TYPE } from '@cyly/json2ts' 137 | 138 | const json = `{ 139 | "a": 123, 140 | "b": { 141 | "c": "123" 142 | }, 143 | d: [1, 2, 3] 144 | }` 145 | 146 | const ast = parse(json) 147 | 148 | traverser(ast, { 149 | [STRING_TYPE]: { 150 | entry(node, parent) {}, 151 | exit(node, parent) {}, 152 | }, 153 | [ARRAY_TYPE]: { 154 | entry(node, parent) {}, 155 | exit(node, parent) {}, 156 | }, 157 | }) 158 | ``` 159 | 160 | ```javascript 161 | 162 | { 163 | "a": 123, 164 | "b": { 165 | "c": "123" 166 | }, 167 | d: [1, 2, 3] 168 | } 169 | 170 | => 171 | 172 | { 173 | "key": "root", 174 | "type": "Root", 175 | "value": [{ 176 | "key": "a", 177 | "value": "123", 178 | "type": "number", 179 | "loc": { 180 | "start": { 181 | "offset": 1, 182 | "column": 2, 183 | "line": 1 184 | }, 185 | "end": { 186 | "offset": 8, 187 | "column": 9, 188 | "line": 1 189 | }, 190 | "source": "\"a\":123" 191 | } 192 | }, 193 | { 194 | "key": "b", 195 | "value": [{ 196 | "key": "c", 197 | "value": "123", 198 | "type": "string", 199 | "loc": { 200 | "start": { 201 | "offset": 14, 202 | "column": 15, 203 | "line": 1 204 | }, 205 | "end": { 206 | "offset": 23, 207 | "column": 24, 208 | "line": 1 209 | }, 210 | "source": "\"c\":\"123\"" 211 | } 212 | }], 213 | "type": "Object", 214 | "loc": { 215 | "start": { 216 | "offset": 9, 217 | "column": 10, 218 | "line": 1 219 | }, 220 | "end": { 221 | "offset": 24, 222 | "column": 25, 223 | "line": 1 224 | }, 225 | "source": "\"b\":{\"c\":\"123\"}" 226 | } 227 | }, 228 | { 229 | "key": "d", 230 | "value": [{ 231 | "key": "$ARRAY_ITEM$", 232 | "value": "1", 233 | "type": "number", 234 | "loc": { 235 | "start": { 236 | "offset": 30, 237 | "column": 31, 238 | "line": 1 239 | }, 240 | "end": { 241 | "offset": 31, 242 | "column": 32, 243 | "line": 1 244 | }, 245 | "source": "1" 246 | } 247 | }, 248 | { 249 | "key": "$ARRAY_ITEM$", 250 | "value": "2", 251 | "type": "number", 252 | "loc": { 253 | "start": { 254 | "offset": 32, 255 | "column": 33, 256 | "line": 1 257 | }, 258 | "end": { 259 | "offset": 33, 260 | "column": 34, 261 | "line": 1 262 | }, 263 | "source": "2" 264 | } 265 | }, 266 | { 267 | "key": "$ARRAY_ITEM$", 268 | "value": "3", 269 | "type": "number", 270 | "loc": { 271 | "start": { 272 | "offset": 34, 273 | "column": 35, 274 | "line": 1 275 | }, 276 | "end": { 277 | "offset": 35, 278 | "column": 36, 279 | "line": 1 280 | }, 281 | "source": "3" 282 | } 283 | }], 284 | "type": "Array", 285 | "loc": { 286 | "start": { 287 | "offset": 25, 288 | "column": 26, 289 | "line": 1 290 | }, 291 | "end": { 292 | "offset": 36, 293 | "column": 37, 294 | "line": 1 295 | }, 296 | "source": "\"d\":[1,2,3]" 297 | } 298 | }] 299 | } 300 | 301 | => 302 | 303 | { 304 | a: number, 305 | b: { 306 | c: string 307 | }, 308 | d: Array< number >; 309 | } 310 | 311 | ``` 312 | -------------------------------------------------------------------------------- /TODOLIST.md: -------------------------------------------------------------------------------- 1 | ## TODO 2 | 3 | - [x] Type resuse 4 | - [x] generate comment 5 | - [x] output format 6 | - [ ] `required` config with key 7 | - [ ] Type inherit 8 | - [ ] parse `function` -------------------------------------------------------------------------------- /assets/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickChen928/json2ts/db0dfab920fa22816bd3bca7ce44e870d2668046/assets/demo.png -------------------------------------------------------------------------------- /assets/jt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickChen928/json2ts/db0dfab920fa22816bd3bca7ce44e870d2668046/assets/jt.png -------------------------------------------------------------------------------- /dist/index.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 4 | 5 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 6 | 7 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 8 | 9 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 10 | 11 | function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } 12 | 13 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } 14 | 15 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } 16 | 17 | function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } 18 | 19 | Object.defineProperty(exports, '__esModule', { 20 | value: true 21 | }); 22 | var ROOT_TYPE = "Root"; 23 | var STRING_TYPE = "string"; 24 | var NUMBER_TYPE = "number"; 25 | var NULL_TYPE = "null"; 26 | var BOOLEAN_TYPE = "boolean"; 27 | var UNDEFINED_TYPE = "undefined"; 28 | var OBJECT_TYPE = "Object"; 29 | var ARRAY_TYPE = "Array"; 30 | var COMMENT_TYPE = "Comment"; 31 | var ROOT_KEY = "root"; 32 | var ARRAY_ITEM = "$ARRAY_ITEM$"; 33 | var LAST_COMMENT = "$LAST_COMMENT$"; 34 | var NEXT_COMMENT = "$NEXT_COMMENT$"; 35 | var ARRAY_ERROR_MESSAGE = "array should be closed"; 36 | var COMMENT_ERROR_MESSAGE = "comment is illegal"; 37 | var VALUE_ILLEGAL_ERROR_MESSAGE = "value is illegal"; 38 | var NUMBER_REGX = /^[-+]?([0-9]+\.?\d*)([Ee][-+]?[0-9]+)?/; 39 | 40 | function getCursor(context) { 41 | var offset = context.offset, 42 | column = context.column, 43 | line = context.line; 44 | return { 45 | offset: offset, 46 | column: column, 47 | line: line 48 | }; 49 | } 50 | 51 | function getLoc(context, start, end) { 52 | end = end || getCursor(context); 53 | return { 54 | start: start, 55 | end: end, 56 | source: context.originalSource.slice(start.offset, end.offset) 57 | }; 58 | } 59 | 60 | function createContext(content, options) { 61 | return { 62 | options: options, 63 | column: 1, 64 | line: 1, 65 | offset: 0, 66 | originalSource: content, 67 | source: content 68 | }; 69 | } 70 | 71 | function createRoot(nodes) { 72 | return { 73 | key: ROOT_KEY, 74 | type: ROOT_TYPE, 75 | value: nodes 76 | }; 77 | } 78 | 79 | function isEnd(context) { 80 | return !context.source; 81 | } 82 | 83 | function advancePositionWithMutation(pos, source) { 84 | var numberOfCharacters = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : source.length; 85 | var lineCount = 0; 86 | var lastNewLinePos = -1; 87 | 88 | for (var i = 0; i < numberOfCharacters; i++) { 89 | if (source.charCodeAt(i) === 10) { 90 | lineCount++; 91 | lastNewLinePos = i; 92 | } 93 | } 94 | 95 | pos.offset += numberOfCharacters; 96 | pos.line += lineCount; 97 | pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos; 98 | } 99 | 100 | function advanceBy(context, numberOfCharacters) { 101 | var source = context.source; 102 | advancePositionWithMutation(context, source, numberOfCharacters); 103 | context.source = source.slice(numberOfCharacters); 104 | } 105 | 106 | function advanceSpaces(context) { 107 | var match = /^[\r\n\f ]+/.exec(context.source); 108 | 109 | if (match && match[0]) { 110 | advanceBy(context, match[0].length); 111 | } 112 | } 113 | 114 | function parseData(context, keyName) { 115 | advanceSpaces(context); 116 | var start = getCursor(context); 117 | var key = keyName || parseKey(context); 118 | 119 | var _parseValue = parseValue(context), 120 | value = _parseValue.value, 121 | type = _parseValue.type; 122 | 123 | var loc = getLoc(context, start); 124 | advanceSpaces(context); 125 | 126 | if (context.source[0] === ",") { 127 | advanceBy(context, 1); 128 | advanceSpaces(context); 129 | } 130 | 131 | return { 132 | key: key, 133 | value: value, 134 | type: type, 135 | loc: loc 136 | }; 137 | } 138 | 139 | function parseChildren(context) { 140 | var nodes = []; 141 | 142 | while (!isEnd(context)) { 143 | advanceSpaces(context); 144 | var s = context.source; 145 | 146 | if (s[0] === "{") { 147 | advanceBy(context, 1); 148 | } else if (s[0] === "}") { 149 | advanceBy(context, 1); 150 | advanceSpaces(context); 151 | return nodes; 152 | } else if (s[0] === "/") { 153 | if (s[1] === "/") { 154 | var lastNode = nodes[nodes.length - 1]; 155 | var lastLine = -1; 156 | 157 | if (lastNode) { 158 | lastLine = lastNode.loc.end.line; 159 | } 160 | 161 | var comment = parseComment(context, lastLine); 162 | 163 | if (comment) { 164 | nodes.push(comment); 165 | } 166 | 167 | advanceSpaces(context); 168 | } else { 169 | throw new Error(COMMENT_ERROR_MESSAGE); 170 | } 171 | } else { 172 | nodes.push(parseData(context)); 173 | } 174 | } 175 | 176 | return nodes; 177 | } 178 | 179 | function parseKey(context) { 180 | var s = context.source[0]; 181 | var match = []; 182 | 183 | if (s === '"') { 184 | match = /^"(.[^"]*)/i.exec(context.source); 185 | } else if (s === "'") { 186 | match = /^'(.[^']*)/i.exec(context.source); 187 | } else { 188 | match = /(.[^:]*)/i.exec(context.source); 189 | match[1] = match[1].trim(); 190 | } 191 | 192 | advanceBy(context, match[0].length + 1); 193 | advanceSpaces(context); 194 | 195 | if (context.source[0] === ":") { 196 | advanceBy(context, 1); 197 | advanceSpaces(context); 198 | } 199 | 200 | return match[1]; 201 | } 202 | 203 | function parseNumber(context) { 204 | var match = NUMBER_REGX.exec(context.source); 205 | advanceBy(context, match[0].length); 206 | return match[1]; 207 | } 208 | 209 | function parseString(context) { 210 | var s = context.source[0]; 211 | var match = new RegExp("^".concat(s, "((?:\\.|[^\\").concat(s, "])*)").concat(s)).exec(context.source); 212 | advanceBy(context, match[0].length); 213 | return match[1]; 214 | } 215 | 216 | function parseNull(context) { 217 | advanceBy(context, 4); 218 | return "null"; 219 | } 220 | 221 | function parseBoolean(context) { 222 | var match = /^(true|false)/i.exec(context.source); 223 | advanceBy(context, match[0].length); 224 | return match[1]; 225 | } 226 | 227 | function parseUndefined(context) { 228 | advanceBy(context, 9); 229 | return "undefined"; 230 | } 231 | 232 | function parseValue(context) { 233 | var value = null; 234 | var type = null; 235 | var code = context.source[0]; 236 | 237 | if (NUMBER_REGX.test(context.source)) { 238 | value = parseNumber(context); 239 | type = NUMBER_TYPE; 240 | } else if (code === '"' || code === "'") { 241 | value = parseString(context); 242 | type = STRING_TYPE; 243 | } else if (code === "[") { 244 | advanceBy(context, 1); 245 | value = parseArray(context); 246 | type = ARRAY_TYPE; 247 | } else if (code === "{") { 248 | value = parseChildren(context); 249 | type = OBJECT_TYPE; 250 | } else if (context.source.indexOf("null") === 0) { 251 | value = parseNull(context); 252 | type = NULL_TYPE; 253 | } else if (context.source.indexOf("true") === 0 || context.source.indexOf("false") === 0) { 254 | value = parseBoolean(context); 255 | type = BOOLEAN_TYPE; 256 | } else if (context.source.indexOf("undefined") === 0) { 257 | value = parseUndefined(context); 258 | type = UNDEFINED_TYPE; 259 | } else { 260 | throw new Error(VALUE_ILLEGAL_ERROR_MESSAGE); 261 | } 262 | 263 | return { 264 | value: value, 265 | type: type 266 | }; 267 | } 268 | 269 | function parseArray(context) { 270 | var nodes = []; 271 | var lastLine = getCursor(context).line; 272 | advanceSpaces(context); 273 | 274 | while (!isEnd(context)) { 275 | var s = context.source[0]; 276 | 277 | if (s === "]") { 278 | advanceBy(context, 1); 279 | return nodes; 280 | } 281 | 282 | if (s === "}" || s === ":") { 283 | throw new Error(ARRAY_ERROR_MESSAGE); 284 | } 285 | 286 | if (context.source.indexOf("//") === 0) { 287 | var cv = parseComment(context, lastLine); 288 | 289 | if (cv) { 290 | nodes.push(cv); 291 | } 292 | 293 | advanceSpaces(context); 294 | } else { 295 | var itemValue = parseData(context, ARRAY_ITEM); 296 | lastLine = itemValue.loc.end.line; 297 | nodes.push(itemValue); 298 | } 299 | } 300 | 301 | throw new Error(ARRAY_ERROR_MESSAGE); 302 | } 303 | 304 | function parseComment(context, lastLine) { 305 | var currLine = getCursor(context).line; 306 | var key = lastLine === currLine ? LAST_COMMENT : NEXT_COMMENT; 307 | var match = /^\/\/(.*)?(?=\n)/i.exec(context.source); 308 | var start = getCursor(context); 309 | var comment = match[1]; 310 | advanceBy(context, match[0].length); 311 | 312 | if (!comment || !comment.trim()) { 313 | return null; 314 | } 315 | 316 | return { 317 | key: key, 318 | value: comment.trim(), 319 | type: COMMENT_TYPE, 320 | loc: getLoc(context, start) 321 | }; 322 | } 323 | 324 | function parse(input, options) { 325 | var context = createContext(input, options); 326 | return createRoot(parseChildren(context)); 327 | } 328 | 329 | function objectToString(o) { 330 | return Object.prototype.toString.call(o); 331 | } 332 | 333 | function isArray(value) { 334 | return _typeof(value) === "object" && objectToString(value).slice(8, -1) === "Array"; 335 | } 336 | 337 | function isObject(value) { 338 | return _typeof(value) === "object" && objectToString(value).slice(8, -1) === "Object"; 339 | } 340 | 341 | function upperCaseFirstChat(str) { 342 | return str.replace(/( |^)[a-z]/g, function (L) { 343 | return L.toUpperCase(); 344 | }); 345 | } 346 | 347 | function stringifyObjAndSort(obj) { 348 | var keys = Object.keys(obj).sort(); 349 | var res = "{"; 350 | 351 | var _iterator = _createForOfIteratorHelper(keys), 352 | _step; 353 | 354 | try { 355 | for (_iterator.s(); !(_step = _iterator.n()).done;) { 356 | var val = _step.value; 357 | res += "".concat(val, ":").concat(obj[val], ","); 358 | } 359 | } catch (err) { 360 | _iterator.e(err); 361 | } finally { 362 | _iterator.f(); 363 | } 364 | 365 | res = res.replace(/,$/, ""); 366 | res += "}"; 367 | return res; 368 | } 369 | 370 | function optimizeArray(arrTypes) { 371 | var root = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ""; 372 | var optionalKeys = []; 373 | var keyCountMap = {}; 374 | var objCount = 0; 375 | var newTypes = []; 376 | var typeObj = {}; 377 | 378 | var _loop = function _loop(i) { 379 | var type = arrTypes[i]; 380 | 381 | if (isObject(type)) { 382 | objCount++; 383 | Object.keys(type).forEach(function (key) { 384 | typeObj[key] = type[key]; 385 | 386 | if (keyCountMap["".concat(root, ">").concat(key)]) { 387 | keyCountMap["".concat(root, ">").concat(key)] += 1; 388 | } else { 389 | keyCountMap["".concat(root, ">").concat(key)] = 1; 390 | } 391 | }); 392 | } else { 393 | newTypes.push(type); 394 | } 395 | }; 396 | 397 | for (var i = 0; i < arrTypes.length; i++) { 398 | _loop(i); 399 | } 400 | 401 | Object.keys(keyCountMap).forEach(function (key) { 402 | if (keyCountMap[key] < objCount) { 403 | optionalKeys.push(key); 404 | } 405 | }); 406 | 407 | if (Object.keys(typeObj).length) { 408 | newTypes.push(typeObj); 409 | } 410 | 411 | return { 412 | optionalKeys: optionalKeys, 413 | newTypes: newTypes 414 | }; 415 | } 416 | 417 | function isOptional(optionalKeys, key) { 418 | var parent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ""; 419 | var newKey = parent + ">" + key; 420 | 421 | var _iterator2 = _createForOfIteratorHelper(optionalKeys), 422 | _step2; 423 | 424 | try { 425 | for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { 426 | var optionalKey = _step2.value; 427 | 428 | if (newKey === optionalKey) { 429 | return true; 430 | } 431 | } 432 | } catch (err) { 433 | _iterator2.e(err); 434 | } finally { 435 | _iterator2.f(); 436 | } 437 | 438 | return false; 439 | } 440 | 441 | var cache = resetCache(); 442 | 443 | function resetCache() { 444 | return { 445 | comments: {}, 446 | i: 0, 447 | lastI: null, 448 | lastNode: null, 449 | nextComment: [] 450 | }; 451 | } 452 | 453 | function normalEntryHandle(node, parent, options) { 454 | node.i = cache.i; 455 | 456 | if (node.key === ARRAY_ITEM) { 457 | cache.nextComment = []; 458 | 459 | if (options.parseArray) { 460 | parent.typeValue = parent.typeValue || []; 461 | parent.typeValue.push(node.type); 462 | } 463 | } else { 464 | parent.typeValue = parent.typeValue || {}; 465 | parent.typeValue[node.key] = node.type; 466 | handleNormalNodeComment(node); 467 | } 468 | } 469 | 470 | function handleNormalNodeComment(node) { 471 | cache.lastNode = node; 472 | 473 | if (cache.nextComment.length) { 474 | var comments = cache.comments; 475 | var key = node.key + cache.i; 476 | comments[key] = comments[key] || []; 477 | comments[key] = comments[key].concat(cache.nextComment); 478 | cache.nextComment = []; 479 | } 480 | } 481 | 482 | function cacheObjectComment(node) { 483 | if (cache.nextComment.length) { 484 | node.nextComment = cache.nextComment; 485 | cache.nextComment = []; 486 | } 487 | } 488 | 489 | function handleObjectNodeComment(node) { 490 | if (node.nextComment) { 491 | var comments = cache.comments; 492 | var key = node.key + node.i; 493 | comments[key] = comments[key] || []; 494 | comments[key] = comments[key].concat(node.nextComment); 495 | cache.nextComment = []; 496 | } 497 | } 498 | 499 | function traverseNode(nodes, parent, visiter) { 500 | nodes.forEach(function (node) { 501 | var visit = visiter[node.type]; 502 | 503 | if (visit) { 504 | visit.entry && visit.entry(node, parent); 505 | } 506 | 507 | if (isArray(node.value)) { 508 | traverseNode(node.value, node, visiter); 509 | } 510 | 511 | if (visit) { 512 | visit.exit && visit.exit(node, parent); 513 | } 514 | }); 515 | } 516 | 517 | function traverser(ast, visiter) { 518 | var root = visiter.Root; 519 | 520 | if (root) { 521 | root.entry && root.entry(ast, null); 522 | } 523 | 524 | traverseNode(ast.value, ast, visiter); 525 | 526 | if (root) { 527 | root.exit && root.exit(ast, null); 528 | } 529 | 530 | return ast; 531 | } 532 | 533 | function transform(ast, options) { 534 | var _traverser; 535 | 536 | traverser(ast, (_traverser = {}, _defineProperty(_traverser, STRING_TYPE, { 537 | entry: function entry(node, parent) { 538 | normalEntryHandle(node, parent, options); 539 | } 540 | }), _defineProperty(_traverser, NUMBER_TYPE, { 541 | entry: function entry(node, parent) { 542 | normalEntryHandle(node, parent, options); 543 | } 544 | }), _defineProperty(_traverser, OBJECT_TYPE, { 545 | entry: function entry(node, parent) { 546 | if (node.key === ARRAY_ITEM) { 547 | cache.nextComment = []; 548 | 549 | if (options.parseArray) { 550 | parent.typeValue = parent.typeValue || []; 551 | node.typeValue = {}; 552 | parent.typeValue.push(node.typeValue); 553 | node.i = cache.i; 554 | cache.i++; 555 | } 556 | } else { 557 | parent.typeValue = parent.typeValue || {}; 558 | parent.typeValue[node.key] = node.typeValue = {}; 559 | 560 | if (options.comment === "inline") { 561 | cacheObjectComment(node); 562 | } else if (options.comment === "block") { 563 | handleNormalNodeComment(node); 564 | } 565 | 566 | node.i = cache.i; 567 | cache.i++; 568 | } 569 | }, 570 | exit: function exit(node) { 571 | if (options.comment === "inline") { 572 | node.i = cache.i; 573 | handleObjectNodeComment(node); 574 | } 575 | 576 | cache.lastNode = node; 577 | } 578 | }), _defineProperty(_traverser, ARRAY_TYPE, { 579 | entry: function entry(node, parent) { 580 | if (node.key === ARRAY_ITEM) { 581 | cache.nextComment = []; 582 | 583 | if (options.parseArray) { 584 | parent.typeValue = parent.typeValue || []; 585 | node.typeValue = []; 586 | parent.typeValue.push(node.typeValue); 587 | } 588 | } else { 589 | parent.typeValue = parent.typeValue || {}; 590 | parent.typeValue[node.key] = node.typeValue = []; 591 | 592 | if (options.comment === "inline") { 593 | cacheObjectComment(node); 594 | } else if (options.comment === "block") { 595 | handleNormalNodeComment(node); 596 | } 597 | } 598 | 599 | node.i = cache.i; 600 | }, 601 | exit: function exit(node) { 602 | if (options.comment === "inline") { 603 | node.i = cache.i; 604 | handleObjectNodeComment(node); 605 | } 606 | 607 | cache.lastNode = node; 608 | } 609 | }), _defineProperty(_traverser, NULL_TYPE, { 610 | entry: function entry(node, parent) { 611 | normalEntryHandle(node, parent, options); 612 | } 613 | }), _defineProperty(_traverser, BOOLEAN_TYPE, { 614 | entry: function entry(node, parent) { 615 | normalEntryHandle(node, parent, options); 616 | } 617 | }), _defineProperty(_traverser, UNDEFINED_TYPE, { 618 | entry: function entry(node, parent) { 619 | normalEntryHandle(node, parent, options); 620 | } 621 | }), _defineProperty(_traverser, COMMENT_TYPE, { 622 | entry: function entry(node) { 623 | if (node.key === LAST_COMMENT) { 624 | var key = cache.lastNode.key + cache.lastNode.i; 625 | cache.comments[key] = cache.comments[key] || []; 626 | cache.comments[key].push(node.value); 627 | } else { 628 | cache.nextComment.push(node.value); 629 | } 630 | } 631 | }), _traverser)); 632 | ast.comments = cache.comments; 633 | cache = resetCache(); 634 | return ast; 635 | } 636 | 637 | var Generate = /*#__PURE__*/function () { 638 | function Generate(ast, options) { 639 | _classCallCheck(this, Generate); 640 | 641 | this.ast = ast; 642 | this.options = options; 643 | this.prefix = options.typePrefix; 644 | this.suffix = options.typeSuffix; 645 | this.vars = ""; 646 | this.i = 0; 647 | this.j = -1; 648 | this.level = 1; 649 | this.objValueMap = /* @__PURE__ */new Map(); 650 | } 651 | 652 | _createClass(Generate, [{ 653 | key: "beginGen", 654 | value: function beginGen() { 655 | var originName = this.genName("Result"); 656 | var typeValue = this.ast.typeValue; 657 | var code = this.gen(typeValue); 658 | var isInterface = this.options.genType === "interface"; 659 | return "".concat(this.vars).concat(this.options.genType, " ").concat(originName).concat(isInterface ? "" : " =", " ").concat(code).concat(this.options.semicolon ? ";" : "", "\n"); 660 | } 661 | }, { 662 | key: "gen", 663 | value: function gen(typeValue) { 664 | var optionalKeys = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; 665 | var code = "{\n"; 666 | 667 | for (var key in typeValue) { 668 | var type = typeValue[key]; 669 | 670 | if (this.options.comment === "block") { 671 | code += this.genBlockComment(key); 672 | } 673 | 674 | code += this.genKey(key, isOptional(optionalKeys, key)); 675 | 676 | if (isObject(type)) { 677 | code += this.genObjcet(key, type); 678 | } else if (isArray(type)) { 679 | code += this.options.parseArray ? this.genArray(key, type) : "Array"; 680 | } else { 681 | code += type; 682 | } 683 | 684 | if (this.options.semicolon) { 685 | code += ";"; 686 | } 687 | 688 | if (this.options.comment === "inline") { 689 | code += this.genInlineComment(key); 690 | } 691 | 692 | code += "\n"; 693 | } 694 | 695 | if (!this.options.splitType) { 696 | code += this.genFormatChat(this.level - 1); 697 | } 698 | 699 | code += "}"; 700 | return code; 701 | } 702 | }, { 703 | key: "genName", 704 | value: function genName(key) { 705 | this.j++; 706 | return "".concat(this.prefix).concat(upperCaseFirstChat(key), "$").concat(this.j).concat(this.suffix); 707 | } 708 | }, { 709 | key: "genKey", 710 | value: function genKey(key) { 711 | var optional = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 712 | return "".concat(this.genFormatChat(this.level)).concat(key).concat(this.options.required && !optional ? ": " : "?: "); 713 | } 714 | }, { 715 | key: "genFormatChat", 716 | value: function genFormatChat(level) { 717 | var indent = this.options.indent; 718 | 719 | if (this.options.splitType) { 720 | return " ".repeat(indent); 721 | } 722 | 723 | return " ".repeat(level * indent); 724 | } 725 | }, { 726 | key: "genInlineComment", 727 | value: function genInlineComment(key) { 728 | var name = key + this.i; 729 | var comments = this.ast.comments[name]; 730 | 731 | if (comments && comments.length) { 732 | var code = " // "; 733 | comments.forEach(function (comment) { 734 | code += comment + "; "; 735 | }); 736 | code = code.substring(0, code.length - 2); 737 | return code; 738 | } 739 | 740 | return ""; 741 | } 742 | }, { 743 | key: "genBlockComment", 744 | value: function genBlockComment(key) { 745 | var _this = this; 746 | 747 | var code = ""; 748 | var name = key + this.i; 749 | var comments = this.ast.comments[name]; 750 | 751 | if (comments && comments.length) { 752 | comments.forEach(function (comment) { 753 | code += _this.genFormatChat(_this.level) + "// " + comment + "\n"; 754 | }); 755 | } 756 | 757 | return code; 758 | } 759 | }, { 760 | key: "genObjcet", 761 | value: function genObjcet(key, type) { 762 | var optionalKeys = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; 763 | var code = ""; 764 | this.level++; 765 | this.i++; 766 | var objType = this.gen(type, optionalKeys); 767 | 768 | if (this.options.splitType) { 769 | code += this.genObjectCodeWithVar(objType, key, type); 770 | } else { 771 | code += objType; 772 | } 773 | 774 | this.level--; 775 | return code; 776 | } 777 | }, { 778 | key: "genObjectCodeWithVar", 779 | value: function genObjectCodeWithVar(objType, key, type) { 780 | var val = stringifyObjAndSort(type); 781 | 782 | if (this.objValueMap.has(val)) { 783 | return this.objValueMap.get(val); 784 | } 785 | 786 | var varName = this.genName(key); 787 | var isInterface = this.options.genType === "interface"; 788 | this.objValueMap.set(val, varName); 789 | this.vars += "".concat(this.options.genType, " ").concat(varName).concat(isInterface ? "" : " =", " ").concat(objType).concat(this.options.semicolon ? ";" : "", "\n\n"); 790 | return varName; 791 | } 792 | }, { 793 | key: "genArray", 794 | value: function genArray(key, types) { 795 | var _this2 = this; 796 | 797 | if (types.length === 0) { 798 | return "Array< unknown >"; 799 | } 800 | 801 | var code = "Array< "; 802 | var arrTypes = /* @__PURE__ */new Set(); 803 | var optionalKeys = []; 804 | 805 | if (this.options.optimizeArrayOptional) { 806 | var _optimizeArray = optimizeArray(types), 807 | keys = _optimizeArray.optionalKeys, 808 | newTypes = _optimizeArray.newTypes; 809 | 810 | optionalKeys = keys; 811 | types = newTypes; 812 | } 813 | 814 | types.forEach(function (type) { 815 | if (isArray(type)) { 816 | arrTypes.add(_this2.genArray(key, type)); 817 | } 818 | 819 | if (isObject(type)) { 820 | arrTypes.add(_this2.genObjcet(key, type, optionalKeys)); 821 | } else { 822 | arrTypes.add(type); 823 | } 824 | }); 825 | code += Array.from(arrTypes).join(" | "); 826 | code += " >"; 827 | return code; 828 | } 829 | }]); 830 | 831 | return Generate; 832 | }(); 833 | 834 | function generate(ast, options) { 835 | return new Generate(ast, options).beginGen(); 836 | } 837 | 838 | function initOptions(options) { 839 | var defaultOptions = { 840 | splitType: true, 841 | parseArray: false, 842 | required: true, 843 | semicolon: false, 844 | typeSuffix: "Type", 845 | typePrefix: "", 846 | indent: 2, 847 | comment: false, 848 | optimizeArrayOptional: false, 849 | genType: "type" 850 | }; 851 | Object.assign(defaultOptions, options); 852 | return defaultOptions; 853 | } 854 | 855 | function json2ts(code) { 856 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 857 | var finalOptions = initOptions(options); 858 | var ast = parse(code, finalOptions); 859 | transform(ast, finalOptions); 860 | return generate(ast, finalOptions); 861 | } 862 | 863 | exports.ARRAY_ERROR_MESSAGE = ARRAY_ERROR_MESSAGE; 864 | exports.ARRAY_ITEM = ARRAY_ITEM; 865 | exports.ARRAY_TYPE = ARRAY_TYPE; 866 | exports.BOOLEAN_TYPE = BOOLEAN_TYPE; 867 | exports.COMMENT_ERROR_MESSAGE = COMMENT_ERROR_MESSAGE; 868 | exports.COMMENT_TYPE = COMMENT_TYPE; 869 | exports.LAST_COMMENT = LAST_COMMENT; 870 | exports.NEXT_COMMENT = NEXT_COMMENT; 871 | exports.NULL_TYPE = NULL_TYPE; 872 | exports.NUMBER_TYPE = NUMBER_TYPE; 873 | exports.OBJECT_TYPE = OBJECT_TYPE; 874 | exports.ROOT_KEY = ROOT_KEY; 875 | exports.ROOT_TYPE = ROOT_TYPE; 876 | exports.STRING_TYPE = STRING_TYPE; 877 | exports.UNDEFINED_TYPE = UNDEFINED_TYPE; 878 | exports.VALUE_ILLEGAL_ERROR_MESSAGE = VALUE_ILLEGAL_ERROR_MESSAGE; 879 | exports["default"] = json2ts; 880 | exports.json2ts = json2ts; 881 | exports.parse = parse; 882 | exports.traverser = traverser; 883 | -------------------------------------------------------------------------------- /dist/index.mjs: -------------------------------------------------------------------------------- 1 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 2 | 3 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 4 | 5 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 6 | 7 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 8 | 9 | function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } 10 | 11 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } 12 | 13 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } 14 | 15 | function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } 16 | 17 | var ROOT_TYPE = "Root"; 18 | var STRING_TYPE = "string"; 19 | var NUMBER_TYPE = "number"; 20 | var NULL_TYPE = "null"; 21 | var BOOLEAN_TYPE = "boolean"; 22 | var UNDEFINED_TYPE = "undefined"; 23 | var OBJECT_TYPE = "Object"; 24 | var ARRAY_TYPE = "Array"; 25 | var COMMENT_TYPE = "Comment"; 26 | var ROOT_KEY = "root"; 27 | var ARRAY_ITEM = "$ARRAY_ITEM$"; 28 | var LAST_COMMENT = "$LAST_COMMENT$"; 29 | var NEXT_COMMENT = "$NEXT_COMMENT$"; 30 | var ARRAY_ERROR_MESSAGE = "array should be closed"; 31 | var COMMENT_ERROR_MESSAGE = "comment is illegal"; 32 | var VALUE_ILLEGAL_ERROR_MESSAGE = "value is illegal"; 33 | var NUMBER_REGX = /^[-+]?([0-9]+\.?\d*)([Ee][-+]?[0-9]+)?/; 34 | 35 | function getCursor(context) { 36 | var offset = context.offset, 37 | column = context.column, 38 | line = context.line; 39 | return { 40 | offset: offset, 41 | column: column, 42 | line: line 43 | }; 44 | } 45 | 46 | function getLoc(context, start, end) { 47 | end = end || getCursor(context); 48 | return { 49 | start: start, 50 | end: end, 51 | source: context.originalSource.slice(start.offset, end.offset) 52 | }; 53 | } 54 | 55 | function createContext(content, options) { 56 | return { 57 | options: options, 58 | column: 1, 59 | line: 1, 60 | offset: 0, 61 | originalSource: content, 62 | source: content 63 | }; 64 | } 65 | 66 | function createRoot(nodes) { 67 | return { 68 | key: ROOT_KEY, 69 | type: ROOT_TYPE, 70 | value: nodes 71 | }; 72 | } 73 | 74 | function isEnd(context) { 75 | return !context.source; 76 | } 77 | 78 | function advancePositionWithMutation(pos, source) { 79 | var numberOfCharacters = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : source.length; 80 | var lineCount = 0; 81 | var lastNewLinePos = -1; 82 | 83 | for (var i = 0; i < numberOfCharacters; i++) { 84 | if (source.charCodeAt(i) === 10) { 85 | lineCount++; 86 | lastNewLinePos = i; 87 | } 88 | } 89 | 90 | pos.offset += numberOfCharacters; 91 | pos.line += lineCount; 92 | pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos; 93 | } 94 | 95 | function advanceBy(context, numberOfCharacters) { 96 | var source = context.source; 97 | advancePositionWithMutation(context, source, numberOfCharacters); 98 | context.source = source.slice(numberOfCharacters); 99 | } 100 | 101 | function advanceSpaces(context) { 102 | var match = /^[\r\n\f ]+/.exec(context.source); 103 | 104 | if (match && match[0]) { 105 | advanceBy(context, match[0].length); 106 | } 107 | } 108 | 109 | function parseData(context, keyName) { 110 | advanceSpaces(context); 111 | var start = getCursor(context); 112 | var key = keyName || parseKey(context); 113 | 114 | var _parseValue = parseValue(context), 115 | value = _parseValue.value, 116 | type = _parseValue.type; 117 | 118 | var loc = getLoc(context, start); 119 | advanceSpaces(context); 120 | 121 | if (context.source[0] === ",") { 122 | advanceBy(context, 1); 123 | advanceSpaces(context); 124 | } 125 | 126 | return { 127 | key: key, 128 | value: value, 129 | type: type, 130 | loc: loc 131 | }; 132 | } 133 | 134 | function parseChildren(context) { 135 | var nodes = []; 136 | 137 | while (!isEnd(context)) { 138 | advanceSpaces(context); 139 | var s = context.source; 140 | 141 | if (s[0] === "{") { 142 | advanceBy(context, 1); 143 | } else if (s[0] === "}") { 144 | advanceBy(context, 1); 145 | advanceSpaces(context); 146 | return nodes; 147 | } else if (s[0] === "/") { 148 | if (s[1] === "/") { 149 | var lastNode = nodes[nodes.length - 1]; 150 | var lastLine = -1; 151 | 152 | if (lastNode) { 153 | lastLine = lastNode.loc.end.line; 154 | } 155 | 156 | var comment = parseComment(context, lastLine); 157 | 158 | if (comment) { 159 | nodes.push(comment); 160 | } 161 | 162 | advanceSpaces(context); 163 | } else { 164 | throw new Error(COMMENT_ERROR_MESSAGE); 165 | } 166 | } else { 167 | nodes.push(parseData(context)); 168 | } 169 | } 170 | 171 | return nodes; 172 | } 173 | 174 | function parseKey(context) { 175 | var s = context.source[0]; 176 | var match = []; 177 | 178 | if (s === '"') { 179 | match = /^"(.[^"]*)/i.exec(context.source); 180 | } else if (s === "'") { 181 | match = /^'(.[^']*)/i.exec(context.source); 182 | } else { 183 | match = /(.[^:]*)/i.exec(context.source); 184 | match[1] = match[1].trim(); 185 | } 186 | 187 | advanceBy(context, match[0].length + 1); 188 | advanceSpaces(context); 189 | 190 | if (context.source[0] === ":") { 191 | advanceBy(context, 1); 192 | advanceSpaces(context); 193 | } 194 | 195 | return match[1]; 196 | } 197 | 198 | function parseNumber(context) { 199 | var match = NUMBER_REGX.exec(context.source); 200 | advanceBy(context, match[0].length); 201 | return match[1]; 202 | } 203 | 204 | function parseString(context) { 205 | var s = context.source[0]; 206 | var match = new RegExp("^".concat(s, "((?:\\.|[^\\").concat(s, "])*)").concat(s)).exec(context.source); 207 | advanceBy(context, match[0].length); 208 | return match[1]; 209 | } 210 | 211 | function parseNull(context) { 212 | advanceBy(context, 4); 213 | return "null"; 214 | } 215 | 216 | function parseBoolean(context) { 217 | var match = /^(true|false)/i.exec(context.source); 218 | advanceBy(context, match[0].length); 219 | return match[1]; 220 | } 221 | 222 | function parseUndefined(context) { 223 | advanceBy(context, 9); 224 | return "undefined"; 225 | } 226 | 227 | function parseValue(context) { 228 | var value = null; 229 | var type = null; 230 | var code = context.source[0]; 231 | 232 | if (NUMBER_REGX.test(context.source)) { 233 | value = parseNumber(context); 234 | type = NUMBER_TYPE; 235 | } else if (code === '"' || code === "'") { 236 | value = parseString(context); 237 | type = STRING_TYPE; 238 | } else if (code === "[") { 239 | advanceBy(context, 1); 240 | value = parseArray(context); 241 | type = ARRAY_TYPE; 242 | } else if (code === "{") { 243 | value = parseChildren(context); 244 | type = OBJECT_TYPE; 245 | } else if (context.source.indexOf("null") === 0) { 246 | value = parseNull(context); 247 | type = NULL_TYPE; 248 | } else if (context.source.indexOf("true") === 0 || context.source.indexOf("false") === 0) { 249 | value = parseBoolean(context); 250 | type = BOOLEAN_TYPE; 251 | } else if (context.source.indexOf("undefined") === 0) { 252 | value = parseUndefined(context); 253 | type = UNDEFINED_TYPE; 254 | } else { 255 | throw new Error(VALUE_ILLEGAL_ERROR_MESSAGE); 256 | } 257 | 258 | return { 259 | value: value, 260 | type: type 261 | }; 262 | } 263 | 264 | function parseArray(context) { 265 | var nodes = []; 266 | var lastLine = getCursor(context).line; 267 | advanceSpaces(context); 268 | 269 | while (!isEnd(context)) { 270 | var s = context.source[0]; 271 | 272 | if (s === "]") { 273 | advanceBy(context, 1); 274 | return nodes; 275 | } 276 | 277 | if (s === "}" || s === ":") { 278 | throw new Error(ARRAY_ERROR_MESSAGE); 279 | } 280 | 281 | if (context.source.indexOf("//") === 0) { 282 | var cv = parseComment(context, lastLine); 283 | 284 | if (cv) { 285 | nodes.push(cv); 286 | } 287 | 288 | advanceSpaces(context); 289 | } else { 290 | var itemValue = parseData(context, ARRAY_ITEM); 291 | lastLine = itemValue.loc.end.line; 292 | nodes.push(itemValue); 293 | } 294 | } 295 | 296 | throw new Error(ARRAY_ERROR_MESSAGE); 297 | } 298 | 299 | function parseComment(context, lastLine) { 300 | var currLine = getCursor(context).line; 301 | var key = lastLine === currLine ? LAST_COMMENT : NEXT_COMMENT; 302 | var match = /^\/\/(.*)?(?=\n)/i.exec(context.source); 303 | var start = getCursor(context); 304 | var comment = match[1]; 305 | advanceBy(context, match[0].length); 306 | 307 | if (!comment || !comment.trim()) { 308 | return null; 309 | } 310 | 311 | return { 312 | key: key, 313 | value: comment.trim(), 314 | type: COMMENT_TYPE, 315 | loc: getLoc(context, start) 316 | }; 317 | } 318 | 319 | function parse(input, options) { 320 | var context = createContext(input, options); 321 | return createRoot(parseChildren(context)); 322 | } 323 | 324 | function objectToString(o) { 325 | return Object.prototype.toString.call(o); 326 | } 327 | 328 | function isArray(value) { 329 | return _typeof(value) === "object" && objectToString(value).slice(8, -1) === "Array"; 330 | } 331 | 332 | function isObject(value) { 333 | return _typeof(value) === "object" && objectToString(value).slice(8, -1) === "Object"; 334 | } 335 | 336 | function upperCaseFirstChat(str) { 337 | return str.replace(/( |^)[a-z]/g, function (L) { 338 | return L.toUpperCase(); 339 | }); 340 | } 341 | 342 | function stringifyObjAndSort(obj) { 343 | var keys = Object.keys(obj).sort(); 344 | var res = "{"; 345 | 346 | var _iterator = _createForOfIteratorHelper(keys), 347 | _step; 348 | 349 | try { 350 | for (_iterator.s(); !(_step = _iterator.n()).done;) { 351 | var val = _step.value; 352 | res += "".concat(val, ":").concat(obj[val], ","); 353 | } 354 | } catch (err) { 355 | _iterator.e(err); 356 | } finally { 357 | _iterator.f(); 358 | } 359 | 360 | res = res.replace(/,$/, ""); 361 | res += "}"; 362 | return res; 363 | } 364 | 365 | function optimizeArray(arrTypes) { 366 | var root = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ""; 367 | var optionalKeys = []; 368 | var keyCountMap = {}; 369 | var objCount = 0; 370 | var newTypes = []; 371 | var typeObj = {}; 372 | 373 | var _loop = function _loop(i) { 374 | var type = arrTypes[i]; 375 | 376 | if (isObject(type)) { 377 | objCount++; 378 | Object.keys(type).forEach(function (key) { 379 | typeObj[key] = type[key]; 380 | 381 | if (keyCountMap["".concat(root, ">").concat(key)]) { 382 | keyCountMap["".concat(root, ">").concat(key)] += 1; 383 | } else { 384 | keyCountMap["".concat(root, ">").concat(key)] = 1; 385 | } 386 | }); 387 | } else { 388 | newTypes.push(type); 389 | } 390 | }; 391 | 392 | for (var i = 0; i < arrTypes.length; i++) { 393 | _loop(i); 394 | } 395 | 396 | Object.keys(keyCountMap).forEach(function (key) { 397 | if (keyCountMap[key] < objCount) { 398 | optionalKeys.push(key); 399 | } 400 | }); 401 | 402 | if (Object.keys(typeObj).length) { 403 | newTypes.push(typeObj); 404 | } 405 | 406 | return { 407 | optionalKeys: optionalKeys, 408 | newTypes: newTypes 409 | }; 410 | } 411 | 412 | function isOptional(optionalKeys, key) { 413 | var parent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ""; 414 | var newKey = parent + ">" + key; 415 | 416 | var _iterator2 = _createForOfIteratorHelper(optionalKeys), 417 | _step2; 418 | 419 | try { 420 | for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { 421 | var optionalKey = _step2.value; 422 | 423 | if (newKey === optionalKey) { 424 | return true; 425 | } 426 | } 427 | } catch (err) { 428 | _iterator2.e(err); 429 | } finally { 430 | _iterator2.f(); 431 | } 432 | 433 | return false; 434 | } 435 | 436 | var cache = resetCache(); 437 | 438 | function resetCache() { 439 | return { 440 | comments: {}, 441 | i: 0, 442 | lastI: null, 443 | lastNode: null, 444 | nextComment: [] 445 | }; 446 | } 447 | 448 | function normalEntryHandle(node, parent, options) { 449 | node.i = cache.i; 450 | 451 | if (node.key === ARRAY_ITEM) { 452 | cache.nextComment = []; 453 | 454 | if (options.parseArray) { 455 | parent.typeValue = parent.typeValue || []; 456 | parent.typeValue.push(node.type); 457 | } 458 | } else { 459 | parent.typeValue = parent.typeValue || {}; 460 | parent.typeValue[node.key] = node.type; 461 | handleNormalNodeComment(node); 462 | } 463 | } 464 | 465 | function handleNormalNodeComment(node) { 466 | cache.lastNode = node; 467 | 468 | if (cache.nextComment.length) { 469 | var comments = cache.comments; 470 | var key = node.key + cache.i; 471 | comments[key] = comments[key] || []; 472 | comments[key] = comments[key].concat(cache.nextComment); 473 | cache.nextComment = []; 474 | } 475 | } 476 | 477 | function cacheObjectComment(node) { 478 | if (cache.nextComment.length) { 479 | node.nextComment = cache.nextComment; 480 | cache.nextComment = []; 481 | } 482 | } 483 | 484 | function handleObjectNodeComment(node) { 485 | if (node.nextComment) { 486 | var comments = cache.comments; 487 | var key = node.key + node.i; 488 | comments[key] = comments[key] || []; 489 | comments[key] = comments[key].concat(node.nextComment); 490 | cache.nextComment = []; 491 | } 492 | } 493 | 494 | function traverseNode(nodes, parent, visiter) { 495 | nodes.forEach(function (node) { 496 | var visit = visiter[node.type]; 497 | 498 | if (visit) { 499 | visit.entry && visit.entry(node, parent); 500 | } 501 | 502 | if (isArray(node.value)) { 503 | traverseNode(node.value, node, visiter); 504 | } 505 | 506 | if (visit) { 507 | visit.exit && visit.exit(node, parent); 508 | } 509 | }); 510 | } 511 | 512 | function traverser(ast, visiter) { 513 | var root = visiter.Root; 514 | 515 | if (root) { 516 | root.entry && root.entry(ast, null); 517 | } 518 | 519 | traverseNode(ast.value, ast, visiter); 520 | 521 | if (root) { 522 | root.exit && root.exit(ast, null); 523 | } 524 | 525 | return ast; 526 | } 527 | 528 | function transform(ast, options) { 529 | var _traverser; 530 | 531 | traverser(ast, (_traverser = {}, _defineProperty(_traverser, STRING_TYPE, { 532 | entry: function entry(node, parent) { 533 | normalEntryHandle(node, parent, options); 534 | } 535 | }), _defineProperty(_traverser, NUMBER_TYPE, { 536 | entry: function entry(node, parent) { 537 | normalEntryHandle(node, parent, options); 538 | } 539 | }), _defineProperty(_traverser, OBJECT_TYPE, { 540 | entry: function entry(node, parent) { 541 | if (node.key === ARRAY_ITEM) { 542 | cache.nextComment = []; 543 | 544 | if (options.parseArray) { 545 | parent.typeValue = parent.typeValue || []; 546 | node.typeValue = {}; 547 | parent.typeValue.push(node.typeValue); 548 | node.i = cache.i; 549 | cache.i++; 550 | } 551 | } else { 552 | parent.typeValue = parent.typeValue || {}; 553 | parent.typeValue[node.key] = node.typeValue = {}; 554 | 555 | if (options.comment === "inline") { 556 | cacheObjectComment(node); 557 | } else if (options.comment === "block") { 558 | handleNormalNodeComment(node); 559 | } 560 | 561 | node.i = cache.i; 562 | cache.i++; 563 | } 564 | }, 565 | exit: function exit(node) { 566 | if (options.comment === "inline") { 567 | node.i = cache.i; 568 | handleObjectNodeComment(node); 569 | } 570 | 571 | cache.lastNode = node; 572 | } 573 | }), _defineProperty(_traverser, ARRAY_TYPE, { 574 | entry: function entry(node, parent) { 575 | if (node.key === ARRAY_ITEM) { 576 | cache.nextComment = []; 577 | 578 | if (options.parseArray) { 579 | parent.typeValue = parent.typeValue || []; 580 | node.typeValue = []; 581 | parent.typeValue.push(node.typeValue); 582 | } 583 | } else { 584 | parent.typeValue = parent.typeValue || {}; 585 | parent.typeValue[node.key] = node.typeValue = []; 586 | 587 | if (options.comment === "inline") { 588 | cacheObjectComment(node); 589 | } else if (options.comment === "block") { 590 | handleNormalNodeComment(node); 591 | } 592 | } 593 | 594 | node.i = cache.i; 595 | }, 596 | exit: function exit(node) { 597 | if (options.comment === "inline") { 598 | node.i = cache.i; 599 | handleObjectNodeComment(node); 600 | } 601 | 602 | cache.lastNode = node; 603 | } 604 | }), _defineProperty(_traverser, NULL_TYPE, { 605 | entry: function entry(node, parent) { 606 | normalEntryHandle(node, parent, options); 607 | } 608 | }), _defineProperty(_traverser, BOOLEAN_TYPE, { 609 | entry: function entry(node, parent) { 610 | normalEntryHandle(node, parent, options); 611 | } 612 | }), _defineProperty(_traverser, UNDEFINED_TYPE, { 613 | entry: function entry(node, parent) { 614 | normalEntryHandle(node, parent, options); 615 | } 616 | }), _defineProperty(_traverser, COMMENT_TYPE, { 617 | entry: function entry(node) { 618 | if (node.key === LAST_COMMENT) { 619 | var key = cache.lastNode.key + cache.lastNode.i; 620 | cache.comments[key] = cache.comments[key] || []; 621 | cache.comments[key].push(node.value); 622 | } else { 623 | cache.nextComment.push(node.value); 624 | } 625 | } 626 | }), _traverser)); 627 | ast.comments = cache.comments; 628 | cache = resetCache(); 629 | return ast; 630 | } 631 | 632 | var Generate = /*#__PURE__*/function () { 633 | function Generate(ast, options) { 634 | _classCallCheck(this, Generate); 635 | 636 | this.ast = ast; 637 | this.options = options; 638 | this.prefix = options.typePrefix; 639 | this.suffix = options.typeSuffix; 640 | this.vars = ""; 641 | this.i = 0; 642 | this.j = -1; 643 | this.level = 1; 644 | this.objValueMap = /* @__PURE__ */new Map(); 645 | } 646 | 647 | _createClass(Generate, [{ 648 | key: "beginGen", 649 | value: function beginGen() { 650 | var originName = this.genName("Result"); 651 | var typeValue = this.ast.typeValue; 652 | var code = this.gen(typeValue); 653 | var isInterface = this.options.genType === "interface"; 654 | return "".concat(this.vars).concat(this.options.genType, " ").concat(originName).concat(isInterface ? "" : " =", " ").concat(code).concat(this.options.semicolon ? ";" : "", "\n"); 655 | } 656 | }, { 657 | key: "gen", 658 | value: function gen(typeValue) { 659 | var optionalKeys = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; 660 | var code = "{\n"; 661 | 662 | for (var key in typeValue) { 663 | var type = typeValue[key]; 664 | 665 | if (this.options.comment === "block") { 666 | code += this.genBlockComment(key); 667 | } 668 | 669 | code += this.genKey(key, isOptional(optionalKeys, key)); 670 | 671 | if (isObject(type)) { 672 | code += this.genObjcet(key, type); 673 | } else if (isArray(type)) { 674 | code += this.options.parseArray ? this.genArray(key, type) : "Array"; 675 | } else { 676 | code += type; 677 | } 678 | 679 | if (this.options.semicolon) { 680 | code += ";"; 681 | } 682 | 683 | if (this.options.comment === "inline") { 684 | code += this.genInlineComment(key); 685 | } 686 | 687 | code += "\n"; 688 | } 689 | 690 | if (!this.options.splitType) { 691 | code += this.genFormatChat(this.level - 1); 692 | } 693 | 694 | code += "}"; 695 | return code; 696 | } 697 | }, { 698 | key: "genName", 699 | value: function genName(key) { 700 | this.j++; 701 | return "".concat(this.prefix).concat(upperCaseFirstChat(key), "$").concat(this.j).concat(this.suffix); 702 | } 703 | }, { 704 | key: "genKey", 705 | value: function genKey(key) { 706 | var optional = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 707 | return "".concat(this.genFormatChat(this.level)).concat(key).concat(this.options.required && !optional ? ": " : "?: "); 708 | } 709 | }, { 710 | key: "genFormatChat", 711 | value: function genFormatChat(level) { 712 | var indent = this.options.indent; 713 | 714 | if (this.options.splitType) { 715 | return " ".repeat(indent); 716 | } 717 | 718 | return " ".repeat(level * indent); 719 | } 720 | }, { 721 | key: "genInlineComment", 722 | value: function genInlineComment(key) { 723 | var name = key + this.i; 724 | var comments = this.ast.comments[name]; 725 | 726 | if (comments && comments.length) { 727 | var code = " // "; 728 | comments.forEach(function (comment) { 729 | code += comment + "; "; 730 | }); 731 | code = code.substring(0, code.length - 2); 732 | return code; 733 | } 734 | 735 | return ""; 736 | } 737 | }, { 738 | key: "genBlockComment", 739 | value: function genBlockComment(key) { 740 | var _this = this; 741 | 742 | var code = ""; 743 | var name = key + this.i; 744 | var comments = this.ast.comments[name]; 745 | 746 | if (comments && comments.length) { 747 | comments.forEach(function (comment) { 748 | code += _this.genFormatChat(_this.level) + "// " + comment + "\n"; 749 | }); 750 | } 751 | 752 | return code; 753 | } 754 | }, { 755 | key: "genObjcet", 756 | value: function genObjcet(key, type) { 757 | var optionalKeys = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; 758 | var code = ""; 759 | this.level++; 760 | this.i++; 761 | var objType = this.gen(type, optionalKeys); 762 | 763 | if (this.options.splitType) { 764 | code += this.genObjectCodeWithVar(objType, key, type); 765 | } else { 766 | code += objType; 767 | } 768 | 769 | this.level--; 770 | return code; 771 | } 772 | }, { 773 | key: "genObjectCodeWithVar", 774 | value: function genObjectCodeWithVar(objType, key, type) { 775 | var val = stringifyObjAndSort(type); 776 | 777 | if (this.objValueMap.has(val)) { 778 | return this.objValueMap.get(val); 779 | } 780 | 781 | var varName = this.genName(key); 782 | var isInterface = this.options.genType === "interface"; 783 | this.objValueMap.set(val, varName); 784 | this.vars += "".concat(this.options.genType, " ").concat(varName).concat(isInterface ? "" : " =", " ").concat(objType).concat(this.options.semicolon ? ";" : "", "\n\n"); 785 | return varName; 786 | } 787 | }, { 788 | key: "genArray", 789 | value: function genArray(key, types) { 790 | var _this2 = this; 791 | 792 | if (types.length === 0) { 793 | return "Array< unknown >"; 794 | } 795 | 796 | var code = "Array< "; 797 | var arrTypes = /* @__PURE__ */new Set(); 798 | var optionalKeys = []; 799 | 800 | if (this.options.optimizeArrayOptional) { 801 | var _optimizeArray = optimizeArray(types), 802 | keys = _optimizeArray.optionalKeys, 803 | newTypes = _optimizeArray.newTypes; 804 | 805 | optionalKeys = keys; 806 | types = newTypes; 807 | } 808 | 809 | types.forEach(function (type) { 810 | if (isArray(type)) { 811 | arrTypes.add(_this2.genArray(key, type)); 812 | } 813 | 814 | if (isObject(type)) { 815 | arrTypes.add(_this2.genObjcet(key, type, optionalKeys)); 816 | } else { 817 | arrTypes.add(type); 818 | } 819 | }); 820 | code += Array.from(arrTypes).join(" | "); 821 | code += " >"; 822 | return code; 823 | } 824 | }]); 825 | 826 | return Generate; 827 | }(); 828 | 829 | function generate(ast, options) { 830 | return new Generate(ast, options).beginGen(); 831 | } 832 | 833 | function initOptions(options) { 834 | var defaultOptions = { 835 | splitType: true, 836 | parseArray: false, 837 | required: true, 838 | semicolon: false, 839 | typeSuffix: "Type", 840 | typePrefix: "", 841 | indent: 2, 842 | comment: false, 843 | optimizeArrayOptional: false, 844 | genType: "type" 845 | }; 846 | Object.assign(defaultOptions, options); 847 | return defaultOptions; 848 | } 849 | 850 | function json2ts(code) { 851 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 852 | var finalOptions = initOptions(options); 853 | var ast = parse(code, finalOptions); 854 | transform(ast, finalOptions); 855 | return generate(ast, finalOptions); 856 | } 857 | 858 | export { ARRAY_ERROR_MESSAGE, ARRAY_ITEM, ARRAY_TYPE, BOOLEAN_TYPE, COMMENT_ERROR_MESSAGE, COMMENT_TYPE, LAST_COMMENT, NEXT_COMMENT, NULL_TYPE, NUMBER_TYPE, OBJECT_TYPE, ROOT_KEY, ROOT_TYPE, STRING_TYPE, UNDEFINED_TYPE, VALUE_ILLEGAL_ERROR_MESSAGE, json2ts as default, json2ts, parse, traverser }; 859 | -------------------------------------------------------------------------------- /dist/index.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | if (typeof define === "function" && define.amd) { 3 | define(["exports"], factory); 4 | } else if (typeof exports !== "undefined") { 5 | factory(exports); 6 | } else { 7 | var mod = { 8 | exports: {} 9 | }; 10 | factory(mod.exports); 11 | global.json2ts = mod.exports; 12 | } 13 | })(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports) { 14 | "use strict"; 15 | 16 | Object.defineProperty(_exports, "__esModule", { 17 | value: true 18 | }); 19 | _exports.VALUE_ILLEGAL_ERROR_MESSAGE = _exports.UNDEFINED_TYPE = _exports.STRING_TYPE = _exports.ROOT_TYPE = _exports.ROOT_KEY = _exports.OBJECT_TYPE = _exports.NUMBER_TYPE = _exports.NULL_TYPE = _exports.NEXT_COMMENT = _exports.LAST_COMMENT = _exports.COMMENT_TYPE = _exports.COMMENT_ERROR_MESSAGE = _exports.BOOLEAN_TYPE = _exports.ARRAY_TYPE = _exports.ARRAY_ITEM = _exports.ARRAY_ERROR_MESSAGE = void 0; 20 | _exports.json2ts = _exports["default"] = json2ts; 21 | _exports.parse = parse; 22 | _exports.traverser = traverser; 23 | 24 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 25 | 26 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 27 | 28 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 29 | 30 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 31 | 32 | function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } 33 | 34 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } 35 | 36 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } 37 | 38 | function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } 39 | 40 | var ROOT_TYPE = "Root"; 41 | _exports.ROOT_TYPE = ROOT_TYPE; 42 | var STRING_TYPE = "string"; 43 | _exports.STRING_TYPE = STRING_TYPE; 44 | var NUMBER_TYPE = "number"; 45 | _exports.NUMBER_TYPE = NUMBER_TYPE; 46 | var NULL_TYPE = "null"; 47 | _exports.NULL_TYPE = NULL_TYPE; 48 | var BOOLEAN_TYPE = "boolean"; 49 | _exports.BOOLEAN_TYPE = BOOLEAN_TYPE; 50 | var UNDEFINED_TYPE = "undefined"; 51 | _exports.UNDEFINED_TYPE = UNDEFINED_TYPE; 52 | var OBJECT_TYPE = "Object"; 53 | _exports.OBJECT_TYPE = OBJECT_TYPE; 54 | var ARRAY_TYPE = "Array"; 55 | _exports.ARRAY_TYPE = ARRAY_TYPE; 56 | var COMMENT_TYPE = "Comment"; 57 | _exports.COMMENT_TYPE = COMMENT_TYPE; 58 | var ROOT_KEY = "root"; 59 | _exports.ROOT_KEY = ROOT_KEY; 60 | var ARRAY_ITEM = "$ARRAY_ITEM$"; 61 | _exports.ARRAY_ITEM = ARRAY_ITEM; 62 | var LAST_COMMENT = "$LAST_COMMENT$"; 63 | _exports.LAST_COMMENT = LAST_COMMENT; 64 | var NEXT_COMMENT = "$NEXT_COMMENT$"; 65 | _exports.NEXT_COMMENT = NEXT_COMMENT; 66 | var ARRAY_ERROR_MESSAGE = "array should be closed"; 67 | _exports.ARRAY_ERROR_MESSAGE = ARRAY_ERROR_MESSAGE; 68 | var COMMENT_ERROR_MESSAGE = "comment is illegal"; 69 | _exports.COMMENT_ERROR_MESSAGE = COMMENT_ERROR_MESSAGE; 70 | var VALUE_ILLEGAL_ERROR_MESSAGE = "value is illegal"; 71 | _exports.VALUE_ILLEGAL_ERROR_MESSAGE = VALUE_ILLEGAL_ERROR_MESSAGE; 72 | var NUMBER_REGX = /^[-+]?([0-9]+\.?\d*)([Ee][-+]?[0-9]+)?/; 73 | 74 | function getCursor(context) { 75 | var offset = context.offset, 76 | column = context.column, 77 | line = context.line; 78 | return { 79 | offset: offset, 80 | column: column, 81 | line: line 82 | }; 83 | } 84 | 85 | function getLoc(context, start, end) { 86 | end = end || getCursor(context); 87 | return { 88 | start: start, 89 | end: end, 90 | source: context.originalSource.slice(start.offset, end.offset) 91 | }; 92 | } 93 | 94 | function createContext(content, options) { 95 | return { 96 | options: options, 97 | column: 1, 98 | line: 1, 99 | offset: 0, 100 | originalSource: content, 101 | source: content 102 | }; 103 | } 104 | 105 | function createRoot(nodes) { 106 | return { 107 | key: ROOT_KEY, 108 | type: ROOT_TYPE, 109 | value: nodes 110 | }; 111 | } 112 | 113 | function isEnd(context) { 114 | return !context.source; 115 | } 116 | 117 | function advancePositionWithMutation(pos, source) { 118 | var numberOfCharacters = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : source.length; 119 | var lineCount = 0; 120 | var lastNewLinePos = -1; 121 | 122 | for (var i = 0; i < numberOfCharacters; i++) { 123 | if (source.charCodeAt(i) === 10) { 124 | lineCount++; 125 | lastNewLinePos = i; 126 | } 127 | } 128 | 129 | pos.offset += numberOfCharacters; 130 | pos.line += lineCount; 131 | pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos; 132 | } 133 | 134 | function advanceBy(context, numberOfCharacters) { 135 | var source = context.source; 136 | advancePositionWithMutation(context, source, numberOfCharacters); 137 | context.source = source.slice(numberOfCharacters); 138 | } 139 | 140 | function advanceSpaces(context) { 141 | var match = /^[\r\n\f ]+/.exec(context.source); 142 | 143 | if (match && match[0]) { 144 | advanceBy(context, match[0].length); 145 | } 146 | } 147 | 148 | function parseData(context, keyName) { 149 | advanceSpaces(context); 150 | var start = getCursor(context); 151 | var key = keyName || parseKey(context); 152 | 153 | var _parseValue = parseValue(context), 154 | value = _parseValue.value, 155 | type = _parseValue.type; 156 | 157 | var loc = getLoc(context, start); 158 | advanceSpaces(context); 159 | 160 | if (context.source[0] === ",") { 161 | advanceBy(context, 1); 162 | advanceSpaces(context); 163 | } 164 | 165 | return { 166 | key: key, 167 | value: value, 168 | type: type, 169 | loc: loc 170 | }; 171 | } 172 | 173 | function parseChildren(context) { 174 | var nodes = []; 175 | 176 | while (!isEnd(context)) { 177 | advanceSpaces(context); 178 | var s = context.source; 179 | 180 | if (s[0] === "{") { 181 | advanceBy(context, 1); 182 | } else if (s[0] === "}") { 183 | advanceBy(context, 1); 184 | advanceSpaces(context); 185 | return nodes; 186 | } else if (s[0] === "/") { 187 | if (s[1] === "/") { 188 | var lastNode = nodes[nodes.length - 1]; 189 | var lastLine = -1; 190 | 191 | if (lastNode) { 192 | lastLine = lastNode.loc.end.line; 193 | } 194 | 195 | var comment = parseComment(context, lastLine); 196 | 197 | if (comment) { 198 | nodes.push(comment); 199 | } 200 | 201 | advanceSpaces(context); 202 | } else { 203 | throw new Error(COMMENT_ERROR_MESSAGE); 204 | } 205 | } else { 206 | nodes.push(parseData(context)); 207 | } 208 | } 209 | 210 | return nodes; 211 | } 212 | 213 | function parseKey(context) { 214 | var s = context.source[0]; 215 | var match = []; 216 | 217 | if (s === '"') { 218 | match = /^"(.[^"]*)/i.exec(context.source); 219 | } else if (s === "'") { 220 | match = /^'(.[^']*)/i.exec(context.source); 221 | } else { 222 | match = /(.[^:]*)/i.exec(context.source); 223 | match[1] = match[1].trim(); 224 | } 225 | 226 | advanceBy(context, match[0].length + 1); 227 | advanceSpaces(context); 228 | 229 | if (context.source[0] === ":") { 230 | advanceBy(context, 1); 231 | advanceSpaces(context); 232 | } 233 | 234 | return match[1]; 235 | } 236 | 237 | function parseNumber(context) { 238 | var match = NUMBER_REGX.exec(context.source); 239 | advanceBy(context, match[0].length); 240 | return match[1]; 241 | } 242 | 243 | function parseString(context) { 244 | var s = context.source[0]; 245 | var match = new RegExp("^".concat(s, "((?:\\.|[^\\").concat(s, "])*)").concat(s)).exec(context.source); 246 | advanceBy(context, match[0].length); 247 | return match[1]; 248 | } 249 | 250 | function parseNull(context) { 251 | advanceBy(context, 4); 252 | return "null"; 253 | } 254 | 255 | function parseBoolean(context) { 256 | var match = /^(true|false)/i.exec(context.source); 257 | advanceBy(context, match[0].length); 258 | return match[1]; 259 | } 260 | 261 | function parseUndefined(context) { 262 | advanceBy(context, 9); 263 | return "undefined"; 264 | } 265 | 266 | function parseValue(context) { 267 | var value = null; 268 | var type = null; 269 | var code = context.source[0]; 270 | 271 | if (NUMBER_REGX.test(context.source)) { 272 | value = parseNumber(context); 273 | type = NUMBER_TYPE; 274 | } else if (code === '"' || code === "'") { 275 | value = parseString(context); 276 | type = STRING_TYPE; 277 | } else if (code === "[") { 278 | advanceBy(context, 1); 279 | value = parseArray(context); 280 | type = ARRAY_TYPE; 281 | } else if (code === "{") { 282 | value = parseChildren(context); 283 | type = OBJECT_TYPE; 284 | } else if (context.source.indexOf("null") === 0) { 285 | value = parseNull(context); 286 | type = NULL_TYPE; 287 | } else if (context.source.indexOf("true") === 0 || context.source.indexOf("false") === 0) { 288 | value = parseBoolean(context); 289 | type = BOOLEAN_TYPE; 290 | } else if (context.source.indexOf("undefined") === 0) { 291 | value = parseUndefined(context); 292 | type = UNDEFINED_TYPE; 293 | } else { 294 | throw new Error(VALUE_ILLEGAL_ERROR_MESSAGE); 295 | } 296 | 297 | return { 298 | value: value, 299 | type: type 300 | }; 301 | } 302 | 303 | function parseArray(context) { 304 | var nodes = []; 305 | var lastLine = getCursor(context).line; 306 | advanceSpaces(context); 307 | 308 | while (!isEnd(context)) { 309 | var s = context.source[0]; 310 | 311 | if (s === "]") { 312 | advanceBy(context, 1); 313 | return nodes; 314 | } 315 | 316 | if (s === "}" || s === ":") { 317 | throw new Error(ARRAY_ERROR_MESSAGE); 318 | } 319 | 320 | if (context.source.indexOf("//") === 0) { 321 | var cv = parseComment(context, lastLine); 322 | 323 | if (cv) { 324 | nodes.push(cv); 325 | } 326 | 327 | advanceSpaces(context); 328 | } else { 329 | var itemValue = parseData(context, ARRAY_ITEM); 330 | lastLine = itemValue.loc.end.line; 331 | nodes.push(itemValue); 332 | } 333 | } 334 | 335 | throw new Error(ARRAY_ERROR_MESSAGE); 336 | } 337 | 338 | function parseComment(context, lastLine) { 339 | var currLine = getCursor(context).line; 340 | var key = lastLine === currLine ? LAST_COMMENT : NEXT_COMMENT; 341 | var match = /^\/\/(.*)?(?=\n)/i.exec(context.source); 342 | var start = getCursor(context); 343 | var comment = match[1]; 344 | advanceBy(context, match[0].length); 345 | 346 | if (!comment || !comment.trim()) { 347 | return null; 348 | } 349 | 350 | return { 351 | key: key, 352 | value: comment.trim(), 353 | type: COMMENT_TYPE, 354 | loc: getLoc(context, start) 355 | }; 356 | } 357 | 358 | function parse(input, options) { 359 | var context = createContext(input, options); 360 | return createRoot(parseChildren(context)); 361 | } 362 | 363 | function objectToString(o) { 364 | return Object.prototype.toString.call(o); 365 | } 366 | 367 | function isArray(value) { 368 | return _typeof(value) === "object" && objectToString(value).slice(8, -1) === "Array"; 369 | } 370 | 371 | function isObject(value) { 372 | return _typeof(value) === "object" && objectToString(value).slice(8, -1) === "Object"; 373 | } 374 | 375 | function upperCaseFirstChat(str) { 376 | return str.replace(/( |^)[a-z]/g, function (L) { 377 | return L.toUpperCase(); 378 | }); 379 | } 380 | 381 | function stringifyObjAndSort(obj) { 382 | var keys = Object.keys(obj).sort(); 383 | var res = "{"; 384 | 385 | var _iterator = _createForOfIteratorHelper(keys), 386 | _step; 387 | 388 | try { 389 | for (_iterator.s(); !(_step = _iterator.n()).done;) { 390 | var val = _step.value; 391 | res += "".concat(val, ":").concat(obj[val], ","); 392 | } 393 | } catch (err) { 394 | _iterator.e(err); 395 | } finally { 396 | _iterator.f(); 397 | } 398 | 399 | res = res.replace(/,$/, ""); 400 | res += "}"; 401 | return res; 402 | } 403 | 404 | function optimizeArray(arrTypes) { 405 | var root = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ""; 406 | var optionalKeys = []; 407 | var keyCountMap = {}; 408 | var objCount = 0; 409 | var newTypes = []; 410 | var typeObj = {}; 411 | 412 | var _loop = function _loop(i) { 413 | var type = arrTypes[i]; 414 | 415 | if (isObject(type)) { 416 | objCount++; 417 | Object.keys(type).forEach(function (key) { 418 | typeObj[key] = type[key]; 419 | 420 | if (keyCountMap["".concat(root, ">").concat(key)]) { 421 | keyCountMap["".concat(root, ">").concat(key)] += 1; 422 | } else { 423 | keyCountMap["".concat(root, ">").concat(key)] = 1; 424 | } 425 | }); 426 | } else { 427 | newTypes.push(type); 428 | } 429 | }; 430 | 431 | for (var i = 0; i < arrTypes.length; i++) { 432 | _loop(i); 433 | } 434 | 435 | Object.keys(keyCountMap).forEach(function (key) { 436 | if (keyCountMap[key] < objCount) { 437 | optionalKeys.push(key); 438 | } 439 | }); 440 | 441 | if (Object.keys(typeObj).length) { 442 | newTypes.push(typeObj); 443 | } 444 | 445 | return { 446 | optionalKeys: optionalKeys, 447 | newTypes: newTypes 448 | }; 449 | } 450 | 451 | function isOptional(optionalKeys, key) { 452 | var parent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ""; 453 | var newKey = parent + ">" + key; 454 | 455 | var _iterator2 = _createForOfIteratorHelper(optionalKeys), 456 | _step2; 457 | 458 | try { 459 | for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { 460 | var optionalKey = _step2.value; 461 | 462 | if (newKey === optionalKey) { 463 | return true; 464 | } 465 | } 466 | } catch (err) { 467 | _iterator2.e(err); 468 | } finally { 469 | _iterator2.f(); 470 | } 471 | 472 | return false; 473 | } 474 | 475 | var cache = resetCache(); 476 | 477 | function resetCache() { 478 | return { 479 | comments: {}, 480 | i: 0, 481 | lastI: null, 482 | lastNode: null, 483 | nextComment: [] 484 | }; 485 | } 486 | 487 | function normalEntryHandle(node, parent, options) { 488 | node.i = cache.i; 489 | 490 | if (node.key === ARRAY_ITEM) { 491 | cache.nextComment = []; 492 | 493 | if (options.parseArray) { 494 | parent.typeValue = parent.typeValue || []; 495 | parent.typeValue.push(node.type); 496 | } 497 | } else { 498 | parent.typeValue = parent.typeValue || {}; 499 | parent.typeValue[node.key] = node.type; 500 | handleNormalNodeComment(node); 501 | } 502 | } 503 | 504 | function handleNormalNodeComment(node) { 505 | cache.lastNode = node; 506 | 507 | if (cache.nextComment.length) { 508 | var comments = cache.comments; 509 | var key = node.key + cache.i; 510 | comments[key] = comments[key] || []; 511 | comments[key] = comments[key].concat(cache.nextComment); 512 | cache.nextComment = []; 513 | } 514 | } 515 | 516 | function cacheObjectComment(node) { 517 | if (cache.nextComment.length) { 518 | node.nextComment = cache.nextComment; 519 | cache.nextComment = []; 520 | } 521 | } 522 | 523 | function handleObjectNodeComment(node) { 524 | if (node.nextComment) { 525 | var comments = cache.comments; 526 | var key = node.key + node.i; 527 | comments[key] = comments[key] || []; 528 | comments[key] = comments[key].concat(node.nextComment); 529 | cache.nextComment = []; 530 | } 531 | } 532 | 533 | function traverseNode(nodes, parent, visiter) { 534 | nodes.forEach(function (node) { 535 | var visit = visiter[node.type]; 536 | 537 | if (visit) { 538 | visit.entry && visit.entry(node, parent); 539 | } 540 | 541 | if (isArray(node.value)) { 542 | traverseNode(node.value, node, visiter); 543 | } 544 | 545 | if (visit) { 546 | visit.exit && visit.exit(node, parent); 547 | } 548 | }); 549 | } 550 | 551 | function traverser(ast, visiter) { 552 | var root = visiter.Root; 553 | 554 | if (root) { 555 | root.entry && root.entry(ast, null); 556 | } 557 | 558 | traverseNode(ast.value, ast, visiter); 559 | 560 | if (root) { 561 | root.exit && root.exit(ast, null); 562 | } 563 | 564 | return ast; 565 | } 566 | 567 | function transform(ast, options) { 568 | var _traverser; 569 | 570 | traverser(ast, (_traverser = {}, _defineProperty(_traverser, STRING_TYPE, { 571 | entry: function entry(node, parent) { 572 | normalEntryHandle(node, parent, options); 573 | } 574 | }), _defineProperty(_traverser, NUMBER_TYPE, { 575 | entry: function entry(node, parent) { 576 | normalEntryHandle(node, parent, options); 577 | } 578 | }), _defineProperty(_traverser, OBJECT_TYPE, { 579 | entry: function entry(node, parent) { 580 | if (node.key === ARRAY_ITEM) { 581 | cache.nextComment = []; 582 | 583 | if (options.parseArray) { 584 | parent.typeValue = parent.typeValue || []; 585 | node.typeValue = {}; 586 | parent.typeValue.push(node.typeValue); 587 | node.i = cache.i; 588 | cache.i++; 589 | } 590 | } else { 591 | parent.typeValue = parent.typeValue || {}; 592 | parent.typeValue[node.key] = node.typeValue = {}; 593 | 594 | if (options.comment === "inline") { 595 | cacheObjectComment(node); 596 | } else if (options.comment === "block") { 597 | handleNormalNodeComment(node); 598 | } 599 | 600 | node.i = cache.i; 601 | cache.i++; 602 | } 603 | }, 604 | exit: function exit(node) { 605 | if (options.comment === "inline") { 606 | node.i = cache.i; 607 | handleObjectNodeComment(node); 608 | } 609 | 610 | cache.lastNode = node; 611 | } 612 | }), _defineProperty(_traverser, ARRAY_TYPE, { 613 | entry: function entry(node, parent) { 614 | if (node.key === ARRAY_ITEM) { 615 | cache.nextComment = []; 616 | 617 | if (options.parseArray) { 618 | parent.typeValue = parent.typeValue || []; 619 | node.typeValue = []; 620 | parent.typeValue.push(node.typeValue); 621 | } 622 | } else { 623 | parent.typeValue = parent.typeValue || {}; 624 | parent.typeValue[node.key] = node.typeValue = []; 625 | 626 | if (options.comment === "inline") { 627 | cacheObjectComment(node); 628 | } else if (options.comment === "block") { 629 | handleNormalNodeComment(node); 630 | } 631 | } 632 | 633 | node.i = cache.i; 634 | }, 635 | exit: function exit(node) { 636 | if (options.comment === "inline") { 637 | node.i = cache.i; 638 | handleObjectNodeComment(node); 639 | } 640 | 641 | cache.lastNode = node; 642 | } 643 | }), _defineProperty(_traverser, NULL_TYPE, { 644 | entry: function entry(node, parent) { 645 | normalEntryHandle(node, parent, options); 646 | } 647 | }), _defineProperty(_traverser, BOOLEAN_TYPE, { 648 | entry: function entry(node, parent) { 649 | normalEntryHandle(node, parent, options); 650 | } 651 | }), _defineProperty(_traverser, UNDEFINED_TYPE, { 652 | entry: function entry(node, parent) { 653 | normalEntryHandle(node, parent, options); 654 | } 655 | }), _defineProperty(_traverser, COMMENT_TYPE, { 656 | entry: function entry(node) { 657 | if (node.key === LAST_COMMENT) { 658 | var key = cache.lastNode.key + cache.lastNode.i; 659 | cache.comments[key] = cache.comments[key] || []; 660 | cache.comments[key].push(node.value); 661 | } else { 662 | cache.nextComment.push(node.value); 663 | } 664 | } 665 | }), _traverser)); 666 | ast.comments = cache.comments; 667 | cache = resetCache(); 668 | return ast; 669 | } 670 | 671 | var Generate = /*#__PURE__*/function () { 672 | function Generate(ast, options) { 673 | _classCallCheck(this, Generate); 674 | 675 | this.ast = ast; 676 | this.options = options; 677 | this.prefix = options.typePrefix; 678 | this.suffix = options.typeSuffix; 679 | this.vars = ""; 680 | this.i = 0; 681 | this.j = -1; 682 | this.level = 1; 683 | this.objValueMap = /* @__PURE__ */new Map(); 684 | } 685 | 686 | _createClass(Generate, [{ 687 | key: "beginGen", 688 | value: function beginGen() { 689 | var originName = this.genName("Result"); 690 | var typeValue = this.ast.typeValue; 691 | var code = this.gen(typeValue); 692 | var isInterface = this.options.genType === "interface"; 693 | return "".concat(this.vars).concat(this.options.genType, " ").concat(originName).concat(isInterface ? "" : " =", " ").concat(code).concat(this.options.semicolon ? ";" : "", "\n"); 694 | } 695 | }, { 696 | key: "gen", 697 | value: function gen(typeValue) { 698 | var optionalKeys = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; 699 | var code = "{\n"; 700 | 701 | for (var key in typeValue) { 702 | var type = typeValue[key]; 703 | 704 | if (this.options.comment === "block") { 705 | code += this.genBlockComment(key); 706 | } 707 | 708 | code += this.genKey(key, isOptional(optionalKeys, key)); 709 | 710 | if (isObject(type)) { 711 | code += this.genObjcet(key, type); 712 | } else if (isArray(type)) { 713 | code += this.options.parseArray ? this.genArray(key, type) : "Array"; 714 | } else { 715 | code += type; 716 | } 717 | 718 | if (this.options.semicolon) { 719 | code += ";"; 720 | } 721 | 722 | if (this.options.comment === "inline") { 723 | code += this.genInlineComment(key); 724 | } 725 | 726 | code += "\n"; 727 | } 728 | 729 | if (!this.options.splitType) { 730 | code += this.genFormatChat(this.level - 1); 731 | } 732 | 733 | code += "}"; 734 | return code; 735 | } 736 | }, { 737 | key: "genName", 738 | value: function genName(key) { 739 | this.j++; 740 | return "".concat(this.prefix).concat(upperCaseFirstChat(key), "$").concat(this.j).concat(this.suffix); 741 | } 742 | }, { 743 | key: "genKey", 744 | value: function genKey(key) { 745 | var optional = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 746 | return "".concat(this.genFormatChat(this.level)).concat(key).concat(this.options.required && !optional ? ": " : "?: "); 747 | } 748 | }, { 749 | key: "genFormatChat", 750 | value: function genFormatChat(level) { 751 | var indent = this.options.indent; 752 | 753 | if (this.options.splitType) { 754 | return " ".repeat(indent); 755 | } 756 | 757 | return " ".repeat(level * indent); 758 | } 759 | }, { 760 | key: "genInlineComment", 761 | value: function genInlineComment(key) { 762 | var name = key + this.i; 763 | var comments = this.ast.comments[name]; 764 | 765 | if (comments && comments.length) { 766 | var code = " // "; 767 | comments.forEach(function (comment) { 768 | code += comment + "; "; 769 | }); 770 | code = code.substring(0, code.length - 2); 771 | return code; 772 | } 773 | 774 | return ""; 775 | } 776 | }, { 777 | key: "genBlockComment", 778 | value: function genBlockComment(key) { 779 | var _this = this; 780 | 781 | var code = ""; 782 | var name = key + this.i; 783 | var comments = this.ast.comments[name]; 784 | 785 | if (comments && comments.length) { 786 | comments.forEach(function (comment) { 787 | code += _this.genFormatChat(_this.level) + "// " + comment + "\n"; 788 | }); 789 | } 790 | 791 | return code; 792 | } 793 | }, { 794 | key: "genObjcet", 795 | value: function genObjcet(key, type) { 796 | var optionalKeys = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; 797 | var code = ""; 798 | this.level++; 799 | this.i++; 800 | var objType = this.gen(type, optionalKeys); 801 | 802 | if (this.options.splitType) { 803 | code += this.genObjectCodeWithVar(objType, key, type); 804 | } else { 805 | code += objType; 806 | } 807 | 808 | this.level--; 809 | return code; 810 | } 811 | }, { 812 | key: "genObjectCodeWithVar", 813 | value: function genObjectCodeWithVar(objType, key, type) { 814 | var val = stringifyObjAndSort(type); 815 | 816 | if (this.objValueMap.has(val)) { 817 | return this.objValueMap.get(val); 818 | } 819 | 820 | var varName = this.genName(key); 821 | var isInterface = this.options.genType === "interface"; 822 | this.objValueMap.set(val, varName); 823 | this.vars += "".concat(this.options.genType, " ").concat(varName).concat(isInterface ? "" : " =", " ").concat(objType).concat(this.options.semicolon ? ";" : "", "\n\n"); 824 | return varName; 825 | } 826 | }, { 827 | key: "genArray", 828 | value: function genArray(key, types) { 829 | var _this2 = this; 830 | 831 | if (types.length === 0) { 832 | return "Array< unknown >"; 833 | } 834 | 835 | var code = "Array< "; 836 | var arrTypes = /* @__PURE__ */new Set(); 837 | var optionalKeys = []; 838 | 839 | if (this.options.optimizeArrayOptional) { 840 | var _optimizeArray = optimizeArray(types), 841 | keys = _optimizeArray.optionalKeys, 842 | newTypes = _optimizeArray.newTypes; 843 | 844 | optionalKeys = keys; 845 | types = newTypes; 846 | } 847 | 848 | types.forEach(function (type) { 849 | if (isArray(type)) { 850 | arrTypes.add(_this2.genArray(key, type)); 851 | } 852 | 853 | if (isObject(type)) { 854 | arrTypes.add(_this2.genObjcet(key, type, optionalKeys)); 855 | } else { 856 | arrTypes.add(type); 857 | } 858 | }); 859 | code += Array.from(arrTypes).join(" | "); 860 | code += " >"; 861 | return code; 862 | } 863 | }]); 864 | 865 | return Generate; 866 | }(); 867 | 868 | function generate(ast, options) { 869 | return new Generate(ast, options).beginGen(); 870 | } 871 | 872 | function initOptions(options) { 873 | var defaultOptions = { 874 | splitType: true, 875 | parseArray: false, 876 | required: true, 877 | semicolon: false, 878 | typeSuffix: "Type", 879 | typePrefix: "", 880 | indent: 2, 881 | comment: false, 882 | optimizeArrayOptional: false, 883 | genType: "type" 884 | }; 885 | Object.assign(defaultOptions, options); 886 | return defaultOptions; 887 | } 888 | 889 | function json2ts(code) { 890 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 891 | var finalOptions = initOptions(options); 892 | var ast = parse(code, finalOptions); 893 | transform(ast, finalOptions); 894 | return generate(ast, finalOptions); 895 | } 896 | }); 897 | -------------------------------------------------------------------------------- /dist/index.umd.min.js: -------------------------------------------------------------------------------- 1 | (function(global,factory){if(typeof define==="function"&&define.amd){define(["exports"],factory)}else if(typeof exports!=="undefined"){factory(exports)}else{var mod={exports:{}};factory(mod.exports);global.json2ts=mod.exports}})(typeof globalThis!=="undefined"?globalThis:typeof self!=="undefined"?self:this,function(_exports){"use strict";Object.defineProperty(_exports,"__esModule",{value:true});_exports.VALUE_ILLEGAL_ERROR_MESSAGE=_exports.UNDEFINED_TYPE=_exports.STRING_TYPE=_exports.ROOT_TYPE=_exports.ROOT_KEY=_exports.OBJECT_TYPE=_exports.NUMBER_TYPE=_exports.NULL_TYPE=_exports.NEXT_COMMENT=_exports.LAST_COMMENT=_exports.COMMENT_TYPE=_exports.COMMENT_ERROR_MESSAGE=_exports.BOOLEAN_TYPE=_exports.ARRAY_TYPE=_exports.ARRAY_ITEM=_exports.ARRAY_ERROR_MESSAGE=void 0;_exports.json2ts=_exports["default"]=json2ts;_exports.parse=parse;_exports.traverser=traverser;function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError("Cannot call a class as a function")}}function _defineProperties(target,props){for(var i=0;i=o.length)return{done:true};return{done:false,value:o[i++]}},e:function e(_e){throw _e},f:F}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var normalCompletion=true,didErr=false,err;return{s:function s(){it=it.call(o)},n:function n(){var step=it.next();normalCompletion=step.done;return step},e:function e(_e2){didErr=true;err=_e2},f:function f(){try{if(!normalCompletion&&it["return"]!=null)it["return"]()}finally{if(didErr)throw err}}}}function _unsupportedIterableToArray(o,minLen){if(!o)return;if(typeof o==="string")return _arrayLikeToArray(o,minLen);var n=Object.prototype.toString.call(o).slice(8,-1);if(n==="Object"&&o.constructor)n=o.constructor.name;if(n==="Map"||n==="Set")return Array.from(o);if(n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return _arrayLikeToArray(o,minLen)}function _arrayLikeToArray(arr,len){if(len==null||len>arr.length)len=arr.length;for(var i=0,arr2=new Array(len);i2&&arguments[2]!==undefined?arguments[2]:source.length;var lineCount=0;var lastNewLinePos=-1;for(var i=0;i1&&arguments[1]!==undefined?arguments[1]:"";var optionalKeys=[];var keyCountMap={};var objCount=0;var newTypes=[];var typeObj={};var _loop=function _loop(i){var type=arrTypes[i];if(isObject(type)){objCount++;Object.keys(type).forEach(function(key){typeObj[key]=type[key];if(keyCountMap["".concat(root,">").concat(key)]){keyCountMap["".concat(root,">").concat(key)]+=1}else{keyCountMap["".concat(root,">").concat(key)]=1}})}else{newTypes.push(type)}};for(var i=0;i2&&arguments[2]!==undefined?arguments[2]:"";var newKey=parent+">"+key;var _iterator2=_createForOfIteratorHelper(optionalKeys),_step2;try{for(_iterator2.s();!(_step2=_iterator2.n()).done;){var optionalKey=_step2.value;if(newKey===optionalKey){return true}}}catch(err){_iterator2.e(err)}finally{_iterator2.f()}return false}var cache=resetCache();function resetCache(){return{comments:{},i:0,lastI:null,lastNode:null,nextComment:[]}}function normalEntryHandle(node,parent,options){node.i=cache.i;if(node.key===ARRAY_ITEM){cache.nextComment=[];if(options.parseArray){parent.typeValue=parent.typeValue||[];parent.typeValue.push(node.type)}}else{parent.typeValue=parent.typeValue||{};parent.typeValue[node.key]=node.type;handleNormalNodeComment(node)}}function handleNormalNodeComment(node){cache.lastNode=node;if(cache.nextComment.length){var comments=cache.comments;var key=node.key+cache.i;comments[key]=comments[key]||[];comments[key]=comments[key].concat(cache.nextComment);cache.nextComment=[]}}function cacheObjectComment(node){if(cache.nextComment.length){node.nextComment=cache.nextComment;cache.nextComment=[]}}function handleObjectNodeComment(node){if(node.nextComment){var comments=cache.comments;var key=node.key+node.i;comments[key]=comments[key]||[];comments[key]=comments[key].concat(node.nextComment);cache.nextComment=[]}}function traverseNode(nodes,parent,visiter){nodes.forEach(function(node){var visit=visiter[node.type];if(visit){visit.entry&&visit.entry(node,parent)}if(isArray(node.value)){traverseNode(node.value,node,visiter)}if(visit){visit.exit&&visit.exit(node,parent)}})}function traverser(ast,visiter){var root=visiter.Root;if(root){root.entry&&root.entry(ast,null)}traverseNode(ast.value,ast,visiter);if(root){root.exit&&root.exit(ast,null)}return ast}function transform(ast,options){var _traverser;traverser(ast,(_traverser={},_defineProperty(_traverser,STRING_TYPE,{entry:function entry(node,parent){normalEntryHandle(node,parent,options)}}),_defineProperty(_traverser,NUMBER_TYPE,{entry:function entry(node,parent){normalEntryHandle(node,parent,options)}}),_defineProperty(_traverser,OBJECT_TYPE,{entry:function entry(node,parent){if(node.key===ARRAY_ITEM){cache.nextComment=[];if(options.parseArray){parent.typeValue=parent.typeValue||[];node.typeValue={};parent.typeValue.push(node.typeValue);node.i=cache.i;cache.i++}}else{parent.typeValue=parent.typeValue||{};parent.typeValue[node.key]=node.typeValue={};if(options.comment==="inline"){cacheObjectComment(node)}else if(options.comment==="block"){handleNormalNodeComment(node)}node.i=cache.i;cache.i++}},exit:function exit(node){if(options.comment==="inline"){node.i=cache.i;handleObjectNodeComment(node)}cache.lastNode=node}}),_defineProperty(_traverser,ARRAY_TYPE,{entry:function entry(node,parent){if(node.key===ARRAY_ITEM){cache.nextComment=[];if(options.parseArray){parent.typeValue=parent.typeValue||[];node.typeValue=[];parent.typeValue.push(node.typeValue)}}else{parent.typeValue=parent.typeValue||{};parent.typeValue[node.key]=node.typeValue=[];if(options.comment==="inline"){cacheObjectComment(node)}else if(options.comment==="block"){handleNormalNodeComment(node)}}node.i=cache.i},exit:function exit(node){if(options.comment==="inline"){node.i=cache.i;handleObjectNodeComment(node)}cache.lastNode=node}}),_defineProperty(_traverser,NULL_TYPE,{entry:function entry(node,parent){normalEntryHandle(node,parent,options)}}),_defineProperty(_traverser,BOOLEAN_TYPE,{entry:function entry(node,parent){normalEntryHandle(node,parent,options)}}),_defineProperty(_traverser,UNDEFINED_TYPE,{entry:function entry(node,parent){normalEntryHandle(node,parent,options)}}),_defineProperty(_traverser,COMMENT_TYPE,{entry:function entry(node){if(node.key===LAST_COMMENT){var key=cache.lastNode.key+cache.lastNode.i;cache.comments[key]=cache.comments[key]||[];cache.comments[key].push(node.value)}else{cache.nextComment.push(node.value)}}}),_traverser));ast.comments=cache.comments;cache=resetCache();return ast}var Generate=/*#__PURE__*/function(){function Generate(ast,options){_classCallCheck(this,Generate);this.ast=ast;this.options=options;this.prefix=options.typePrefix;this.suffix=options.typeSuffix;this.vars="";this.i=0;this.j=-1;this.level=1;this.objValueMap=/* @__PURE__ */new Map}_createClass(Generate,[{key:"beginGen",value:function beginGen(){var originName=this.genName("Result");var typeValue=this.ast.typeValue;var code=this.gen(typeValue);var isInterface=this.options.genType==="interface";return"".concat(this.vars).concat(this.options.genType," ").concat(originName).concat(isInterface?"":" ="," ").concat(code).concat(this.options.semicolon?";":"","\n")}},{key:"gen",value:function gen(typeValue){var optionalKeys=arguments.length>1&&arguments[1]!==undefined?arguments[1]:[];var code="{\n";for(var key in typeValue){var type=typeValue[key];if(this.options.comment==="block"){code+=this.genBlockComment(key)}code+=this.genKey(key,isOptional(optionalKeys,key));if(isObject(type)){code+=this.genObjcet(key,type)}else if(isArray(type)){code+=this.options.parseArray?this.genArray(key,type):"Array"}else{code+=type}if(this.options.semicolon){code+=";"}if(this.options.comment==="inline"){code+=this.genInlineComment(key)}code+="\n"}if(!this.options.splitType){code+=this.genFormatChat(this.level-1)}code+="}";return code}},{key:"genName",value:function genName(key){this.j++;return"".concat(this.prefix).concat(upperCaseFirstChat(key),"$").concat(this.j).concat(this.suffix)}},{key:"genKey",value:function genKey(key){var optional=arguments.length>1&&arguments[1]!==undefined?arguments[1]:false;return"".concat(this.genFormatChat(this.level)).concat(key).concat(this.options.required&&!optional?": ":"?: ")}},{key:"genFormatChat",value:function genFormatChat(level){var indent=this.options.indent;if(this.options.splitType){return" ".repeat(indent)}return" ".repeat(level*indent)}},{key:"genInlineComment",value:function genInlineComment(key){var name=key+this.i;var comments=this.ast.comments[name];if(comments&&comments.length){var code=" // ";comments.forEach(function(comment){code+=comment+"; "});code=code.substring(0,code.length-2);return code}return""}},{key:"genBlockComment",value:function genBlockComment(key){var _this=this;var code="";var name=key+this.i;var comments=this.ast.comments[name];if(comments&&comments.length){comments.forEach(function(comment){code+=_this.genFormatChat(_this.level)+"// "+comment+"\n"})}return code}},{key:"genObjcet",value:function genObjcet(key,type){var optionalKeys=arguments.length>2&&arguments[2]!==undefined?arguments[2]:[];var code="";this.level++;this.i++;var objType=this.gen(type,optionalKeys);if(this.options.splitType){code+=this.genObjectCodeWithVar(objType,key,type)}else{code+=objType}this.level--;return code}},{key:"genObjectCodeWithVar",value:function genObjectCodeWithVar(objType,key,type){var val=stringifyObjAndSort(type);if(this.objValueMap.has(val)){return this.objValueMap.get(val)}var varName=this.genName(key);var isInterface=this.options.genType==="interface";this.objValueMap.set(val,varName);this.vars+="".concat(this.options.genType," ").concat(varName).concat(isInterface?"":" ="," ").concat(objType).concat(this.options.semicolon?";":"","\n\n");return varName}},{key:"genArray",value:function genArray(key,types){var _this2=this;if(types.length===0){return"Array< unknown >"}var code="Array< ";var arrTypes=/* @__PURE__ */new Set;var optionalKeys=[];if(this.options.optimizeArrayOptional){var _optimizeArray=optimizeArray(types),keys=_optimizeArray.optionalKeys,newTypes=_optimizeArray.newTypes;optionalKeys=keys;types=newTypes}types.forEach(function(type){if(isArray(type)){arrTypes.add(_this2.genArray(key,type))}if(isObject(type)){arrTypes.add(_this2.genObjcet(key,type,optionalKeys))}else{arrTypes.add(type)}});code+=Array.from(arrTypes).join(" | ");code+=" >";return code}}]);return Generate}();function generate(ast,options){return new Generate(ast,options).beginGen()}function initOptions(options){var defaultOptions={splitType:true,parseArray:false,required:true,semicolon:false,typeSuffix:"Type",typePrefix:"",indent:2,comment:false,optimizeArrayOptional:false,genType:"type"};Object.assign(defaultOptions,options);return defaultOptions}function json2ts(code){var options=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};var finalOptions=initOptions(options);var ast=parse(code,finalOptions);transform(ast,finalOptions);return generate(ast,finalOptions)}}); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cyly/json2ts", 3 | "version": "0.2.8", 4 | "description": "transform json to ts", 5 | "main": "./dist/index.cjs", 6 | "module": "./dist/index.mjs", 7 | "types": "types/index.d.ts", 8 | "keywords": [ 9 | "frontend", 10 | "json2ts", 11 | "compile", 12 | "tool", 13 | "typescript" 14 | ], 15 | "scripts": { 16 | "build": "rollup -c", 17 | "test": "vitest", 18 | "test:ui": "vitest --ui", 19 | "test:run": "vitest run", 20 | "coverage": "vitest run --coverage", 21 | "release": "bumpp --commit --push --tag && pnpm publish" 22 | }, 23 | "files": [ 24 | "dist/", 25 | "types/" 26 | ], 27 | "author": "cyly cp786156072@163.com", 28 | "homepage": "https://patrickchen928.github.io/json2ts/index.html", 29 | "bugs": { 30 | "url": "https://github.com/PatrickChen928/json2ts/issues" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/PatrickChen928/json2ts.git" 35 | }, 36 | "license": "MIT", 37 | "exports": { 38 | ".": { 39 | "require": "./dist/index.cjs", 40 | "import": "./dist/index.mjs", 41 | "types": "./types/index.d.ts" 42 | } 43 | }, 44 | "devDependencies": { 45 | "@babel/core": "^7.17.8", 46 | "@babel/preset-env": "^7.16.11", 47 | "@rollup/plugin-babel": "^5.3.1", 48 | "@rollup/plugin-commonjs": "^21.0.2", 49 | "@vitest/ui": "^0.7.4", 50 | "bumpp": "^7.1.1", 51 | "c8": "^7.11.0", 52 | "esbuild": "^0.14.27", 53 | "rollup": "^2.70.1", 54 | "rollup-plugin-babel": "^4.4.0", 55 | "rollup-plugin-clear": "^2.0.7", 56 | "rollup-plugin-dts": "^4.2.0", 57 | "rollup-plugin-esbuild": "^4.8.2", 58 | "typescript": "^4.1", 59 | "vite": "^2.8.6", 60 | "vitest": "^0.7.4" 61 | }, 62 | "dependencies": { 63 | "@antfu/ni": "^0.21.6" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import clear from 'rollup-plugin-clear'; 2 | import esbuild from 'rollup-plugin-esbuild'; 3 | import { getBabelOutputPlugin } from '@rollup/plugin-babel'; 4 | import dts from 'rollup-plugin-dts'; 5 | 6 | const useBabelPlugin = function(options = {}, minified) { 7 | return getBabelOutputPlugin({ presets: [['@babel/preset-env', options]], filename: 'json2ts', minified }); 8 | } 9 | 10 | const esbuildPlugin = esbuild({ 11 | sourceMap: true, 12 | target: 'es2015' 13 | }); 14 | 15 | const esbuildMinifer = (options) => { 16 | const { renderChunk } = esbuild(options) 17 | 18 | return { 19 | name: 'esbuild-minifer', 20 | renderChunk, 21 | } 22 | } 23 | 24 | module.exports = [ 25 | { 26 | input: 'src/index.ts', 27 | output: [ 28 | { 29 | file: 'dist/index.mjs', 30 | format: 'es', 31 | plugins: [ useBabelPlugin() ] 32 | }, 33 | { 34 | file: 'dist/index.cjs', 35 | format: 'cjs', 36 | plugins: [ useBabelPlugin() ] 37 | }, 38 | { 39 | file: `dist/index.umd.js`, 40 | name: 'json2ts', 41 | plugins: [ useBabelPlugin({ modules: 'umd' }), ] 42 | }, 43 | { 44 | file: `dist/index.umd.min.js`, 45 | name: 'json2ts', 46 | plugins: [ 47 | useBabelPlugin({ modules: 'umd' }, true), 48 | ], 49 | }, 50 | ], 51 | plugins: [ 52 | clear({ 53 | targets: ['./dist'] 54 | }), 55 | esbuildPlugin 56 | ] 57 | }, 58 | { 59 | input: "./src/index.ts", 60 | output: [{ file: "types/index.d.ts", format: "es" }], 61 | plugins: [dts()], 62 | }, 63 | { 64 | input: "./src/parse.ts", 65 | output: [{ file: "types/parse.d.ts", format: "es" }], 66 | plugins: [dts()], 67 | }, 68 | { 69 | input: "./src/transform.ts", 70 | output: [{ file: "types/transform.d.ts", format: "es" }], 71 | plugins: [dts()], 72 | } 73 | ] -------------------------------------------------------------------------------- /src/codegen.ts: -------------------------------------------------------------------------------- 1 | import type { TransformNodeType, CompileOptions } from './types'; 2 | import { 3 | isArray, 4 | isObject, 5 | upperCaseFirstChat, 6 | stringifyObjAndSort, 7 | optimizeArray, 8 | isOptional 9 | } from './utils'; 10 | 11 | class Generate { 12 | private ast: TransformNodeType 13 | private options: CompileOptions; 14 | private vars: string; 15 | private i: number; 16 | private j: number; 17 | private level: number; 18 | private prefix: string; 19 | private suffix: string; 20 | private objValueMap: Map; 21 | 22 | constructor(ast: TransformNodeType, options: CompileOptions) { 23 | this.ast = ast; 24 | this.options = options; 25 | this.prefix = options.typePrefix; 26 | this.suffix = options.typeSuffix; 27 | this.vars = ''; 28 | this.i = 0; 29 | this.j = -1; 30 | this.level = 1; 31 | this.objValueMap = new Map(); 32 | } 33 | 34 | beginGen() { 35 | const originName = this.genName('Result'); 36 | const typeValue = this.ast.typeValue!; 37 | const code = this.gen(typeValue); 38 | const isInterface = this.options.genType === 'interface'; 39 | return `${this.vars}${this.options.genType} ${originName}${isInterface ? '' : ' ='} ${code}${this.options.semicolon ? ';' : ''}\n`; 40 | } 41 | 42 | gen(typeValue: Record | Array, optionalKeys: string[] = []) { 43 | let code = `{\n`; 44 | for (const key in typeValue) { 45 | const type = typeValue[key]; 46 | if (this.options.comment === 'block') { 47 | code += this.genBlockComment(key); 48 | } 49 | code += this.genKey(key, isOptional(optionalKeys, key)); 50 | if (isObject(type)) { 51 | code += this.genObjcet(key, type); 52 | } else if (isArray(type)) { 53 | code += this.options.parseArray ? this.genArray(key, type) : 'Array'; 54 | } else { 55 | code += type; 56 | } 57 | if (this.options.semicolon) { 58 | code += ';'; 59 | } 60 | if (this.options.comment === 'inline') { 61 | code += this.genInlineComment(key); 62 | } 63 | code += '\n'; 64 | } 65 | if (!this.options.splitType) { 66 | code += this.genFormatChat(this.level - 1); 67 | } 68 | code += '}' 69 | return code; 70 | } 71 | 72 | genName(key: string) { 73 | this.j++; 74 | return `${this.prefix}${upperCaseFirstChat(key)}$${this.j}${this.suffix}` 75 | } 76 | 77 | genKey(key: string, optional = false) { 78 | return `${this.genFormatChat(this.level)}${key}${this.options.required && !optional ? ': ' : '?: '}`; 79 | } 80 | 81 | genFormatChat(level: number) { 82 | const indent = this.options.indent; 83 | if (this.options.splitType) { 84 | return ' '.repeat(indent); 85 | } 86 | return ' '.repeat(level * indent); 87 | } 88 | 89 | genInlineComment(key: string) { 90 | const name = key + this.i; 91 | const comments = this.ast.comments[name]; 92 | if (comments && comments.length) { 93 | let code = ' // '; 94 | comments.forEach(comment => { 95 | code += comment + '; '; 96 | }) 97 | code = code.substring(0, code.length - 2) 98 | return code; 99 | } 100 | return ''; 101 | } 102 | 103 | genBlockComment(key: string) { 104 | let code = ''; 105 | const name = key + this.i; 106 | const comments = this.ast.comments[name]; 107 | if (comments && comments.length) { 108 | comments.forEach(comment => { 109 | code += this.genFormatChat(this.level) + '// ' + comment + '\n'; 110 | }) 111 | } 112 | return code; 113 | } 114 | 115 | genObjcet(key: string, type: Record, optionalKeys: string[] = []) { 116 | let code = ''; 117 | this.level++; 118 | this.i++; 119 | const objType = this.gen(type, optionalKeys); 120 | if (this.options.splitType) { 121 | code += this.genObjectCodeWithVar(objType, key, type as Record); 122 | } else { 123 | code += objType; 124 | } 125 | this.level--; 126 | return code; 127 | } 128 | 129 | genObjectCodeWithVar(objType: string, key: string, type: Record) { 130 | const val = stringifyObjAndSort(type); 131 | if (this.objValueMap.has(val)) { 132 | return this.objValueMap.get(val); 133 | } 134 | const varName = this.genName(key); 135 | const isInterface = this.options.genType === 'interface'; 136 | this.objValueMap.set(val, varName); 137 | this.vars += `${this.options.genType} ${varName}${isInterface ? '' : ' ='} ${objType}${this.options.semicolon ? ';' : ''}\n\n`; 138 | return varName; 139 | } 140 | 141 | genArray(key: string, types: Array) { 142 | if (types.length === 0) { 143 | return 'Array< unknown >'; 144 | } 145 | let code = `Array< `; 146 | // 使用 set 过滤重复类型 147 | const arrTypes = new Set(); 148 | let optionalKeys = []; 149 | if (this.options.optimizeArrayOptional) { 150 | const { optionalKeys: keys, newTypes } = optimizeArray(types); 151 | optionalKeys = keys; 152 | types = newTypes; 153 | } 154 | types.forEach(type => { 155 | if (isArray(type)) { 156 | arrTypes.add(this.genArray(key, type)); 157 | } if (isObject(type)) { 158 | arrTypes.add(this.genObjcet(key, type, optionalKeys)); 159 | } else { 160 | arrTypes.add(type); 161 | } 162 | }); 163 | code += Array.from(arrTypes).join(' | '); 164 | code += ' >'; 165 | return code; 166 | } 167 | } 168 | 169 | export function generate(ast: TransformNodeType, options: CompileOptions) { 170 | return new Generate(ast, options).beginGen(); 171 | } -------------------------------------------------------------------------------- /src/contant.ts: -------------------------------------------------------------------------------- 1 | export const ROOT_TYPE = "Root"; 2 | export const STRING_TYPE = "string"; 3 | export const NUMBER_TYPE = "number"; 4 | export const NULL_TYPE = "null"; 5 | export const BOOLEAN_TYPE = "boolean"; 6 | export const UNDEFINED_TYPE = "undefined"; 7 | export const OBJECT_TYPE = "Object"; 8 | export const ARRAY_TYPE = "Array"; 9 | export const COMMENT_TYPE = "Comment"; 10 | 11 | export const ROOT_KEY = "root"; 12 | export const ARRAY_ITEM = "$ARRAY_ITEM$"; 13 | export const LAST_COMMENT = "$LAST_COMMENT$"; 14 | export const NEXT_COMMENT = "$NEXT_COMMENT$"; 15 | 16 | export const ARRAY_ERROR_MESSAGE = "array should be closed"; 17 | export const COMMENT_ERROR_MESSAGE = "comment is illegal"; 18 | export const VALUE_ILLEGAL_ERROR_MESSAGE = "value is illegal"; -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { parse } from './parse'; 2 | import { transform, traverser } from './transform'; 3 | import { generate } from './codegen'; 4 | 5 | import type { CompileOptions } from './types'; 6 | 7 | 8 | function initOptions(options: CompileOptions): CompileOptions { 9 | const defaultOptions: CompileOptions = { 10 | splitType: true, 11 | parseArray: false, 12 | required: true, 13 | semicolon: false, 14 | typeSuffix: 'Type', 15 | typePrefix: '', 16 | indent: 2, 17 | comment: false, 18 | optimizeArrayOptional: false, 19 | genType: 'type', 20 | }; 21 | Object.assign(defaultOptions, options) 22 | return defaultOptions; 23 | } 24 | 25 | export { 26 | traverser, 27 | parse, 28 | json2ts 29 | }; 30 | 31 | export * from './contant'; 32 | 33 | export default function json2ts(code: string, options: CompileOptions = {}) { 34 | const finalOptions = initOptions(options); 35 | const ast = parse(code, finalOptions); 36 | // const 37 | transform(ast, finalOptions); 38 | return generate(ast, finalOptions); 39 | } -------------------------------------------------------------------------------- /src/parse.ts: -------------------------------------------------------------------------------- 1 | import type { ParserContext, Position, LocType, AstChildNode, CompileOptions } from './types'; 2 | import { 3 | ROOT_TYPE, 4 | ARRAY_TYPE, 5 | STRING_TYPE, 6 | NUMBER_TYPE, 7 | NULL_TYPE, 8 | UNDEFINED_TYPE, 9 | BOOLEAN_TYPE, 10 | OBJECT_TYPE , 11 | ROOT_KEY, 12 | ARRAY_ITEM, 13 | COMMENT_TYPE, 14 | LAST_COMMENT, 15 | NEXT_COMMENT, 16 | ARRAY_ERROR_MESSAGE, 17 | COMMENT_ERROR_MESSAGE, 18 | VALUE_ILLEGAL_ERROR_MESSAGE 19 | } from './contant'; 20 | 21 | const NUMBER_REGX = /^[-+]?([0-9]+\.?\d*)([Ee][-+]?[0-9]+)?/; 22 | 23 | function getCursor(context: ParserContext) { 24 | const { offset, column, line } = context; 25 | return { offset, column, line }; 26 | } 27 | 28 | function getLoc(context: ParserContext, start: Position, end?: Position): LocType { 29 | end = end || getCursor(context); 30 | return { 31 | start, 32 | end, 33 | source: context.originalSource.slice(start.offset, end.offset) 34 | } 35 | } 36 | 37 | function createContext(content: string, options?: Record): ParserContext { 38 | return { 39 | options, 40 | column: 1, 41 | line: 1, 42 | offset: 0, 43 | originalSource: content, 44 | source: content 45 | } 46 | } 47 | 48 | function createRoot(nodes: AstChildNode[]): AstChildNode { 49 | return { 50 | key: ROOT_KEY, 51 | type: ROOT_TYPE, 52 | value: nodes 53 | } 54 | } 55 | 56 | function isEnd(context: ParserContext) { 57 | return !context.source; 58 | } 59 | 60 | 61 | function advancePositionWithMutation( 62 | pos: Position, 63 | source: string, 64 | numberOfCharacters: number = source.length 65 | ) { 66 | let lineCount = 0; 67 | let lastNewLinePos = -1; 68 | for (let i = 0; i < numberOfCharacters; i++) { 69 | if (source.charCodeAt(i) === 10 /* newline char code */) { 70 | lineCount++; 71 | lastNewLinePos = i; 72 | } 73 | } 74 | pos.offset += numberOfCharacters; 75 | pos.line += lineCount; 76 | pos.column = 77 | lastNewLinePos === -1 78 | ? pos.column + numberOfCharacters 79 | : numberOfCharacters - lastNewLinePos; 80 | } 81 | 82 | function advanceBy(context: ParserContext, numberOfCharacters: number) { 83 | const source = context.source; 84 | advancePositionWithMutation(context, source, numberOfCharacters); 85 | context.source = source.slice(numberOfCharacters); 86 | } 87 | 88 | function advanceSpaces(context: ParserContext) { 89 | const match = /^[\r\n\f ]+/.exec(context.source); 90 | if (match && match[0]) { 91 | advanceBy(context, match[0].length); 92 | } 93 | } 94 | 95 | function parseData(context: ParserContext, keyName?: string) { 96 | advanceSpaces(context); 97 | const start = getCursor(context); 98 | let key = keyName || parseKey(context); 99 | const { value, type } = parseValue(context); 100 | const loc = getLoc(context, start); 101 | advanceSpaces(context); 102 | if (context.source[0] === ',') { 103 | advanceBy(context, 1); 104 | advanceSpaces(context); 105 | } 106 | return {key, value, type, loc: loc}; 107 | } 108 | /** 109 | * 解析 {} 里面的内容 110 | * @param context 111 | * @returns 112 | */ 113 | function parseChildren(context: ParserContext) { 114 | const nodes: AstChildNode[] = []; 115 | while(!isEnd(context)) { 116 | advanceSpaces(context); 117 | const s = context.source; 118 | // 新的一行 119 | if (s[0] === '{') { 120 | advanceBy(context, 1); 121 | } else if (s[0] === '}') { 122 | advanceBy(context, 1); 123 | advanceSpaces(context); 124 | return nodes; 125 | } else if (s[0] === '/') { 126 | if (s[1] === '/') { 127 | const lastNode = nodes[nodes.length - 1]; 128 | let lastLine = -1; 129 | if (lastNode) { 130 | lastLine = lastNode.loc.end.line; 131 | } 132 | const comment = parseComment(context, lastLine); 133 | if (comment) { 134 | nodes.push(comment); 135 | } 136 | advanceSpaces(context); 137 | } else { 138 | throw new Error(COMMENT_ERROR_MESSAGE) 139 | } 140 | } else { 141 | nodes.push(parseData(context)); 142 | } 143 | } 144 | return nodes; 145 | } 146 | 147 | function parseKey(context: ParserContext) { 148 | const s = context.source[0]; 149 | let match = []; 150 | // ([^:]*) 151 | // "xxx" 类型的key 152 | if (s === '"') { 153 | match = /^"(.[^"]*)/i.exec(context.source); 154 | } else if (s === `'`) { 155 | // 'xxx' 类型的key 156 | match = /^'(.[^']*)/i.exec(context.source); 157 | } else { 158 | // xxx: 类型的key 159 | match = /(.[^:]*)/i.exec(context.source); 160 | match[1] = match[1].trim(); 161 | } 162 | // 去掉末尾的" 或 ' 或 : 163 | advanceBy(context, match[0].length + 1); 164 | advanceSpaces(context); 165 | // 去掉 " 和 ' 后面的冒号 166 | if (context.source[0] === ':') { 167 | advanceBy(context, 1); 168 | advanceSpaces(context); 169 | } 170 | return match[1]; 171 | } 172 | 173 | function parseNumber(context: ParserContext) { 174 | const match = NUMBER_REGX.exec(context.source); 175 | advanceBy(context, match[0].length); 176 | return match[1]; 177 | } 178 | 179 | function parseString(context: ParserContext) { 180 | const s = context.source[0]; 181 | //TODO: 处理转义字符 \" \' 182 | const match = new RegExp(`^${s}((?:\\.|[^\\${s}])*)${s}`).exec(context.source); 183 | advanceBy(context, match[0].length); 184 | return match[1]; 185 | } 186 | 187 | function parseNull(context: ParserContext) { 188 | advanceBy(context, 4); 189 | return 'null'; 190 | } 191 | 192 | function parseBoolean(context: ParserContext) { 193 | const match = /^(true|false)/i.exec(context.source); 194 | advanceBy(context, match[0].length); 195 | return match[1]; 196 | } 197 | 198 | function parseUndefined(context: ParserContext) { 199 | advanceBy(context, 9); 200 | return 'undefined'; 201 | } 202 | 203 | function parseValue(context: ParserContext) { 204 | let value = null; 205 | let type = null; 206 | let code = context.source[0]; 207 | if (NUMBER_REGX.test(context.source)) { 208 | value = parseNumber(context); 209 | type = NUMBER_TYPE; 210 | } else if (code === '"' || code === '\'') { 211 | value = parseString(context); 212 | type = STRING_TYPE; 213 | } else if (code === '[') { 214 | advanceBy(context, 1); 215 | value = parseArray(context); 216 | type = ARRAY_TYPE; 217 | } else if (code === '{') { 218 | value = parseChildren(context); 219 | type = OBJECT_TYPE; 220 | } else if (context.source.indexOf('null') === 0) { 221 | value = parseNull(context); 222 | type = NULL_TYPE; 223 | } else if (context.source.indexOf('true') === 0 || context.source.indexOf('false') === 0) { 224 | value = parseBoolean(context); 225 | type = BOOLEAN_TYPE; 226 | } else if (context.source.indexOf('undefined') === 0) { 227 | value = parseUndefined(context); 228 | type = UNDEFINED_TYPE; 229 | } else { 230 | throw new Error(VALUE_ILLEGAL_ERROR_MESSAGE) 231 | } 232 | return { 233 | value, 234 | type 235 | } 236 | } 237 | 238 | function parseArray(context: ParserContext) { 239 | const nodes = []; 240 | let lastLine = getCursor(context).line; 241 | advanceSpaces(context); 242 | while(!isEnd(context)) { 243 | const s = context.source[0]; 244 | if (s === ']') { 245 | advanceBy(context, 1); 246 | return nodes; 247 | } 248 | if (s === '}' || s === ':') { 249 | throw new Error(ARRAY_ERROR_MESSAGE); 250 | } 251 | // array item 的注释 252 | if (context.source.indexOf('//') === 0) { 253 | const cv = parseComment(context, lastLine); 254 | if (cv) { 255 | nodes.push(cv); 256 | } 257 | // 这里不需要重置lastLine, 因为如果下一个还是注释,肯定是next_comment, 258 | advanceSpaces(context); 259 | } else { 260 | const itemValue = parseData(context, ARRAY_ITEM); 261 | // 缓存上一个node的注释 262 | lastLine = itemValue.loc.end.line; 263 | nodes.push(itemValue); 264 | } 265 | } 266 | throw new Error(ARRAY_ERROR_MESSAGE); 267 | } 268 | 269 | function parseComment(context: ParserContext, lastLine: number) { 270 | const currLine = getCursor(context).line; 271 | const key = lastLine === currLine ? LAST_COMMENT : NEXT_COMMENT; 272 | const match = /^\/\/(.*)?(?=\n)/i.exec(context.source); 273 | const start = getCursor(context); 274 | const comment = match[1]; 275 | advanceBy(context, match[0].length); 276 | if (!comment || !comment.trim()) { 277 | return null; 278 | } 279 | return {key, value: comment.trim(), type: COMMENT_TYPE, loc: getLoc(context, start)}; 280 | } 281 | 282 | /** 283 | * parse to ast 284 | * @param input 285 | * @param options 286 | * @returns ast 287 | */ 288 | export function parse(input: string, options?: CompileOptions): AstChildNode { 289 | const context = createContext(input, options); 290 | return createRoot(parseChildren(context)); 291 | } -------------------------------------------------------------------------------- /src/transform.ts: -------------------------------------------------------------------------------- 1 | import type { TransformNodeType, Visiter, CompileOptions } from './types'; 2 | import { 3 | ARRAY_ITEM, 4 | ARRAY_TYPE, 5 | STRING_TYPE, 6 | NUMBER_TYPE, 7 | OBJECT_TYPE, 8 | NULL_TYPE, 9 | BOOLEAN_TYPE, 10 | UNDEFINED_TYPE, 11 | COMMENT_TYPE, 12 | LAST_COMMENT 13 | } from './contant'; 14 | import { isArray } from './utils'; 15 | 16 | 17 | let cache = resetCache(); 18 | 19 | function resetCache() { 20 | return { 21 | comments: {}, 22 | // 对象的个数 23 | i: 0, 24 | lastI: null, 25 | lastNode: null, 26 | nextComment: [] 27 | } 28 | } 29 | 30 | function normalEntryHandle(node: TransformNodeType, parent: TransformNodeType, options: CompileOptions) { 31 | node.i = cache.i; 32 | if (node.key === ARRAY_ITEM) { 33 | // 数组的item的注释没什么意义,清空 34 | cache.nextComment = []; 35 | if (options.parseArray) { 36 | parent.typeValue = parent.typeValue || []; 37 | (parent.typeValue as Array).push(node.type); 38 | } 39 | } else { 40 | parent.typeValue = parent.typeValue || {}; 41 | parent.typeValue[node.key] = node.type; 42 | handleNormalNodeComment(node); 43 | } 44 | } 45 | 46 | function handleNormalNodeComment(node: TransformNodeType) { 47 | cache.lastNode = node; 48 | if (cache.nextComment.length) { 49 | const comments = cache.comments; 50 | const key = node.key + cache.i; 51 | comments[key] = comments[key] || []; 52 | comments[key] = comments[key].concat(cache.nextComment); 53 | cache.nextComment = []; 54 | } 55 | } 56 | 57 | function cacheObjectComment(node: TransformNodeType) { 58 | if (cache.nextComment.length) { 59 | // 先缓存到节点上, 在 exit 中读取 60 | node.nextComment = cache.nextComment; 61 | cache.nextComment = []; 62 | } 63 | } 64 | 65 | function handleObjectNodeComment(node: TransformNodeType) { 66 | if (node.nextComment) { 67 | const comments = cache.comments; 68 | const key = node.key + node.i; 69 | comments[key] = comments[key] || []; 70 | comments[key] = comments[key].concat(node.nextComment); 71 | cache.nextComment = []; 72 | } 73 | } 74 | 75 | function traverseNode(nodes: TransformNodeType[], parent: TransformNodeType, visiter: Visiter) { 76 | nodes.forEach(node => { 77 | let visit = visiter[node.type]; 78 | if (visit) { 79 | visit.entry && visit.entry(node, parent); 80 | } 81 | if (isArray(node.value)) { 82 | traverseNode(node.value, node, visiter); 83 | } 84 | if (visit) { 85 | visit.exit && visit.exit(node, parent); 86 | } 87 | }) 88 | } 89 | 90 | export function traverser(ast: TransformNodeType, visiter: Visiter) { 91 | let root = visiter.Root; 92 | if (root) { 93 | root.entry && root.entry(ast, null); 94 | } 95 | traverseNode((ast.value as TransformNodeType[]), ast, visiter); 96 | if (root) { 97 | root.exit && root.exit(ast, null); 98 | } 99 | return ast; 100 | } 101 | 102 | export function transform(ast: TransformNodeType, options?: CompileOptions) { 103 | traverser(ast, { 104 | [STRING_TYPE]: { 105 | entry(node, parent) { 106 | normalEntryHandle(node, parent, options); 107 | } 108 | }, 109 | [NUMBER_TYPE]: { 110 | entry(node, parent) { 111 | normalEntryHandle(node, parent, options); 112 | } 113 | }, 114 | [OBJECT_TYPE]: { 115 | entry(node, parent) { 116 | if (node.key === ARRAY_ITEM) { 117 | // 数组的item的注释没什么意义,清空 118 | cache.nextComment = []; 119 | if (options.parseArray) { 120 | parent.typeValue = parent.typeValue || []; 121 | node.typeValue = {}; 122 | (parent.typeValue as Array).push(node.typeValue); 123 | node.i = cache.i; 124 | cache.i++; 125 | } 126 | } else { 127 | parent.typeValue = parent.typeValue || {}; 128 | parent.typeValue[node.key] = node.typeValue = {}; 129 | // 因为对象还得继续往内解析,所以需要在exit里面写入comments。不然 i 会对不上 130 | if (options.comment === 'inline') { 131 | cacheObjectComment(node); 132 | } else if (options.comment === 'block') { 133 | handleNormalNodeComment(node); 134 | } 135 | node.i = cache.i; 136 | cache.i++; 137 | } 138 | }, 139 | exit(node) { 140 | if (options.comment === 'inline') { 141 | node.i = cache.i; 142 | handleObjectNodeComment(node); 143 | } 144 | cache.lastNode = node; 145 | } 146 | }, 147 | [ARRAY_TYPE]: { 148 | entry(node, parent) { 149 | if (node.key === ARRAY_ITEM) { 150 | // 数组的item的注释没什么意义,清空 151 | cache.nextComment = []; 152 | if (options.parseArray) { 153 | parent.typeValue = parent.typeValue || []; 154 | node.typeValue = []; 155 | (parent.typeValue as Array).push(node.typeValue); 156 | } 157 | } else { 158 | parent.typeValue = parent.typeValue || {}; 159 | parent.typeValue[node.key] = node.typeValue = []; 160 | // 因为数组还得继续往内解析,所以需要在exit里面写入comments。不然 i 会对不上 161 | if (options.comment === 'inline') { 162 | cacheObjectComment(node); 163 | } else if (options.comment === 'block') { 164 | handleNormalNodeComment(node); 165 | } 166 | } 167 | node.i = cache.i; 168 | }, 169 | exit(node) { 170 | if (options.comment === 'inline') { 171 | node.i = cache.i; 172 | handleObjectNodeComment(node); 173 | } 174 | cache.lastNode = node; 175 | } 176 | }, 177 | [NULL_TYPE]: { 178 | entry(node, parent) { 179 | normalEntryHandle(node, parent, options); 180 | } 181 | }, 182 | [BOOLEAN_TYPE]: { 183 | entry(node, parent) { 184 | normalEntryHandle(node, parent, options); 185 | } 186 | }, 187 | [UNDEFINED_TYPE]: { 188 | entry(node, parent) { 189 | normalEntryHandle(node, parent, options); 190 | } 191 | }, 192 | [COMMENT_TYPE]: { 193 | entry(node) { 194 | if (node.key === LAST_COMMENT) { 195 | const key = cache.lastNode.key + cache.lastNode.i; 196 | cache.comments[key] = cache.comments[key] || []; 197 | cache.comments[key].push(node.value); 198 | } else { 199 | cache.nextComment.push(node.value); 200 | } 201 | } 202 | } 203 | }); 204 | ast.comments = cache.comments; 205 | cache = resetCache(); 206 | return ast; 207 | } -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type CompileOptions = { 2 | splitType?: boolean; 3 | parseArray?: boolean; 4 | required?: boolean; 5 | semicolon?: boolean; 6 | typePrefix?: string; 7 | typeSuffix?: string; 8 | indent?: number; 9 | comment?: 'inline' | 'block' | false | 'false'; 10 | optimizeArrayOptional?: boolean; 11 | genType?: 'type' | 'interface'; 12 | } 13 | 14 | export type Position = { 15 | offset: number // from start of file 16 | line: number 17 | column: number 18 | } 19 | 20 | export type ParserContext = { 21 | options: Record 22 | readonly originalSource: string 23 | source: string 24 | offset: number 25 | line: number 26 | column: number 27 | } 28 | 29 | export type LocType = { 30 | start: Position; 31 | end: Position; 32 | source: string; 33 | } 34 | 35 | export type AstChildNode = { 36 | key: string; 37 | value: string | AstChildNode[] 38 | type: string; 39 | loc?: LocType; 40 | } 41 | 42 | export type TransformNodeType = AstChildNode & { 43 | typeValue?: Record | Array; 44 | comments?: Record; 45 | i?: number; 46 | nextComment?: string[] 47 | } 48 | 49 | export type Visiter = { 50 | [key: string]: { 51 | entry?: (node: TransformNodeType, parent: TransformNodeType | null) => void; 52 | exit?: (node: TransformNodeType, parent: TransformNodeType | null) => void; 53 | } 54 | } -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | function objectToString(o: any) { 2 | return Object.prototype.toString.call(o); 3 | } 4 | 5 | export function isArray(value: any): value is Array { 6 | return typeof value === 'object' && objectToString(value).slice(8, -1) === 'Array'; 7 | } 8 | 9 | export function isObject(value: any): value is Record { 10 | return typeof value === 'object' && objectToString(value).slice(8, -1) === 'Object'; 11 | } 12 | 13 | export function upperCaseFirstChat(str: string) { 14 | return str.replace(/( |^)[a-z]/g, (L: string) => L.toUpperCase()) 15 | } 16 | 17 | 18 | export function objIsEqual(obj1: Record, obj2: Record) { 19 | for (let key in obj1) { 20 | if (isObject(obj1[key])) { 21 | if (!isObject(obj2[key])) { 22 | return false; 23 | } 24 | return objIsEqual(obj1[key], obj2[key]); 25 | } 26 | if (isArray(obj1[key])) { 27 | if (!isArray(obj2[key])) { 28 | return false; 29 | } 30 | // Todo 31 | return obj1[key].join(',') === obj2[key].join(','); 32 | } 33 | if (obj1[key] !== obj2[key]) { 34 | return false; 35 | } 36 | } 37 | return true; 38 | } 39 | 40 | export function stringifyObjAndSort(obj: Record) { 41 | let keys = Object.keys(obj).sort(); 42 | let res = `{`; 43 | for (let val of keys) { 44 | res += `${val}:${obj[val]},`; 45 | } 46 | res = res.replace(/,$/, ''); 47 | res += '}'; 48 | return res; 49 | } 50 | 51 | export function optimizeArray(arrTypes: Array, root: string = '') { 52 | const optionalKeys: string[] = []; 53 | const keyCountMap = {}; 54 | let objCount = 0; 55 | const newTypes = []; 56 | const typeObj = {}; 57 | for (let i = 0; i < arrTypes.length; i++) { 58 | const type = arrTypes[i]; 59 | if (isObject(type)) { 60 | objCount++; 61 | Object.keys(type).forEach(key => { 62 | // if (isObject(type[key])) { 63 | // optimizeArray 64 | // } 65 | typeObj[key] = type[key]; 66 | if (keyCountMap[`${root}>${key}`]) { 67 | keyCountMap[`${root}>${key}`] += 1; 68 | } else { 69 | keyCountMap[`${root}>${key}`] = 1; 70 | } 71 | }); 72 | } else { 73 | newTypes.push(type); 74 | } 75 | } 76 | Object.keys(keyCountMap).forEach(key => { 77 | if (keyCountMap[key] < objCount) { 78 | optionalKeys.push(key); 79 | } 80 | }); 81 | if (Object.keys(typeObj).length) { 82 | newTypes.push(typeObj); 83 | } 84 | return { 85 | optionalKeys, 86 | newTypes 87 | }; 88 | } 89 | 90 | export function isOptional(optionalKeys: string[], key: string, parent = '') { 91 | const newKey = parent + '>' + key; 92 | for (const optionalKey of optionalKeys) { 93 | if (newKey === optionalKey) { 94 | return true; 95 | } 96 | } 97 | return false; 98 | } -------------------------------------------------------------------------------- /tests/compile.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, expect, test, it } from 'vitest'; 2 | import json2ts, { VALUE_ILLEGAL_ERROR_MESSAGE } from '../src'; 3 | 4 | // Edit an assertion and save to see HMR in action 5 | 6 | 7 | const json0 = `{}`; 8 | const json1 = `{ 9 | "name": "aphto", 10 | "age": 18 11 | }`; 12 | 13 | describe('empty json', () => { 14 | it('expect', () => { 15 | expect(json2ts(json1)).toMatchInlineSnapshot(` 16 | "type Result\$0Type = { 17 | name: string 18 | age: number 19 | } 20 | " 21 | `) 22 | }) 23 | }); 24 | 25 | describe('semicolon end', () => { 26 | it('expect', () => { 27 | expect(json2ts(json1, { semicolon: true })).toMatchInlineSnapshot(` 28 | "type Result\$0Type = { 29 | name: string; 30 | age: number; 31 | }; 32 | " 33 | `) 34 | }) 35 | }); 36 | 37 | describe('null', () => { 38 | it('expect', () => { 39 | expect(json2ts(`{ name: null }`, { semicolon: true })).toMatchInlineSnapshot(` 40 | "type Result\$0Type = { 41 | name: null; 42 | }; 43 | " 44 | `) 45 | }) 46 | }); 47 | 48 | describe('undefined', () => { 49 | it('expect', () => { 50 | expect(json2ts(`{ name: undefined }`, { semicolon: true })).toMatchInlineSnapshot(` 51 | "type Result\$0Type = { 52 | name: undefined; 53 | }; 54 | " 55 | `) 56 | }) 57 | }); 58 | 59 | describe('boolean', () => { 60 | it('expect', () => { 61 | expect(json2ts(`{ name: true, key: false }`, { semicolon: true })).toMatchInlineSnapshot(` 62 | "type Result\$0Type = { 63 | name: boolean; 64 | key: boolean; 65 | }; 66 | " 67 | `) 68 | }) 69 | }); 70 | 71 | const inputArray = `{ 72 | "name": "aphto", 73 | "arrName": ["fu", 18, { name: 'fu', age: 18 }] 74 | }`; 75 | describe('array any', () => { 76 | it('expect', () => { 77 | expect(json2ts(inputArray, { semicolon: true })).toMatchInlineSnapshot(` 78 | "type Result\$0Type = { 79 | name: string; 80 | arrName: Array; 81 | }; 82 | " 83 | `) 84 | }) 85 | }); 86 | 87 | describe('array parse', () => { 88 | it('expect', () => { 89 | expect(json2ts(inputArray, { semicolon: true, parseArray: true })).toMatchInlineSnapshot(` 90 | "type ArrName\$1Type = { 91 | name: string; 92 | age: number; 93 | }; 94 | 95 | type Result\$0Type = { 96 | name: string; 97 | arrName: Array< string | number | ArrName\$1Type >; 98 | }; 99 | " 100 | `) 101 | 102 | expect(json2ts(`{"foo": [{"bar": ""}]}`, {parseArray: true})).toMatchInlineSnapshot(` 103 | "type Foo\$1Type = { 104 | bar: string 105 | } 106 | 107 | type Result\$0Type = { 108 | foo: Array< Foo\$1Type > 109 | } 110 | " 111 | `); 112 | }) 113 | }); 114 | 115 | 116 | describe('not splitType', () => { 117 | const inputArray = `{ 118 | // This is a name key 119 | name: "bengbeng", // His name is bengbeng 120 | age: 20, // This is his age 121 | interest: [ 'swim', 'football', 22 ] 122 | girlfriend: { 123 | name: "qiaqia", 124 | age: 18, 125 | "exboyfriend": [ 126 | { 127 | name: "uzzz", 128 | age: 40 129 | } 130 | ] 131 | } 132 | }`; 133 | it('expect', () => { 134 | expect(json2ts(inputArray, { semicolon: true, parseArray: true, splitType: false, comment: 'inline' })).toMatchInlineSnapshot(` 135 | "type Result\$0Type = { 136 | name: string; // This is a name key; His name is bengbeng 137 | age: number; // This is his age 138 | interest: Array< string | number >; 139 | girlfriend: { 140 | name: string; 141 | age: number; 142 | exboyfriend: Array< { 143 | name: string; 144 | age: number; 145 | } >; 146 | }; 147 | }; 148 | " 149 | `) 150 | }) 151 | }); 152 | 153 | describe('indent', () => { 154 | const inputArray = `{ 155 | "name": { 156 | key: { 157 | val: 3 158 | } 159 | } 160 | }`; 161 | it('expect', () => { 162 | expect(json2ts(inputArray, { semicolon: true, parseArray: true, splitType: false, indent: 4 })).toMatchInlineSnapshot(` 163 | "type Result\$0Type = { 164 | name: { 165 | key: { 166 | val: number; 167 | }; 168 | }; 169 | }; 170 | " 171 | `) 172 | }) 173 | }); 174 | 175 | describe('indent + splitType', () => { 176 | const inputArray = `{ 177 | "name": { 178 | key: { 179 | val: 3 180 | } 181 | } 182 | }`; 183 | it('expect', () => { 184 | expect(json2ts(inputArray, { semicolon: true, parseArray: true, indent: 4 })).toMatchInlineSnapshot(` 185 | "type Key\$1Type = { 186 | val: number; 187 | }; 188 | 189 | type Name\$2Type = { 190 | key: Key\$1Type; 191 | }; 192 | 193 | type Result\$0Type = { 194 | name: Name\$2Type; 195 | }; 196 | " 197 | `) 198 | }) 199 | }); 200 | 201 | describe('object reuse', () => { 202 | const inputArray = `{ 203 | name: "qiaqia", 204 | age: 18, 205 | girlfriend: [ 206 | { 207 | age: 20, 208 | name: "bengbeng", 209 | }, 210 | { 211 | name: "qiqi", 212 | age: 30 213 | } 214 | ] 215 | }`; 216 | it('expect', () => { 217 | expect(json2ts(inputArray, { semicolon: true, parseArray: true, indent: 2 })).toMatchInlineSnapshot(` 218 | "type Girlfriend\$1Type = { 219 | age: number; 220 | name: string; 221 | }; 222 | 223 | type Result\$0Type = { 224 | name: string; 225 | age: number; 226 | girlfriend: Array< Girlfriend\$1Type >; 227 | }; 228 | " 229 | `) 230 | }) 231 | }); 232 | 233 | describe('comment false', () => { 234 | const inputArray = `{ 235 | // This is 0 name 236 | // This is 1 name 237 | name: '1', 238 | arr: [ 239 | // this is arr 1 240 | 1, 241 | { 242 | // this is arr n 243 | n: 2 } ], // This is arr 244 | girlfriend: { 245 | name: '2', // This is 2 name 246 | hh: { 247 | name: '3' // This is 3 name, 248 | } 249 | } // This is girlfriend 250 | }`; 251 | it('expect', () => { 252 | expect(json2ts(inputArray, { semicolon: true, parseArray: true, indent: 2 })).toMatchInlineSnapshot(` 253 | "type Arr\$1Type = { 254 | n: number; 255 | }; 256 | 257 | type Hh\$2Type = { 258 | name: string; 259 | }; 260 | 261 | type Girlfriend\$3Type = { 262 | name: string; 263 | hh: Hh\$2Type; 264 | }; 265 | 266 | type Result\$0Type = { 267 | name: string; 268 | arr: Array< number | Arr\$1Type >; 269 | girlfriend: Girlfriend\$3Type; 270 | }; 271 | " 272 | `) 273 | }) 274 | }); 275 | 276 | describe('comment block', () => { 277 | const inputArray = `{ 278 | // This is 0 name 279 | // This is 1 name 280 | name: '1', 281 | // This is prefix arr 282 | arr: [ 283 | // this is arr 1 284 | 1, 285 | { 286 | // this is arr n 287 | n: 2 } ], // This is arr 288 | // girlfriend prefix comment 289 | girlfriend: { 290 | name: '2', // This is 2 name 291 | hh: { 292 | name: '3' // This is 3 name, 293 | } 294 | } // This is girlfriend 295 | }`; 296 | it('expect', () => { 297 | expect(json2ts(inputArray, { semicolon: true, parseArray: true, indent: 2, comment: 'block' })).toMatchInlineSnapshot(` 298 | "type Arr\$1Type = { 299 | // this is arr n 300 | n: number; 301 | }; 302 | 303 | type Hh\$2Type = { 304 | // This is 3 name, 305 | name: string; 306 | }; 307 | 308 | type Girlfriend\$3Type = { 309 | // This is 2 name 310 | name: string; 311 | hh: Hh\$2Type; 312 | }; 313 | 314 | type Result\$0Type = { 315 | // This is 0 name 316 | // This is 1 name 317 | name: string; 318 | // This is prefix arr 319 | // This is arr 320 | arr: Array< number | Arr\$1Type >; 321 | // girlfriend prefix comment 322 | // This is girlfriend 323 | girlfriend: Girlfriend\$3Type; 324 | }; 325 | " 326 | `) 327 | expect(json2ts(`{ 328 | "foo": [{ 329 | "bar": 1 // 330 | "baz": 2 331 | }], 332 | }`, { semicolon: true, parseArray: true, indent: 2, comment: 'block' })).toMatchInlineSnapshot(` 333 | "type Foo\$1Type = { 334 | bar: number; 335 | baz: number; 336 | }; 337 | 338 | type Result\$0Type = { 339 | foo: Array< Foo\$1Type >; 340 | }; 341 | " 342 | `) 343 | expect(json2ts(`{ 344 | "foo": 1 // 345 | }`, { semicolon: true, parseArray: true, indent: 2, comment: 'block' })).toMatchInlineSnapshot(` 346 | "type Result\$0Type = { 347 | foo: number; 348 | }; 349 | " 350 | `) 351 | }); 352 | }) 353 | 354 | describe('comment inline', () => { 355 | const inputArray = `{ 356 | // This is 0 name 357 | // This is 1 name 358 | name: '1', 359 | // This is prefix arr 360 | arr: [ 361 | // this is arr 1 362 | 1, 363 | { 364 | // this is arr n 365 | n: 2 } ], // This is arr 366 | // girlfriend prefix comment 367 | girlfriend: { 368 | name: '2', // This is 2 name 369 | hh: { 370 | name: '3' // This is 3 name, 371 | } 372 | } // This is girlfriend 373 | }`; 374 | it('expect', () => { 375 | expect(json2ts(inputArray, { semicolon: true, parseArray: true, indent: 2, comment: 'inline' })).toMatchInlineSnapshot(` 376 | "type Arr\$1Type = { 377 | n: number; // this is arr n 378 | }; 379 | 380 | type Hh\$2Type = { 381 | name: string; // This is 3 name, 382 | }; 383 | 384 | type Girlfriend\$3Type = { 385 | name: string; // This is 2 name 386 | hh: Hh\$2Type; 387 | }; 388 | 389 | type Result\$0Type = { 390 | name: string; // This is 0 name; This is 1 name 391 | arr: Array< number | Arr\$1Type >; // This is prefix arr; This is arr 392 | girlfriend: Girlfriend\$3Type; // girlfriend prefix comment; This is girlfriend 393 | }; 394 | " 395 | `) 396 | expect(json2ts(inputArray, { semicolon: true, parseArray: false, indent: 2, comment: 'inline' })).toMatchInlineSnapshot(` 397 | "type Hh\$1Type = { 398 | name: string; // This is 3 name, 399 | }; 400 | 401 | type Girlfriend\$2Type = { 402 | name: string; // This is 2 name 403 | hh: Hh\$1Type; 404 | }; 405 | 406 | type Result\$0Type = { 407 | name: string; // This is 0 name; This is 1 name 408 | arr: Array; // This is prefix arr; This is arr 409 | girlfriend: Girlfriend\$2Type; // girlfriend prefix comment; This is girlfriend 410 | }; 411 | " 412 | `) 413 | }) 414 | }); 415 | 416 | describe('empty array', () => { 417 | const inputArray = `{ 418 | name: '1', 419 | arr: [] 420 | }`; 421 | it('expect', () => { 422 | expect(json2ts(inputArray, { semicolon: true, parseArray: true})).toMatchInlineSnapshot(` 423 | "type Result\$0Type = { 424 | name: string; 425 | arr: Array< unknown >; 426 | }; 427 | " 428 | `) 429 | }) 430 | }); 431 | 432 | describe('array with different Object', () => { 433 | const inputArray = `{ 434 | arr: [ 435 | { 436 | a: 'name', 437 | b: 'age' 438 | }, 439 | { 440 | a: 'name', 441 | } 442 | ] 443 | }`; 444 | it('expect', () => { 445 | expect(json2ts(inputArray, { semicolon: true, parseArray: true, optimizeArrayOptional: true })).toMatchInlineSnapshot(` 446 | "type Arr\$1Type = { 447 | a: string; 448 | b?: string; 449 | }; 450 | 451 | type Result\$0Type = { 452 | arr: Array< Arr\$1Type >; 453 | }; 454 | " 455 | `) 456 | }) 457 | }); 458 | 459 | describe('array with different Object', () => { 460 | const inputArray = `{ 461 | name: 'name', 462 | obj: { 463 | name: 'name', 464 | } 465 | }`; 466 | it('expect', () => { 467 | expect(json2ts(inputArray, { genType: 'interface' })).toMatchInlineSnapshot(` 468 | "interface Obj\$1Type { 469 | name: string 470 | } 471 | 472 | interface Result\$0Type { 473 | name: string 474 | obj: Obj\$1Type 475 | } 476 | " 477 | `) 478 | }) 479 | }); 480 | 481 | describe("parse number with digit", () => { 482 | 483 | it('compile number with digit', () => { 484 | expect(json2ts(`{num: 0.2}`)).toMatchInlineSnapshot(` 485 | "type Result\$0Type = { 486 | num: number 487 | } 488 | " 489 | `) 490 | }) 491 | 492 | it('compile number with -', () => { 493 | expect(json2ts(`{num: -0.2}`)).toMatchInlineSnapshot(` 494 | "type Result\$0Type = { 495 | num: number 496 | } 497 | " 498 | `) 499 | }) 500 | 501 | it('compile number with +', () => { 502 | expect(json2ts(`{num: +0.2}`)).toMatchInlineSnapshot(` 503 | "type Result\$0Type = { 504 | num: number 505 | } 506 | " 507 | `) 508 | }) 509 | }) 510 | 511 | describe("scientific notation", () => { 512 | it("compile scientific notation", () => { 513 | expect(json2ts(`{num: 3.14E+10}`)).toMatchInlineSnapshot(` 514 | "type Result\$0Type = { 515 | num: number 516 | } 517 | " 518 | `); 519 | }); 520 | 521 | it("compile scientific notation with -", () => { 522 | expect(json2ts(`{num: 2.71828e-05}`)).toMatchInlineSnapshot(` 523 | "type Result\$0Type = { 524 | num: number 525 | } 526 | " 527 | `); 528 | }); 529 | 530 | it("JSON does not support scientific notation starting with decimal point", () => { 531 | expect(() => json2ts(`{num: .71828e-05}`)).toThrowError( 532 | VALUE_ILLEGAL_ERROR_MESSAGE 533 | ); 534 | }); 535 | }); 536 | 537 | describe("parse with quotes", () => { 538 | it("compile with quotes", () => { 539 | expect(json2ts(`{ "foo": "''" }`)).toMatchInlineSnapshot(` 540 | "type Result\$0Type = { 541 | foo: string 542 | } 543 | " 544 | `); 545 | expect(json2ts(`{ "foo": "'', ''" } `)).toMatchInlineSnapshot(` 546 | "type Result\$0Type = { 547 | foo: string 548 | } 549 | " 550 | `); 551 | }); 552 | }); -------------------------------------------------------------------------------- /tests/parse.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, expect, test, it } from 'vitest'; 2 | import { parse } from '../src/parse'; 3 | import { 4 | ARRAY_ITEM, 5 | ARRAY_TYPE, 6 | NULL_TYPE, 7 | NUMBER_TYPE, 8 | STRING_TYPE, 9 | UNDEFINED_TYPE, 10 | ARRAY_ERROR_MESSAGE, 11 | COMMENT_ERROR_MESSAGE, 12 | VALUE_ILLEGAL_ERROR_MESSAGE, 13 | NEXT_COMMENT, 14 | LAST_COMMENT 15 | } from '../src'; 16 | import type { AstChildNode } from '../src/types'; 17 | 18 | // Edit an assertion and save to see HMR in action 19 | 20 | 21 | const json0 = `{}`; 22 | const json1 = `{ 23 | "name:": "aphto", 24 | "age": 18 25 | }`; 26 | 27 | describe('empty json', () => { 28 | it('expect', () => { 29 | expect(parse(json0)).toMatchObject({ key: 'root', type: 'Root', value: [] }) 30 | }) 31 | }); 32 | 33 | describe('null parse', () => { 34 | const nullJson = { 35 | "name": null 36 | } 37 | const result = parse(JSON.stringify(nullJson)).value; 38 | const content = result[0] as AstChildNode; 39 | it('expect', () => { 40 | expect(result.length).toEqual(1); 41 | expect(content.value).toEqual('null'); 42 | expect(content.key).toEqual('name'); 43 | expect(content.type).toEqual(NULL_TYPE); 44 | expect(content.loc.source).toMatchInlineSnapshot('"\\"name\\":null"') 45 | }) 46 | }) 47 | 48 | describe('array should be closed', () => { 49 | // 数组没有闭合,会继续往下解析,把girlfriend当成value,所以会提示 VALUE_ILLEGAL_ERROR_MESSAGE 50 | const json1 = `{ 51 | // 这是一个名字 52 | interest: [ 'swim', 'football', 22 53 | girlfriend: { 54 | name: "qiaqia", 55 | age: 18, 56 | "exboyfriend": [ 57 | { 58 | name: "uzzz", 59 | age: 40 60 | } 61 | ] 62 | } 63 | }`; 64 | const json2 = `{ 65 | // 这是一个名字 66 | interest: [ 'swim', 'football', 22 67 | }`; 68 | it('expect', () => { 69 | expect(() => parse(json1)).toThrow(VALUE_ILLEGAL_ERROR_MESSAGE); 70 | expect(() => parse(json2)).toThrow(ARRAY_ERROR_MESSAGE); 71 | }) 72 | }) 73 | 74 | describe('array item comment', () => { 75 | // 数组没有闭合,会继续往下解析,把girlfriend当成value,所以会提示 VALUE_ILLEGAL_ERROR_MESSAGE 76 | const json1 = `{ 77 | // This is a name key 78 | name: "bengbeng", // His name is bengbeng 79 | age: 20, // This is his age 80 | interest: [ 81 | // 2 82 | // 2-1 83 | 'swim', // inline swim 84 | // 3 85 | 'football', 86 | // 4 87 | 22 // 5 88 | ] 89 | }`; 90 | let res = (parse(json1).value[5] as AstChildNode).value as AstChildNode[]; 91 | it('expect', () => { 92 | expect(res[0].key).toEqual(NEXT_COMMENT); 93 | expect(res[1].key).toEqual(NEXT_COMMENT); 94 | expect(res[3].key).toEqual(LAST_COMMENT); 95 | expect(res[4].key).toEqual(NEXT_COMMENT); 96 | expect(res[6].key).toEqual(NEXT_COMMENT); 97 | expect(res[8].key).toEqual(LAST_COMMENT); 98 | }) 99 | }) 100 | 101 | 102 | 103 | describe('comment should be legal', () => { 104 | const nullJson = `{ 105 | / is illegal 106 | "name": 1 107 | }` 108 | it('expect', () => { 109 | expect(() => parse(nullJson)).toThrow(COMMENT_ERROR_MESSAGE); 110 | }) 111 | }) 112 | 113 | describe('undefined parse', () => { 114 | const undefinedJson = `{ 115 | name: undefined 116 | }` 117 | const result = parse(undefinedJson).value; 118 | const content = result[0] as AstChildNode; 119 | it('expect', () => { 120 | expect(result.length).toEqual(1); 121 | expect(content.value).toEqual('undefined'); 122 | expect(content.key).toEqual('name'); 123 | expect(content.type).toEqual(UNDEFINED_TYPE); 124 | expect(content.loc.source).toMatchInlineSnapshot('"name: undefined"') 125 | }) 126 | }) 127 | 128 | 129 | const json2 = { 130 | "a": 123, 131 | "b": { 132 | "c": "123" 133 | }, 134 | d: [1, 2, 3] 135 | } 136 | describe('object + array + no quo', () => { 137 | it('expect', () => { 138 | expect(parse(JSON.stringify(json2))).toEqual({ "key": "root", "type": "Root", "value": [{ "key": "a", "value": "123", "type": "number", "loc": { "start": { "offset": 1, "column": 2, "line": 1 }, "end": { "offset": 8, "column": 9, "line": 1 }, "source": "\"a\":123" } }, { "key": "b", "value": [{ "key": "c", "value": "123", "type": "string", "loc": { "start": { "offset": 14, "column": 15, "line": 1 }, "end": { "offset": 23, "column": 24, "line": 1 }, "source": "\"c\":\"123\"" } }], "type": "Object", "loc": { "start": { "offset": 9, "column": 10, "line": 1 }, "end": { "offset": 24, "column": 25, "line": 1 }, "source": "\"b\":{\"c\":\"123\"}" } }, { "key": "d", "value": [{ "key": "$ARRAY_ITEM$", "value": "1", "type": "number", "loc": { "start": { "offset": 30, "column": 31, "line": 1 }, "end": { "offset": 31, "column": 32, "line": 1 }, "source": "1" } }, { "key": "$ARRAY_ITEM$", "value": "2", "type": "number", "loc": { "start": { "offset": 32, "column": 33, "line": 1 }, "end": { "offset": 33, "column": 34, "line": 1 }, "source": "2" } }, { "key": "$ARRAY_ITEM$", "value": "3", "type": "number", "loc": { "start": { "offset": 34, "column": 35, "line": 1 }, "end": { "offset": 35, "column": 36, "line": 1 }, "source": "3" } }], "type": "Array", "loc": { "start": { "offset": 25, "column": 26, "line": 1 }, "end": { "offset": 36, "column": 37, "line": 1 }, "source": "\"d\":[1,2,3]" } }] }) 139 | }) 140 | }) 141 | 142 | describe('array any', () => { 143 | const jsonArr = { 144 | arr: [1, 2, '3'] 145 | } 146 | const res = parse(JSON.stringify(jsonArr)); 147 | const content = res.value[0] as AstChildNode; 148 | const value1 = content.value[0] as AstChildNode; 149 | const value2 = content.value[1] as AstChildNode; 150 | const value3 = content.value[2] as AstChildNode; 151 | it('expect', () => { 152 | expect(content.key).toEqual('arr'); 153 | expect(content.type).toEqual(ARRAY_TYPE); 154 | expect(content.value.length).toEqual(3); 155 | expect(value1.type).toEqual(NUMBER_TYPE); 156 | expect(value1.value).toEqual('1'); 157 | expect(value1.key).toEqual(ARRAY_ITEM); 158 | expect(value2.type).toEqual(NUMBER_TYPE); 159 | expect(value2.value).toEqual('2'); 160 | expect(value2.key).toEqual(ARRAY_ITEM); 161 | expect(value3.type).toEqual(STRING_TYPE); 162 | expect(value3.value).toEqual('3'); 163 | expect(value3.key).toEqual(ARRAY_ITEM); 164 | }) 165 | }) -------------------------------------------------------------------------------- /tests/transform.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { parse } from '../src/parse'; 3 | import { transform } from '../src/transform'; 4 | 5 | const json0 = `{}`; 6 | const json1 = `{ 7 | "name": "aphto", 8 | "age": 18 9 | }`; 10 | 11 | describe('empty json', () => { 12 | it('expect', () => { 13 | expect(transform(parse(json0))).toMatchObject({ key: 'root', type: 'Root', value: [] }) 14 | }) 15 | }); 16 | 17 | describe('simple json', () => { 18 | it('expect', () => { 19 | const ast = transform(parse(json1)); 20 | expect(ast).toMatchInlineSnapshot(` 21 | { 22 | "comments": {}, 23 | "key": "root", 24 | "type": "Root", 25 | "typeValue": { 26 | "age": "number", 27 | "name": "string", 28 | }, 29 | "value": [ 30 | { 31 | "i": 0, 32 | "key": "name", 33 | "loc": { 34 | "end": { 35 | "column": 18, 36 | "line": 2, 37 | "offset": 19, 38 | }, 39 | "source": "\\"name\\": \\"aphto\\"", 40 | "start": { 41 | "column": 3, 42 | "line": 2, 43 | "offset": 4, 44 | }, 45 | }, 46 | "type": "string", 47 | "value": "aphto", 48 | }, 49 | { 50 | "i": 0, 51 | "key": "age", 52 | "loc": { 53 | "end": { 54 | "column": 12, 55 | "line": 3, 56 | "offset": 32, 57 | }, 58 | "source": "\\"age\\": 18", 59 | "start": { 60 | "column": 3, 61 | "line": 3, 62 | "offset": 23, 63 | }, 64 | }, 65 | "type": "number", 66 | "value": "18", 67 | }, 68 | ], 69 | } 70 | `); 71 | }) 72 | }); -------------------------------------------------------------------------------- /tests/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { upperCaseFirstChat, isObject, isArray } from '../src/utils'; 3 | 4 | describe('upperCaseFirstChat', () => { 5 | it('expect', () => { 6 | expect(upperCaseFirstChat('userName')).toMatchInlineSnapshot('"UserName"'); 7 | expect(upperCaseFirstChat('UserName')).toMatchInlineSnapshot('"UserName"'); 8 | }) 9 | }); 10 | 11 | describe('isObject', () => { 12 | it('expect', () => { 13 | expect(isObject({})).toBeTruthy(); 14 | expect(isObject([])).toBeFalsy(); 15 | expect(isObject(`{}`)).toBeFalsy(); 16 | }) 17 | }); 18 | 19 | describe('isArray', () => { 20 | it('expect', () => { 21 | expect(isArray([])).toBeTruthy(); 22 | expect(isArray({})).toBeFalsy(); 23 | expect(isArray(`[]`)).toBeFalsy(); 24 | }) 25 | }); 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "CommonJS", 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "outDir": "./lib" 8 | } 9 | } -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare type CompileOptions = { 2 | splitType?: boolean; 3 | parseArray?: boolean; 4 | required?: boolean; 5 | semicolon?: boolean; 6 | typePrefix?: string; 7 | typeSuffix?: string; 8 | indent?: number; 9 | comment?: 'inline' | 'block' | false | 'false'; 10 | optimizeArrayOptional?: boolean; 11 | genType?: 'type' | 'interface'; 12 | }; 13 | declare type Position = { 14 | offset: number; 15 | line: number; 16 | column: number; 17 | }; 18 | declare type LocType = { 19 | start: Position; 20 | end: Position; 21 | source: string; 22 | }; 23 | declare type AstChildNode = { 24 | key: string; 25 | value: string | AstChildNode[]; 26 | type: string; 27 | loc?: LocType; 28 | }; 29 | declare type TransformNodeType = AstChildNode & { 30 | typeValue?: Record | Array; 31 | comments?: Record; 32 | i?: number; 33 | nextComment?: string[]; 34 | }; 35 | declare type Visiter = { 36 | [key: string]: { 37 | entry?: (node: TransformNodeType, parent: TransformNodeType | null) => void; 38 | exit?: (node: TransformNodeType, parent: TransformNodeType | null) => void; 39 | }; 40 | }; 41 | 42 | /** 43 | * parse to ast 44 | * @param input 45 | * @param options 46 | * @returns ast 47 | */ 48 | declare function parse(input: string, options?: CompileOptions): AstChildNode; 49 | 50 | declare function traverser(ast: TransformNodeType, visiter: Visiter): TransformNodeType; 51 | 52 | declare const ROOT_TYPE = "Root"; 53 | declare const STRING_TYPE = "string"; 54 | declare const NUMBER_TYPE = "number"; 55 | declare const NULL_TYPE = "null"; 56 | declare const BOOLEAN_TYPE = "boolean"; 57 | declare const UNDEFINED_TYPE = "undefined"; 58 | declare const OBJECT_TYPE = "Object"; 59 | declare const ARRAY_TYPE = "Array"; 60 | declare const COMMENT_TYPE = "Comment"; 61 | declare const ROOT_KEY = "root"; 62 | declare const ARRAY_ITEM = "$ARRAY_ITEM$"; 63 | declare const LAST_COMMENT = "$LAST_COMMENT$"; 64 | declare const NEXT_COMMENT = "$NEXT_COMMENT$"; 65 | declare const ARRAY_ERROR_MESSAGE = "array should be closed"; 66 | declare const COMMENT_ERROR_MESSAGE = "comment is illegal"; 67 | declare const VALUE_ILLEGAL_ERROR_MESSAGE = "value is illegal"; 68 | 69 | declare function json2ts(code: string, options?: CompileOptions): string; 70 | 71 | export { ARRAY_ERROR_MESSAGE, ARRAY_ITEM, ARRAY_TYPE, BOOLEAN_TYPE, COMMENT_ERROR_MESSAGE, COMMENT_TYPE, LAST_COMMENT, NEXT_COMMENT, NULL_TYPE, NUMBER_TYPE, OBJECT_TYPE, ROOT_KEY, ROOT_TYPE, STRING_TYPE, UNDEFINED_TYPE, VALUE_ILLEGAL_ERROR_MESSAGE, json2ts as default, json2ts, parse, traverser }; 72 | -------------------------------------------------------------------------------- /types/parse.d.ts: -------------------------------------------------------------------------------- 1 | declare type CompileOptions = { 2 | splitType?: boolean; 3 | parseArray?: boolean; 4 | required?: boolean; 5 | semicolon?: boolean; 6 | typePrefix?: string; 7 | typeSuffix?: string; 8 | indent?: number; 9 | comment?: 'inline' | 'block' | false | 'false'; 10 | optimizeArrayOptional?: boolean; 11 | genType?: 'type' | 'interface'; 12 | }; 13 | declare type Position = { 14 | offset: number; 15 | line: number; 16 | column: number; 17 | }; 18 | declare type LocType = { 19 | start: Position; 20 | end: Position; 21 | source: string; 22 | }; 23 | declare type AstChildNode = { 24 | key: string; 25 | value: string | AstChildNode[]; 26 | type: string; 27 | loc?: LocType; 28 | }; 29 | 30 | /** 31 | * parse to ast 32 | * @param input 33 | * @param options 34 | * @returns ast 35 | */ 36 | declare function parse(input: string, options?: CompileOptions): AstChildNode; 37 | 38 | export { parse }; 39 | -------------------------------------------------------------------------------- /types/transform.d.ts: -------------------------------------------------------------------------------- 1 | declare type CompileOptions = { 2 | splitType?: boolean; 3 | parseArray?: boolean; 4 | required?: boolean; 5 | semicolon?: boolean; 6 | typePrefix?: string; 7 | typeSuffix?: string; 8 | indent?: number; 9 | comment?: 'inline' | 'block' | false | 'false'; 10 | optimizeArrayOptional?: boolean; 11 | genType?: 'type' | 'interface'; 12 | }; 13 | declare type Position = { 14 | offset: number; 15 | line: number; 16 | column: number; 17 | }; 18 | declare type LocType = { 19 | start: Position; 20 | end: Position; 21 | source: string; 22 | }; 23 | declare type AstChildNode = { 24 | key: string; 25 | value: string | AstChildNode[]; 26 | type: string; 27 | loc?: LocType; 28 | }; 29 | declare type TransformNodeType = AstChildNode & { 30 | typeValue?: Record | Array; 31 | comments?: Record; 32 | i?: number; 33 | nextComment?: string[]; 34 | }; 35 | declare type Visiter = { 36 | [key: string]: { 37 | entry?: (node: TransformNodeType, parent: TransformNodeType | null) => void; 38 | exit?: (node: TransformNodeType, parent: TransformNodeType | null) => void; 39 | }; 40 | }; 41 | 42 | declare function traverser(ast: TransformNodeType, visiter: Visiter): TransformNodeType; 43 | declare function transform(ast: TransformNodeType, options?: CompileOptions): TransformNodeType; 44 | 45 | export { transform, traverser }; 46 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | 3 | export default defineConfig({ 4 | test: {}, 5 | 6 | }) 7 | --------------------------------------------------------------------------------