From b4d9913d996bb37cdc575c8efb00b37f36191a62 Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Fri, 22 Apr 2022 04:10:18 +0900 Subject: [PATCH] Close #20 and Close #21 --- README.md | 2 +- package.json | 2 +- src/factories/ExpressionFactory.ts | 49 ++++ src/factories/FunctionFactory.ts | 22 +- src/factories/JsonFactory.ts | 138 --------- src/factories/MetadataFactory.ts | 240 ++++++++++++++++ src/factories/SchemaFactory.ts | 268 ++++-------------- src/structures/IMetadata.ts | 23 ++ src/structures/ISchema.ts | 22 -- src/structures/TypeEntry.ts | 26 ++ .../test_stringify_object_implicit.ts | 4 +- .../test_stringify_object_optional.ts | 20 ++ tsconfig.json | 16 +- 13 files changed, 445 insertions(+), 387 deletions(-) create mode 100644 src/factories/ExpressionFactory.ts delete mode 100644 src/factories/JsonFactory.ts create mode 100644 src/factories/MetadataFactory.ts create mode 100644 src/structures/IMetadata.ts delete mode 100644 src/structures/ISchema.ts create mode 100644 src/structures/TypeEntry.ts create mode 100644 test/features/test_stringify_object_optional.ts diff --git a/README.md b/README.md index 8472ee3d1e..71b61076a6 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ interface ICompany interface IEmployee { name: string; - gendor: string | number | null; + gender: string | number | null; } const company: ICompany; diff --git a/package.json b/package.json index bdef083c60..908e558322 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typescript-json", - "version": "2.0.2", + "version": "2.0.3", "description": "Faster JSON stringify with only one line", "main": "lib/index.js", "typings": "lib/index.d.ts", diff --git a/src/factories/ExpressionFactory.ts b/src/factories/ExpressionFactory.ts new file mode 100644 index 0000000000..80f4cfafff --- /dev/null +++ b/src/factories/ExpressionFactory.ts @@ -0,0 +1,49 @@ +import ts from "typescript"; + +export namespace ExpressionFactory +{ + export function generate(input: any) + { + if (input instanceof Array) + return generate_array(input); + else if (typeof input === "object") + return generate_object(input); + else if (typeof input === "boolean") + return generate_boolean(input); + else if (typeof input === "string") + return generate_string(input); + else + throw new Error("Unknown type."); + } + + function generate_object(obj: object): ts.ObjectLiteralExpression + { + const properties = Object + .entries(obj) + .map(([key, value]) => ts.factory.createPropertyAssignment + ( + key, + generate(value) + )); + return ts.factory.createObjectLiteralExpression(properties, true); + } + + function generate_array(array: any[]): ts.ArrayLiteralExpression + { + return ts.factory.createArrayLiteralExpression + ( + array.map(generate), + true + ); + } + + function generate_boolean(value: boolean): ts.Identifier + { + return ts.factory.createIdentifier(value.toString()); + } + + function generate_string(value: string): ts.StringLiteral + { + return ts.factory.createStringLiteral(value); + } +} \ No newline at end of file diff --git a/src/factories/FunctionFactory.ts b/src/factories/FunctionFactory.ts index 5d8c96eeb1..1df6bca78c 100644 --- a/src/factories/FunctionFactory.ts +++ b/src/factories/FunctionFactory.ts @@ -2,10 +2,11 @@ import crypto from "crypto"; import path from "path"; import ts from "typescript"; -import { JsonFactory } from "../factories/JsonFactory"; import { SchemaFactory } from "../factories/SchemaFactory"; +import { IMetadata } from "../structures/IMetadata"; import { IProject } from "../structures/IProject"; -import { ISchema } from "../structures/ISchema"; +import { ExpressionFactory } from "./ExpressionFactory"; +import { MetadataFactory } from "./MetadataFactory"; export namespace FunctionFactory { @@ -61,12 +62,14 @@ export namespace FunctionFactory type = project.checker.getTypeAtLocation(top); } - const app: ISchema.IApplication | null = SchemaFactory.generate(project.checker, type); - const tuple: ts.ArrayLiteralExpression = JsonFactory.application(app); + const app: IMetadata.IApplication | null = MetadataFactory.generate(project.checker, type); + const tuple = SchemaFactory.application(app); + const literal = ExpressionFactory.generate(tuple); + const script: string = project.printer.printNode ( ts.EmitHint.Unspecified, - tuple, + literal, expression.getSourceFile() ); const key: string = crypto @@ -84,7 +87,7 @@ export namespace FunctionFactory [], undefined, undefined, - tuple + literal ) ]); } @@ -104,8 +107,9 @@ export namespace FunctionFactory throw new Error(`Error on TSON.createStringifier(): the generic argument must be specified - ${file.fileName}:${line + 1}:${character + 1}.`); } - const app: ISchema.IApplication | null = SchemaFactory.generate(project.checker, type); - const tuple: ts.ArrayLiteralExpression = JsonFactory.application(app); + const app: IMetadata.IApplication | null = MetadataFactory.generate(project.checker, type); + const tuple = SchemaFactory.application(app); + const literal = ExpressionFactory.generate(tuple); return ts.factory.createArrowFunction ( @@ -114,7 +118,7 @@ export namespace FunctionFactory [], undefined, undefined, - tuple + literal ); } diff --git a/src/factories/JsonFactory.ts b/src/factories/JsonFactory.ts deleted file mode 100644 index e808a1d616..0000000000 --- a/src/factories/JsonFactory.ts +++ /dev/null @@ -1,138 +0,0 @@ -import ts from "typescript"; -import { Singleton } from "tstl/thread/Singleton"; - -import { ISchema } from "../structures/ISchema"; - -export namespace JsonFactory -{ - export function application(app: ISchema.IApplication | null): ts.ArrayLiteralExpression - { - return ts.factory.createArrayLiteralExpression - ( - [ - schema(app), - external(app?.storage || null) - ], - true - ) - } - - /* ----------------------------------------------------------- - STORAGE - ----------------------------------------------------------- */ - export function external(storage: ISchema.IStorage | null): ts.ObjectLiteralExpression - { - const properties: ts.PropertyAssignment[] = storage ? Object - .entries(storage) - .map(([key, object]) => ts.factory.createPropertyAssignment - ( - key, - generate_object(object) - )) : []; - const external: ts.PropertyAssignment = ts.factory.createPropertyAssignment - ( - "external", - ts.factory.createObjectLiteralExpression(properties, true) - ); - return ts.factory.createObjectLiteralExpression([ external ], true); - } - - function generate_object(object: ISchema.IObject): ts.ObjectLiteralExpression - { - const properties: ts.PropertyAssignment[] = Object - .entries(object.properties) - .map(([key, value]) => ts.factory.createPropertyAssignment - ( - key, - schema(value) - )); - const members: ts.PropertyAssignment[] = [ - ts.factory.createPropertyAssignment("type", - ts.factory.createStringLiteral("object") - ), - ts.factory.createPropertyAssignment("properties", - ts.factory.createObjectLiteralExpression(properties, true) - ), - ts.factory.createPropertyAssignment("nullable", - ts.factory.createIdentifier(object.nullable.toString()) - ) - ]; - return ts.factory.createObjectLiteralExpression(members, true) - } - - /* ----------------------------------------------------------- - SCHEMA - ----------------------------------------------------------- */ - export function schema(elem: ISchema | null): ts.ObjectLiteralExpression - { - if (elem === null) - return empty.get(); - - // GATHER UNION TYPES - const unions: ts.ObjectLiteralExpression[] = []; - for (const type of elem.atomics) - unions.push(generate_atomic(type, elem.nullable)); - for (const address of elem.objects.values()) - unions.push(generate_pointer(address)); - for (const schema of elem.arraies.values()) - unions.push(generate_array(schema, elem.nullable)); - - // NO MULTIPLE UNION TYPES - if (unions.length === 0) - return empty.get(); - else if (unions.length === 1) - return unions[0]!; - - // MULTIPLE UNION TYPES - const assignment: ts.PropertyAssignment = ts.factory.createPropertyAssignment - ( - "anyOf", - ts.factory.createArrayLiteralExpression(unions, true) - ); - return ts.factory.createObjectLiteralExpression([ assignment ], true); - } - - function generate_atomic - ( - type: string, - nullable: boolean - ): ts.ObjectLiteralExpression - { - const parameters: ts.PropertyAssignment[] = [ - ts.factory.createPropertyAssignment("type", - ts.factory.createStringLiteral(type) - ), - ts.factory.createPropertyAssignment("nullable", - ts.factory.createIdentifier(nullable.toString()) - ) - ]; - return ts.factory.createObjectLiteralExpression(parameters, true); - } - - function generate_pointer(address: string): ts.ObjectLiteralExpression - { - const members: ts.PropertyAssignment[] = [ - ts.factory.createPropertyAssignment("$ref", - ts.factory.createStringLiteral(address) - ) - ]; - return ts.factory.createObjectLiteralExpression(members, true); - } - - function generate_array(elem: ISchema | null, nullable: boolean) - { - const members: ts.PropertyAssignment[] = [ - ts.factory.createPropertyAssignment("type", - ts.factory.createStringLiteral("array") - ), - ts.factory.createPropertyAssignment("items", - schema(elem) - ), - ts.factory.createPropertyAssignment("nullable", - ts.factory.createIdentifier(nullable.toString()) - ) - ]; - return ts.factory.createObjectLiteralExpression(members, true); - } -} -const empty = new Singleton(() => ts.factory.createObjectLiteralExpression([], true)); \ No newline at end of file diff --git a/src/factories/MetadataFactory.ts b/src/factories/MetadataFactory.ts new file mode 100644 index 0000000000..93b5ff35f8 --- /dev/null +++ b/src/factories/MetadataFactory.ts @@ -0,0 +1,240 @@ +import crypto from "crypto"; +import ts from "typescript"; +import { HashMap } from "tstl/container/HashMap"; +import { Pair } from "tstl/utility/Pair"; +import { Singleton } from "tstl/thread/Singleton"; + +import { IMetadata } from "../structures/IMetadata"; +import { TypeFactory } from "./TypeFactry"; + +export namespace MetadataFactory +{ + export function generate + ( + checker: ts.TypeChecker, + type: ts.Type | null + ): IMetadata.IApplication | null + { + // CONSTRUCT SCHEMA WITH OBJECTS + const dict: HashMap, [string, IMetadata.IObject]> = new HashMap(); + const entity: IMetadata | null = explore(checker, dict, type); + + // SERIALIZE STORAGE + const storage: IMetadata.IStorage = {}; + for (const it of dict) + storage[it.second[0]] = it.second[1]; + + // RETURNS + return entity + ? { ...entity, storage } + : null; + } + + function explore + ( + checker: ts.TypeChecker, + dict: HashMap, [string, IMetadata.IObject]>, + type: ts.Type | null, + ): IMetadata | null + { + if (type === null) + return null; + + const schema: IMetadata = { + atomics: new Set(), + arraies: new Map(), + objects: new Set(), + nullable: false, + required: true, + }; + if (iterate(checker, dict, schema, type) === false) + return null; + return schema; + } + + function iterate + ( + checker: ts.TypeChecker, + dict: HashMap, [string, IMetadata.IObject]>, + schema: IMetadata, + type: ts.Type, + ): boolean + { + type = TypeFactory.escape(checker, type); + if (type.isUnion()) + return type.types.every(t => iterate(checker, dict, schema, t)); + + const node: ts.TypeNode | undefined = checker.typeToTypeNode(type, undefined, undefined); + if (!node) + return false; + + const filter = (flag: ts.TypeFlags) => (type.getFlags() & flag) !== 0; + const check = (flag: ts.TypeFlags, literal: ts.TypeFlags, className: string) => + { + if (filter(flag) || filter(literal) || type.symbol?.escapedName === className) + { + schema.atomics.add(className.toLowerCase()); + return true; + } + return false + }; + + // UNKNOWN OR NULL + if (filter(ts.TypeFlags.Unknown) || filter(ts.TypeFlags.Never) || filter(ts.TypeFlags.Any)) + return false; + else if (filter(ts.TypeFlags.Null)) + return schema.nullable = true; + else if (filter(ts.TypeFlags.Undefined)) + return !(schema.required = false); + + // ATOMIC VALUE TYPES + for (const [flag, literal, className] of ATOMICS.get()) + if (check(flag, literal, className) === true) + return true; + + // WHEN ARRAY + if (ts.isArrayTypeNode(node)) + { + const elemType: ts.Type | null = checker.getTypeArguments(type as ts.TypeReference)[0] || null; + const elemSchema: IMetadata | null = explore(checker, dict, elemType); + + const key: string = get_uid(elemSchema); + schema.arraies.set(key, elemSchema); + } + + // WHEN TUPLE + else if (ts.isTupleTypeNode(node)) + { + if (node.elements.length === 0) + return false; + + const elemSchema: IMetadata | null = explore + ( + checker, + dict, + checker.getTypeFromTypeNode(node.elements[0]!) + ); + if (elemSchema === null) + return false; + + for (const elem of node.elements.slice(1)) + if (iterate(checker, dict, elemSchema, checker.getTypeFromTypeNode(elem)) === false) + return false; + } + + // WHEN OBJECT, MAYBE + else if (type.isClassOrInterface() || ts.isTypeLiteralNode(node) || type.isIntersection()) + { + if (type.isIntersection()) + { + const fakeDict: HashMap, [string, IMetadata.IObject]> = new HashMap(); + const fakeSchema: IMetadata = { + atomics: new Set(), + arraies: new Map(), + objects: new Set(), + nullable: false, + required: true, + }; + if (type.types.every(t => iterate(checker, fakeDict, fakeSchema, t)) === false) + return false; + else if (fakeSchema.atomics.size || fakeSchema.arraies.size || !fakeSchema.objects.size) + return false; + } + + const key: string = emplace(checker, dict, type, schema.nullable); + schema.objects.add(`external#/${key}`); + } + return true; + } + + function emplace + ( + checker: ts.TypeChecker, + dict: HashMap, [string, IMetadata.IObject]>, + type: ts.Type, + nullable: boolean + ): string + { + // CHECK MEMORY + const key: Pair = new Pair(type, nullable); + const it: HashMap.Iterator, [string, IMetadata.IObject]> = dict.find(key); + if (it.equals(dict.end()) === false) + return it.second[0]; + + // PRE-ENROLLMENT FOR THE RECURSIVE STRUCTURE + const id: string = `o${++sequence}`; + const object: IMetadata.IObject = { + properties: {}, + nullable + }; + dict.set(key, [id, object]); + + // PREPARE ASSETS + const isClass: boolean = type.isClass(); + const pred: (node: ts.Declaration) => boolean = isClass + ? node => (ts.isParameter(node) || ts.isPropertyDeclaration(node)) + : node => (ts.isPropertySignature(node) || ts.isTypeLiteralNode(node)); + + for (const prop of type.getProperties()) + { + // CHECK NODE IS A FORMAL PROPERTY + const node: ts.PropertyDeclaration | undefined = (prop.getDeclarations() || [])[0] as ts.PropertyDeclaration | undefined; + if (!node || !pred(node)) + continue; + else if (node.getChildren().some(child => TypeFactory.is_function(child))) + continue; + + // CHECK NOT PRIVATE OR PROTECTED MEMBER + if (isClass) + { + const kind = node.getChildren()[0]?.getChildren()[0]?.kind; + if (kind === ts.SyntaxKind.PrivateKeyword || kind === ts.SyntaxKind.ProtectedKeyword) + continue; + } + + // DETERMINE PROPERTY TYPE BY ADDITIONAL EXPLORATION + const key: string = node.name.getText(); + const type: ts.Type | null = node.type + ? checker.getTypeFromTypeNode(node.type) + : null; + + const child = type ? explore(checker, dict, type) : null; + if (child && node.questionToken) + child.required = false; + + object.properties[key] = child; + } + return id; + } + + function get_uid(schema: IMetadata | null): string + { + if (schema === null) + return "null"; + + return crypto.createHash("sha256") + .update(JSON.stringify(to_primitive(schema))) + .digest("base64"); + } + + function to_primitive(schema: IMetadata | null): any + { + if (schema === null) + return null; + return { + atomics: Array.from(schema.atomics), + arraies: [...schema.arraies].map(([key, value]) => [key, to_primitive(value)]), + objects: Array.from(schema.objects), + nullable: schema.nullable + }; + } +} + +const ATOMICS: Singleton<[ts.TypeFlags, ts.TypeFlags, string][]> = new Singleton(() => [ + [ts.TypeFlags.BooleanLike, ts.TypeFlags.BooleanLiteral, "Boolean"], + [ts.TypeFlags.NumberLike, ts.TypeFlags.NumberLiteral, "Number"], + [ts.TypeFlags.BigIntLike, ts.TypeFlags.BigIntLiteral, "BigInt"], + [ts.TypeFlags.StringLike, ts.TypeFlags.StringLiteral, "String"] +]); + +let sequence: number = 0; \ No newline at end of file diff --git a/src/factories/SchemaFactory.ts b/src/factories/SchemaFactory.ts index 7217af854c..7398ee3952 100644 --- a/src/factories/SchemaFactory.ts +++ b/src/factories/SchemaFactory.ts @@ -1,234 +1,90 @@ -import crypto from "crypto"; -import ts from "typescript"; -import { HashMap } from "tstl/container/HashMap"; -import { Pair } from "tstl/utility/Pair"; -import { Singleton } from "tstl/thread/Singleton"; - -import { ISchema } from "../structures/ISchema"; -import { TypeFactory } from "./TypeFactry"; +import { IMetadata } from "../structures/IMetadata"; export namespace SchemaFactory { - export function generate - ( - checker: ts.TypeChecker, - type: ts.Type | null - ): ISchema.IApplication | null + export function application(app: IMetadata.IApplication | null) { - // CONSTRUCT SCHEMA WITH OBJECTS - const dict: HashMap, [string, ISchema.IObject]> = new HashMap(); - const entity: ISchema | null = explore(checker, dict, type); - - // SERIALIZE STORAGE - const storage: ISchema.IStorage = {}; - for (const it of dict) - storage[it.second[0]] = it.second[1]; - - // RETURNS - return entity - ? { ...entity, storage } - : null; + return [schema(app), external(app?.storage || null)]; } - function explore - ( - checker: ts.TypeChecker, - dict: HashMap, [string, ISchema.IObject]>, - type: ts.Type | null, - ): ISchema | null + /* ----------------------------------------------------------- + SCHEMA + ----------------------------------------------------------- */ + export function schema(meta: IMetadata | null): any { - if (type === null) - return null; + if (meta === null) + return {}; + + const unions = []; + for (const type of meta.atomics) + unions.push(generate_atomic(type, meta.nullable)); + for (const address of meta.objects.values()) + unions.push(generate_pointer(address)); + for (const schema of meta.arraies.values()) + unions.push(generate_array(schema, meta.nullable)); + + if (unions.length === 0) + return {}; + else if (unions.length === 1) + return unions[0]; + else + return { + anyOf: unions + }; + } - const schema: ISchema = { - atomics: new Set(), - arraies: new Map(), - objects: new Set(), - nullable: false + function generate_atomic(type: string, nullable: boolean) + { + return { + type, + nullable, }; - if (iterate(checker, dict, schema, type) === false) - return null; - return schema; } - function iterate - ( - checker: ts.TypeChecker, - dict: HashMap, [string, ISchema.IObject]>, - schema: ISchema, - type: ts.Type - ): boolean + function generate_pointer(address: string) { - type = TypeFactory.escape(checker, type); - if (type.isUnion()) - return type.types.every(t => iterate(checker, dict, schema, t)); - - const node: ts.TypeNode | undefined = checker.typeToTypeNode(type, undefined, undefined); - if (!node) - return false; - - const filter = (flag: ts.TypeFlags) => (type.flags & flag) !== 0; - const check = (flag: ts.TypeFlags, literal: ts.TypeFlags, className: string) => - { - if (filter(flag) || filter(literal) || type.symbol?.escapedName === className) - { - schema.atomics.add(className.toLowerCase()); - return true; - } - return false + return { + $ref: address }; - - // UNKNOWN OR NULL - if (filter(ts.TypeFlags.Unknown) || filter(ts.TypeFlags.Never) || filter(ts.TypeFlags.Any)) - return false; - else if (filter(ts.TypeFlags.Null)) - return schema.nullable = true; - - // ATOMIC VALUE TYPES - for (const [flag, literal, className] of ATOMICS.get()) - if (check(flag, literal, className) === true) - return true; - - // WHEN ARRAY - if (ts.isArrayTypeNode(node)) - { - const elemType: ts.Type | null = checker.getTypeArguments(type as ts.TypeReference)[0] || null; - const elemSchema: ISchema | null = explore(checker, dict, elemType); - - const key: string = get_uid(elemSchema); - schema.arraies.set(key, elemSchema); - } - - // WHEN TUPLE - else if (ts.isTupleTypeNode(node)) - { - if (node.elements.length === 0) - return false; - - const elemSchema: ISchema | null = explore - ( - checker, - dict, - checker.getTypeFromTypeNode(node.elements[0]!) - ); - if (elemSchema === null) - return false; - - for (const elem of node.elements.slice(1)) - if (iterate(checker, dict, elemSchema, checker.getTypeFromTypeNode(elem)) === false) - return false; - } - - // WHEN OBJECT, MAYBE - else if (type.isClassOrInterface() || ts.isTypeLiteralNode(node) || type.isIntersection()) - { - if (type.isIntersection()) - { - const fakeDict: HashMap, [string, ISchema.IObject]> = new HashMap(); - const fakeSchema: ISchema = { - atomics: new Set(), - arraies: new Map(), - objects: new Set(), - nullable: false - }; - if (type.types.every(t => iterate(checker, fakeDict, fakeSchema, t)) === false) - return false; - else if (fakeSchema.atomics.size || fakeSchema.arraies.size || !fakeSchema.objects.size) - return false; - } - - const key: string = emplace(checker, dict, type, schema.nullable); - schema.objects.add(`external#/${key}`); - } - return true; } - function emplace - ( - checker: ts.TypeChecker, - dict: HashMap, [string, ISchema.IObject]>, - type: ts.Type, - nullable: boolean - ): string + function generate_array(metadata: IMetadata | null, nullable: boolean) { - // CHECK MEMORY - const key: Pair = new Pair(type, nullable); - const it: HashMap.Iterator, [string, ISchema.IObject]> = dict.find(key); - if (it.equals(dict.end()) === false) - return it.second[0]; - - // PRE-ENROLLMENT FOR THE RECURSIVE STRUCTURE - const id: string = `o${++sequence}`; - const object: ISchema.IObject = { - properties: {}, + return { + type: "array", + items: schema(metadata), nullable }; - dict.set(key, [id, object]); - - // PREPARE ASSETS - const isClass: boolean = type.isClass(); - const pred: (node: ts.Declaration) => boolean = isClass - ? node => (ts.isParameter(node) || ts.isPropertyDeclaration(node)) - : node => (ts.isPropertySignature(node) || ts.isTypeLiteralNode(node)); - - for (const prop of type.getProperties()) - { - // CHECK NODE IS A FORMAL PROPERTY - const node: ts.PropertyDeclaration | undefined = (prop.getDeclarations() || [])[0] as ts.PropertyDeclaration | undefined; - if (!node || !pred(node)) - continue; - else if (node.getChildren().some(child => TypeFactory.is_function(child))) - continue; - - // CHECK NOT PRIVATE OR PROTECTED MEMBER - if (isClass) - { - const kind = node.getChildren()[0]?.getChildren()[0]?.kind; - if (kind === ts.SyntaxKind.PrivateKeyword || kind === ts.SyntaxKind.ProtectedKeyword) - continue; - } - - // DETERMINE PROPERTY TYPE BY ADDITIONAL EXPLORATION - const key: string = node.name.getText(); - const type: ts.Type | null = node.type - ? checker.getTypeFromTypeNode(node.type) - : null; - - object.properties[key] = type - ? explore(checker, dict, type) - : null; - } - return id; } - function get_uid(schema: ISchema | null): string + /* ----------------------------------------------------------- + STORAGE + ----------------------------------------------------------- */ + export function external(storage: IMetadata.IStorage | null) { - if (schema === null) - return "null"; + const external: Record = {}; + for (const [key, value] of Object.entries(storage || [])) + external[key] = generate_object(value); - return crypto.createHash("sha256") - .update(JSON.stringify(to_primitive(schema))) - .digest("base64"); + return { external }; } - function to_primitive(schema: ISchema | null): any + function generate_object(obj: IMetadata.IObject) { - if (schema === null) - return null; + const properties: Record = {}; + const required: string[] = []; + + for (const [key, value] of Object.entries(obj.properties || [])) + { + properties[key] = schema(value); + if (value?.required === true) + required.push(key); + } return { - atomics: Array.from(schema.atomics), - arraies: [...schema.arraies].map(([key, value]) => [key, to_primitive(value)]), - objects: Array.from(schema.objects), - nullable: schema.nullable + type: "object", + properties, + nullable: obj.nullable, + required }; } -} - -const ATOMICS: Singleton<[ts.TypeFlags, ts.TypeFlags, string][]> = new Singleton(() => [ - [ts.TypeFlags.BooleanLike, ts.TypeFlags.BooleanLike, "Boolean"], - [ts.TypeFlags.NumberLike, ts.TypeFlags.NumberLiteral, "Number"], - [ts.TypeFlags.BigIntLike, ts.TypeFlags.BigIntLiteral, "BigInt"], - [ts.TypeFlags.StringLike, ts.TypeFlags.StringLiteral, "String"] -]); - -let sequence: number = 0; \ No newline at end of file +} \ No newline at end of file diff --git a/src/structures/IMetadata.ts b/src/structures/IMetadata.ts new file mode 100644 index 0000000000..6151aa2cad --- /dev/null +++ b/src/structures/IMetadata.ts @@ -0,0 +1,23 @@ +export interface IMetadata +{ + atomics: Set; + arraies: Map; + objects: Set; + nullable: boolean; + required: boolean; +} +export namespace IMetadata +{ + export interface IObject + { + properties: Record; + nullable: boolean; + } + + export interface IApplication + extends IMetadata + { + storage: IStorage; + } + export type IStorage = Record; +} \ No newline at end of file diff --git a/src/structures/ISchema.ts b/src/structures/ISchema.ts deleted file mode 100644 index 76ca86af13..0000000000 --- a/src/structures/ISchema.ts +++ /dev/null @@ -1,22 +0,0 @@ -export interface ISchema -{ - atomics: Set; - arraies: Map; - objects: Set; - nullable: boolean; -} -export namespace ISchema -{ - export interface IObject - { - properties: Record; - nullable: boolean; - } - - export interface IApplication - extends ISchema - { - storage: IStorage; - } - export type IStorage = Record; -} \ No newline at end of file diff --git a/src/structures/TypeEntry.ts b/src/structures/TypeEntry.ts new file mode 100644 index 0000000000..bcadc0e117 --- /dev/null +++ b/src/structures/TypeEntry.ts @@ -0,0 +1,26 @@ +import ts from "typescript"; +import { hash } from "tstl/functional/hash"; + +export class TypeEntry +{ + public constructor + ( + public readonly type: ts.Type, + public readonly nullable: boolean, + public readonly required: boolean, + ) + { + } + + public equals(obj: TypeEntry): boolean + { + return this.type === obj.type + && this.nullable === obj.nullable + && this.required === obj.required; + } + + public hashCode(): number + { + return hash(this.type, this.nullable, this.required); + } +} \ No newline at end of file diff --git a/test/features/test_stringify_object_implicit.ts b/test/features/test_stringify_object_implicit.ts index 391fc865d7..b44591e705 100644 --- a/test/features/test_stringify_object_implicit.ts +++ b/test/features/test_stringify_object_implicit.ts @@ -18,6 +18,6 @@ export function test_stringify_object_implicit(): void interface ILecture { name: string; - professor: string; - grade: number; + professor?: string | number; + grade: number | undefined; } \ No newline at end of file diff --git a/test/features/test_stringify_object_optional.ts b/test/features/test_stringify_object_optional.ts new file mode 100644 index 0000000000..ed116cb24b --- /dev/null +++ b/test/features/test_stringify_object_optional.ts @@ -0,0 +1,20 @@ +import TSON from "../../src"; + +export function test_stringify_object_optional(): void +{ + TSON.createStringifier(); +} + +interface IParent extends IChild +{ + first: IChild; + second: IChild | null; + third: IChild | null | undefined; + fourth?: IChild | null; + fifth: IChild | undefined; + seventh?: IChild; +} +interface IChild +{ + name: string; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 387c1c20a6..b984191862 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -78,14 +78,14 @@ /* Type Checking */ "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ - // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ - // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */