Skip to content

Commit 86dce60

Browse files
author
k.golikov
committed
different fixes
1 parent 64370c2 commit 86dce60

13 files changed

+1097
-71
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
"@testing-library/jest-dom": "^5.14.1",
7171
"@testing-library/react": "^12.0.0",
7272
"@testing-library/user-event": "^13.2.1",
73-
"@types/jest": "^27.0.1",
73+
"@types/jest": "^27.4.1",
7474
"@types/lodash": "^4.14.180",
7575
"@types/mustache": "^4.1.2",
7676
"@types/node": "^16.7.13",
@@ -93,6 +93,7 @@
9393
"gulp": "^4.0.2",
9494
"gulp-debug": "^4.0.0",
9595
"husky": "^7.0.0",
96+
"jest": "^28.0.3",
9697
"lint-staged": "^12.3.7",
9798
"prettier": "^2.6.2",
9899
"pretty-quick": "^3.1.3",

src/pages/jsonToTypeScriptPage/JsonToTypeScriptPage.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@ import classNames from 'classnames';
66
import AppEditor from '../../components/appEditor/AppEditor';
77
import { editor } from 'monaco-editor';
88
import { useDebouncedMemo } from '../../hooks/debouncedMemo';
9-
import getTypeScriptType from './utils/getTypeScriptType';
10-
import parseJsonObject from './utils/parseJsonObject';
119
import ExportType from './types/ExportType';
12-
import getAllTypeScriptTypeDeclarations from './utils/getAllTypeScriptTypeDeclarations';
10+
import convertJsonToTypeScript from './utils/convertJsonToTypeScript';
1311

