Skip to content

Commit fd573ee

Browse files
author
k.golikov
committed
JSON-To-TypeScript: merging types, WIP
1 parent 72522b4 commit fd573ee

File tree

6 files changed

+112
-20
lines changed

6 files changed

+112
-20
lines changed

src/pages/jsonToTypeScriptPage/types/typescript.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { JsonPrimitive } from './json';
2-
import { IObject } from './common';
32
import getTypeScriptTypeReference from '../utils/getTypeScriptTypeReference';
43
import mapObject from '../../../utils/mapObject';
54
import ExportType from './ExportType';
@@ -8,10 +7,13 @@ export interface ITypeScriptType {
87
stringifyReference(): string;
98
}
109

11-
export interface IDeclarableTypeScriptType {
10+
export interface IDeclarable {
11+
stringifyDeclarationBody(): string;
12+
}
13+
14+
export interface IDeclarableTypeScriptType extends IDeclarable {
1215
readonly name: string;
1316
stringifyDeclaration(exportType?: ExportType): string;
14-
stringifyDeclarationBody(): string;
1517
}
1618

1719
export class TypeScriptUnknown implements ITypeScriptType {
@@ -22,10 +24,16 @@ export class TypeScriptUnknown implements ITypeScriptType {
2224
}
2325
}
2426

25-
export class TypeScriptInterface extends IObject<TypeScriptType> implements ITypeScriptType, IDeclarableTypeScriptType {
26-
public constructor(public readonly name: string, public readonly fields: Record<string, TypeScriptType>) {
27-
super(fields);
27+
export class TypeScriptObjectField implements IDeclarable {
28+
public constructor(public readonly type: TypeScriptType, public readonly isOptional = false) {}
29+
30+
stringifyDeclarationBody(): string {
31+
return `${this.isOptional ? '?' : ''}: ${getTypeScriptTypeReference(this.type)}`;
2832
}
33+
}
34+
35+
export class TypeScriptInterface implements ITypeScriptType, IDeclarableTypeScriptType {
36+
public constructor(public readonly name: string, public readonly fields: Record<string, TypeScriptObjectField>) {}
2937

3038
stringifyDeclaration(exportType?: ExportType): string {
3139
return `${getExportKeyword(exportType)}interface ${this.name} ${this.stringifyDeclarationBody()}`;
@@ -34,8 +42,8 @@ export class TypeScriptInterface extends IObject<TypeScriptType> implements ITyp
3442
stringifyDeclarationBody(): string {
3543
return (
3644
'{\n' +
37-
mapObject(this.fields, (key, value) => {
38-
return ` ${key}: ${getTypeScriptTypeReference(value)};`;
45+
mapObject(this.fields, (key, field) => {
46+
return ` ${key}${field.stringifyDeclarationBody()};`;
3947
}).join('\n') +
4048
'\n}'
4149
);

src/pages/jsonToTypeScriptPage/utils/getAllTypeScriptTypeDeclarableTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const getAllTypeScriptTypeDeclarableTypes = (type: TypeScriptType): IDeclarableT
2222
}
2323

2424
return [
25-
...mapObject(type.fields, (fieldName, fieldType) => getAllTypeScriptTypeDeclarableTypes(fieldType)).flatMap(
25+
...mapObject(type.fields, (fieldName, field) => getAllTypeScriptTypeDeclarableTypes(field.type)).flatMap(
2626
(value) => value
2727
),
2828
type

src/pages/jsonToTypeScriptPage/utils/getTypeScriptType.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import { JsonArray, JsonType } from '../types/json';
22
import {
33
TypeScriptArray,
44
TypeScriptInterface,
5+
TypeScriptObjectField,
56
TypeScriptType,
67
TypeScriptUnion,
78
TypeScriptUnknown
89
} from '../types/typescript';
910
import { camelCase, isString, mapValues } from 'lodash';
1011
import { singular } from 'pluralize';
1112
import capitalizeFirst from '../../../utils/capitalizeFirst';
13+
import getMergedTypeScriptTypes from './mergeTypeScriptTypesList';
1214

1315
const getTypeScriptType = (name: string, jsonObject: JsonType): TypeScriptType => {
1416
// const parsedJson = parseJsonType(object);
@@ -25,24 +27,22 @@ const getTypeScriptType = (name: string, jsonObject: JsonType): TypeScriptType =
2527
}
2628

2729
if (jsonObject instanceof JsonArray) {
28-
if (jsonObject.types.length === 0) {
30+
const typeScriptTypes = jsonObject.types.map((type) => getTypeScriptType(arrayElementName, type));
31+
const mergedTypeScriptTypes = getMergedTypeScriptTypes(typeScriptTypes);
32+
33+
if (mergedTypeScriptTypes.length === 0) {
2934
return new TypeScriptArray(new TypeScriptUnknown());
3035
}
31-
if (jsonObject.types.length === 1) {
32-
return new TypeScriptArray(getTypeScriptType(arrayElementName, jsonObject.types[0]));
36+
if (mergedTypeScriptTypes.length === 1) {
37+
return new TypeScriptArray(mergedTypeScriptTypes[0]);
3338
}
3439

35-
return new TypeScriptArray(
36-
new TypeScriptUnion(
37-
arrayElementName,
38-
jsonObject.types.map((type) => getTypeScriptType(arrayElementName, type))
39-
)
40-
);
40+
return new TypeScriptArray(new TypeScriptUnion(arrayElementName, mergedTypeScriptTypes));
4141
}
4242

4343
return new TypeScriptInterface(
4444
typeName,
45-
mapValues(jsonObject.fields, (field, name) => getTypeScriptType(name, field))
45+
mapValues(jsonObject.fields, (field, name) => new TypeScriptObjectField(getTypeScriptType(name, field)))
4646
);
4747
};
4848

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { chain, isString } from 'lodash';
2+
import { TypeScriptInterface, TypeScriptObjectField, TypeScriptType, TypeScriptUnion } from '../types/typescript';
3+
4+
const mergeTypeScriptTypes = (a: TypeScriptType, b: TypeScriptType): TypeScriptType[] => {
5+
const singleType = [a];
6+
const bothTypes = [a, b];
7+
8+
if (a.constructor !== b.constructor) {
9+
return bothTypes;
10+
}
11+
12+
if (isString(a) && isString(b)) {
13+
return a === b ? singleType : bothTypes;
14+
}
15+
16+
if (a instanceof TypeScriptInterface && b instanceof TypeScriptInterface) {
17+
const aKeys = Object.keys(a.fields);
18+
const bKeys = Object.keys(b.fields);
19+
20+
const allKeys = chain([aKeys, bKeys]).flatMap().uniq().value();
21+
22+
const mergedFields = chain(allKeys)
23+
.reduce((result, fieldKey) => {
24+
const aHasKey = aKeys.includes(fieldKey);
25+
const bHasKey = bKeys.includes(fieldKey);
26+
27+
const aField = a.fields[fieldKey];
28+
const bField = b.fields[fieldKey];
29+
30+
if (!aHasKey && !bHasKey) {
31+
console.error('Both keys are undefined');
32+
return result;
33+
}
34+
35+
if (aHasKey && !bHasKey) {
36+
result[fieldKey] = new TypeScriptObjectField(aField.type, true);
37+
return result;
38+
}
39+
40+
if (!aHasKey && bHasKey) {
41+
result[fieldKey] = new TypeScriptObjectField(bField.type, true);
42+
return result;
43+
}
44+
45+
//has both keys
46+
47+
const mergedFieldTypes = mergeTypeScriptTypes(aField.type, bField.type);
48+
if (mergedFieldTypes.length === 0) {
49+
return result;
50+
}
51+
if (mergedFieldTypes.length === 1) {
52+
result[fieldKey] = new TypeScriptObjectField(mergedFieldTypes[0], false);
53+
}
54+
55+
result[fieldKey] = new TypeScriptObjectField(new TypeScriptUnion(fieldKey, mergedFieldTypes));
56+
return result;
57+
}, {} as Record<string, TypeScriptObjectField>)
58+
.value();
59+
60+
return [new TypeScriptInterface(a.name, mergedFields)];
61+
}
62+
63+
return bothTypes; //TODO
64+
};
65+
66+
export default mergeTypeScriptTypes;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { TypeScriptType } from '../types/typescript';
2+
import mergeTypeScriptTypes from './mergeTypeScriptTypes';
3+
import { last } from 'lodash';
4+
5+
//TODO
6+
const getMergedTypeScriptTypes = (types: TypeScriptType[]): TypeScriptType[] => {
7+
return types.reduce((result, value) => {
8+
const previous = last(result);
9+
10+
if (previous === undefined) {
11+
return [value];
12+
}
13+
14+
return mergeTypeScriptTypes(previous, value);
15+
}, [] as TypeScriptType[]);
16+
};
17+
18+
export default getMergedTypeScriptTypes;

src/pages/jsonToTypeScriptPage/utils/parseJsonObject.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { chain, isArray, isBoolean, isNil, isObject, isString, mapValues } from
44
const parseJsonObject = (object: unknown): JsonType => {
55
if (isObject(object)) {
66
if (isArray(object)) {
7-
return new JsonArray(chain(object).map(parseJsonObject).uniq().value());
7+
return new JsonArray(chain(object).map(parseJsonObject).uniq().value()); //TODO remove `uniq` here?
88
}
99

1010
return new JsonObject(

0 commit comments

Comments
 (0)