├── .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 | 
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 |
--------------------------------------------------------------------------------