1412
const jsonEditorOptions: editor.IStandaloneEditorConstructionOptions = {
1513
minimap: { enabled: false }
@@ -30,8 +28,7 @@ const JsonToTypeScriptPage = () => {
3028
return '';
3129
}
3230

33-
const typeScriptType = getTypeScriptType('Root', parseJsonObject(JSON.parse(json)));
34-
return getAllTypeScriptTypeDeclarations(typeScriptType, 'Root', {
31+
return convertJsonToTypeScript(json, {
3532
exportType: ExportType.ES_MODULE,
3633
isReversedOrder: true
3734
});
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import convertJsonToTypeScript from '../utils/convertJsonToTypeScript';
2+
import TypeScriptDeclarationOptions from '../types/TypeScriptDeclarationOptions';
3+
import ExportType from '../types/ExportType';
4+
5+
const defaultOptions: TypeScriptDeclarationOptions = {
6+
exportType: ExportType.ES_MODULE,
7+
isReversedOrder: true
8+
};
9+
10+
test('test1', () => {
11+
const json = `
12+
[
13+
2,
14+
4,
15+
"",
16+
{
17+
"id": 122
18+
},
19+
{
20+
"id": 123214,
21+
"name": "ewrwereeve"
22+
}
23+
]`;
24+
25+
const expected = `
26+
export type Root = Array<number | string | Root2>;
27+
28+
export interface Root2 {
29+
id: number;
30+
name?: string;
31+
}`.trimStart();
32+
33+
const actual = convertJsonToTypeScript(json, defaultOptions);
34+
35+
expect(actual).toBe(expected);
36+
});
37+
38+
test('test2', () => {
39+
const json = `{
40+
"suggestions": [{
41+
"feature_name": "〒105-0004 東京都港区新橋1丁目10番1号",
42+
"matching_name": "〒105-0004 東京都港区新橋1丁目10番1号",
43+
"description": "",
44+
"result_type": [
45+
"address"
46+
],
47+
"language": "ja",
48+
"action": {
49+
"endpoint": "retrieve",
50+
"method": "POST",
51+
"body": {
52+
"id": "abc123"
53+
},
54+
"multi_retrievable": false
55+
},
56+
"maki": "marker",
57+
"internal_id": "example internal id",
58+
"external_ids": {
59+
"service": "2wwRMXwBRYqRl13lTvTP"
60+
},
61+
"context": [{
62+
"layer": "block",
63+
"localized_layer": "block",
64+
"name": "10"
65+
},
66+
{
67+
"layer": "neighborhood",
68+
"localized_layer": "chome",
69+
"name": "1丁目"
70+
},
71+
{
72+
"layer": "locality",
73+
"localized_layer": "oaza",
74+
"name": "新橋"
75+
},
76+
{
77+
"layer": "place",
78+
"localized_layer": "city",
79+
"name": "港区"
80+
},
81+
{
82+
"layer": "region",
83+
"localized_layer": "prefecture",
84+
"name": "東京都"
85+
}
86+
],
87+
"metadata": {
88+
"iso_3166_1": "jp",
89+
"iso_3166_2": "JP-13",
90+
"reading": {
91+
"ja_kana": "トウキョウトミナトクシンバシ",
92+
"ja_latin": "toukyouto minatoku shinbashi"
93+
}
94+
}
95+
}],
96+
"attribution": "© 2021 Mapbox and its suppliers. All rights reserved. Use of this data is subject to the Mapbox Terms of Service. (https://www.mapbox.com/about/maps/)",
97+
"version": "11:061a06471ec519420eeca5be2b87e043f2fe4cbe",
98+
"response_uuid": "4b659949-c8c7-42d8-a541-6b427e5806cc"
99+
}`;
100+
101+
const expected = `
102+
export interface Root {
103+
suggestions: Suggestion[];
104+
attribution: string;
105+
version: string;
106+
response_uuid: string;
107+
}
108+
109+
export interface Suggestion {
110+
feature_name: string;
111+
matching_name: string;
112+
description: string;
113+
result_type: string[];
114+
language: string;
115+
action: Action;
116+
maki: string;
117+
internal_id: string;
118+
external_ids: ExternalIds;
119+
context: Context[];
120+
metadata: Metadata;
121+
}
122+
123+
export interface Action {
124+
endpoint: string;
125+
method: string;
126+
body: Body;
127+
multi_retrievable: boolean;
128+
}
129+
130+
export interface Body {
131+
id: string;
132+
}
133+
134+
export interface ExternalIds {
135+
service: string;
136+
}
137+
138+
export interface Context {
139+
layer: string;
140+
localized_layer: string;
141+
name: string;
142+
}
143+
144+
export interface Metadata {
145+
iso_3166_1: string;
146+
iso_3166_2: string;
147+
reading: Reading;
148+
}
149+
150+
export interface Reading {
151+
ja_kana: string;
152+
ja_latin: string;
153+
}`.trimStart();
154+
155+
const actual = convertJsonToTypeScript(json, defaultOptions);
156+
157+
expect(actual).toBe(expected);
158+
});
159+
160+
export {};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import getTypeScriptType from '../utils/getTypeScriptType';
2+
import { JsonArray, JsonObject } from '../types/json';
3+
import { TypeScriptArray, TypeScriptInterface, TypeScriptUnion } from '../types/typescript';
4+
5+
test('merging', () => {
6+
const jsonType = new JsonArray([
7+
new JsonObject({}),
8+
'number',
9+
new JsonObject({}),
10+
'string',
11+
'string',
12+
'string',
13+
'number',
14+
'number'
15+
]);
16+
17+
const expected = new TypeScriptArray(
18+
new TypeScriptUnion('Root', [new TypeScriptInterface('Root2', {}), 'number', 'string'])
19+
);
20+
21+
const actual = getTypeScriptType('Root', jsonType);
22+
23+
expect(actual).toBe(expected);
24+
});
25+
26+
export {};

src/pages/jsonToTypeScriptPage/types/typescript.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { JsonPrimitive } from './json';
22
import getTypeScriptTypeReference from '../utils/getTypeScriptTypeReference';
33
import mapObject from '../../../utils/mapObject';
44
import ExportType from './ExportType';
5-
import { isObject, isString } from 'lodash';
5+
import { filter, isObject, isString } from 'lodash';
66
import TypeScriptDeclarationOptions from './TypeScriptDeclarationOptions';
7+
import isValidJsIdentifier, { isValidTsTypeFieldName } from '../../../utils/isValidJsIdentifier';
78

89
export interface ITypeScriptType {
910
stringifyReference(): string;
@@ -32,7 +33,9 @@ export class DeclarableTypeScriptType implements IDeclarableTypeScriptType {
3233
return this.type.stringifyDeclaration(options);
3334
}
3435

35-
return `${getExportKeyword(options.exportType)}type ${this.name} = ${this.stringifyDeclarationBody()}`;
36+
return `${getExportKeyword(options.exportType)}type ${stringifyTypeName(
37+
this.name
38+
)} = ${this.stringifyDeclarationBody()};`;
3639
}
3740

3841
stringifyDeclarationBody(): string {
@@ -68,21 +71,23 @@ export class TypeScriptInterface implements ITypeScriptType, IDeclarableTypeScri
6871
public constructor(public name: string, public readonly fields: Record<string, TypeScriptObjectField>) {}
6972

7073
stringifyDeclaration({ exportType }: TypeScriptDeclarationOptions): string {
71-
return `${getExportKeyword(exportType)}interface ${this.name} ${this.stringifyDeclarationBody()}`;
74+
return `${getExportKeyword(
75+
exportType
76+
)}interface ${this.stringifyReference()} ${this.stringifyDeclarationBody()}`;
7277
}
7378

7479
stringifyDeclarationBody(): string {
7580
return (
7681
'{\n' +
7782
mapObject(this.fields, (key, field) => {
78-
return ` ${key}${field.stringifyDeclarationBody()};`;
83+
return ` ${stringifyFieldName(key)}${field.stringifyDeclarationBody()};`;
7984
}).join('\n') +
8085
'\n}'
8186
);
8287
}
8388

8489
stringifyReference(): string {
85-
return this.name;
90+
return stringifyTypeName(this.name);
8691
}
8792
}
8893

@@ -144,3 +149,25 @@ const getExportKeyword = (exportType: ExportType = ExportType.NONE) => {
144149
[ExportType.COMMONJS]: 'module.exports = '
145150
}[exportType];
146151
};
152+
153+
const stringifyFieldName = (name: string) => {
154+
if (isValidTsTypeFieldName(name)) {
155+
return name;
156+
}
157+
158+
return `'${name}'`;
159+
};
160+
161+
const stringifyTypeName = (name: string) => {
162+
if (isValidJsIdentifier(name)) {
163+
return name;
164+
}
165+
166+
let result = name;
167+
168+
if (/^\d$/.test(name[0])) {
169+
result = 'N' + result;
170+
}
171+
172+
return filter(result, isValidTsTypeFieldName).join('');
173+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import getTypeScriptType from './getTypeScriptType';
2+
import parseJsonObject from './parseJsonObject';
3+
import getAllTypeScriptTypeDeclarations from './getAllTypeScriptTypeDeclarations';
4+
import TypeScriptDeclarationOptions from '../types/TypeScriptDeclarationOptions';
5+
6+
const rootName = 'Root';
7+
8+
const convertJsonToTypeScript = (json: string, options: TypeScriptDeclarationOptions): string => {
9+
const typeScriptType = getTypeScriptType(rootName, parseJsonObject(JSON.parse(json)));
10+
return getAllTypeScriptTypeDeclarations(typeScriptType, rootName, options);
11+
};
12+
13+
export default convertJsonToTypeScript;

src/pages/jsonToTypeScriptPage/utils/getTypeScriptType.ts

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,12 @@
11
import { JsonArray, JsonType } from '../types/json';
2-
import {
3-
TypeScriptArray,
4-
TypeScriptInterface,
5-
TypeScriptObjectField,
6-
TypeScriptType,
7-
TypeScriptUnion,
8-
TypeScriptUnknown
9-
} from '../types/typescript';
10-
import { camelCase, isString, mapValues } from 'lodash';
2+
import { TypeScriptArray, TypeScriptInterface, TypeScriptObjectField, TypeScriptType } from '../types/typescript';
3+
import { isString, mapValues } from 'lodash';
114
import { singular } from 'pluralize';
12-
import capitalizeFirst from '../../../utils/capitalizeFirst';
135
import getMergedTypeScriptTypes from './mergeTypeScriptTypesList';
6+
import pascalCase from '../../../utils/pascalCase';
147

158
const getTypeScriptType = (name: string, jsonObject: JsonType): TypeScriptType => {
16-
// const parsedJson = parseJsonType(object);
17-
//
18-
// if (!(parsedJson instanceof JsonObject)) {
19-
// throw new Error('The parsed object is not a JsonObject');
20-
// }
21-
22-
const typeName = capitalizeFirst(camelCase(name));
9+
const typeName = pascalCase(name);
2310
const arrayElementName = singular(typeName);
2411

2512
if (isString(jsonObject)) {
@@ -30,14 +17,7 @@ const getTypeScriptType = (name: string, jsonObject: JsonType): TypeScriptType =
3017
const typeScriptTypes = jsonObject.types.map((type) => getTypeScriptType(arrayElementName, type));
3118
const mergedTypeScriptTypes = getMergedTypeScriptTypes(typeScriptTypes);
3219

33-
if (mergedTypeScriptTypes.length === 0) {
34-
return new TypeScriptArray(new TypeScriptUnknown());
35-
}
36-
if (mergedTypeScriptTypes.length === 1) {
37-
return new TypeScriptArray(mergedTypeScriptTypes[0]);
38-
}
39-
40-
return new TypeScriptArray(new TypeScriptUnion(arrayElementName, mergedTypeScriptTypes));
20+
return new TypeScriptArray(mergedTypeScriptTypes);
4121
}
4222

4323
return new TypeScriptInterface(

src/pages/jsonToTypeScriptPage/utils/getTypeScriptUnion.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ import { TypeScriptType, TypeScriptUnion, TypeScriptUnknown } from '../types/typ
22
import { remove } from 'lodash';
33

44
const getTypeScriptUnion = (name: string, types: TypeScriptType[]): TypeScriptType => {
5+
if (types.length > 1) {
6+
remove(types, (type) => type instanceof TypeScriptUnknown);
7+
}
8+
59
if (types.length === 0) {
610
return new TypeScriptUnknown();
711
}
812
if (types.length === 1) {
913
return types[0];
1014
}
1115

12-
remove(types, (type) => type instanceof TypeScriptUnknown);
1316
return new TypeScriptUnion(name, types);
1417
};
1518

0 commit comments

Comments
 (0)