From 19e2ddac988f95ab284dca8463224b85ca3e6acf Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Mon, 2 May 2022 02:49:28 +0900 Subject: [PATCH] Fix #32 --- package.json | 2 +- src/factories/FunctionFactory.ts | 2 +- src/factories/MetadataFactory.ts | 54 ++++++----- .../test_stringify_array_generic_alias.ts | 13 +++ .../features/test_stringify_object_generic.ts | 30 ++++++ .../test_stringify_object_generic_alias.ts | 22 +++++ .../test_stringify_object_generic_union.ts | 94 +++++++++++++++++++ test/manual.js | 2 +- 8 files changed, 193 insertions(+), 26 deletions(-) create mode 100644 test/features/test_stringify_array_generic_alias.ts create mode 100644 test/features/test_stringify_object_generic.ts create mode 100644 test/features/test_stringify_object_generic_alias.ts create mode 100644 test/features/test_stringify_object_generic_union.ts diff --git a/package.json b/package.json index b895dceec7..b1f67e524c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typescript-json", - "version": "2.0.5", + "version": "2.0.7", "description": "Faster JSON stringify with only one line", "main": "lib/index.js", "typings": "lib/index.d.ts", diff --git a/src/factories/FunctionFactory.ts b/src/factories/FunctionFactory.ts index 1df6bca78c..194be5b945 100644 --- a/src/factories/FunctionFactory.ts +++ b/src/factories/FunctionFactory.ts @@ -61,7 +61,7 @@ export namespace FunctionFactory const top: ts.Expression = expression.arguments[0]! type = project.checker.getTypeAtLocation(top); } - + const app: IMetadata.IApplication | null = MetadataFactory.generate(project.checker, type); const tuple = SchemaFactory.application(app); const literal = ExpressionFactory.generate(tuple); diff --git a/src/factories/MetadataFactory.ts b/src/factories/MetadataFactory.ts index bcf101b892..1fc02c5aa2 100644 --- a/src/factories/MetadataFactory.ts +++ b/src/factories/MetadataFactory.ts @@ -58,13 +58,18 @@ export namespace MetadataFactory ): IMetadata.IApplication | null { // CONSTRUCT SCHEMA WITH OBJECTS - const metadata: IMetadata | null = explore(collection, checker, type); + const metadata: IMetadata | null = explore + ( + collection, + checker, + type + ); if (metadata === null) return null; // RETURNS WITH STORAGE const storage: IMetadata.IStorage = collection.storage(); - return { metadata, storage } + return { metadata, storage }; } function explore @@ -102,14 +107,14 @@ export namespace MetadataFactory if (partialEscaped === true) type = converted; - const escaped: boolean = partialEscaped || parentEscaped;; + const escaped: boolean = partialEscaped || parentEscaped; if (type.isUnion()) return type.types.every(t => iterate(collection, checker, schema, t, escaped)); 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) => { @@ -121,19 +126,19 @@ export namespace MetadataFactory return false }; - // UNKNOWN OR NULL + // UNKNOWN, NULL OR UNDEFINED if (filter(ts.TypeFlags.Unknown) || filter(ts.TypeFlags.Never) || filter(ts.TypeFlags.Any)) return false; else if (filter(ts.TypeFlags.Null)) return escaped ? false : schema.nullable = true; - else if (filter(ts.TypeFlags.Undefined)) + else if (filter(ts.TypeFlags.Undefined) || filter(ts.TypeFlags.Void) || filter(ts.TypeFlags.VoidLike)) return escaped ? false : !(schema.required = false); // ATOMIC VALUE TYPES for (const [flag, literal, className] of ATOMICS.get()) if (check(flag, literal, className) === true) return escaped ? false : true; - + // WHEN ARRAY if (ts.isArrayTypeNode(node)) { @@ -157,7 +162,7 @@ export namespace MetadataFactory ( collection, checker, - checker.getTypeFromTypeNode(node.elements[0]!) + checker.getTypeFromTypeNode(node.elements[0]!), ); if (elemSchema === null) return false; @@ -168,7 +173,7 @@ export namespace MetadataFactory } // WHEN OBJECT, MAYBE - else if (type.isClassOrInterface() || ts.isTypeLiteralNode(node) || type.isIntersection()) + else if (filter(ts.TypeFlags.Object)) { if (type.isIntersection()) { @@ -180,7 +185,6 @@ export namespace MetadataFactory nullable: false, required: true, }; - if (type.types.every(t => iterate(fakeCollection, checker, fakeSchema, t)) === false) return false; else if (fakeSchema.atomics.size || fakeSchema.arraies.size || !fakeSchema.objects.size) @@ -190,7 +194,7 @@ export namespace MetadataFactory const key: string = emplace ( collection, - checker, + checker, type, schema.nullable ); @@ -206,22 +210,22 @@ export namespace MetadataFactory ( collection: Collection, checker: ts.TypeChecker, - type: ts.Type, + parent: ts.Type, nullable: boolean ): string { // CHECK MEMORY - const [id, object] = collection.emplace(type, nullable); + const [id, object] = collection.emplace(parent, nullable); if (object === null) return id; - + // PREPARE ASSETS - const isClass: boolean = type.isClass(); + const isClass: boolean = parent.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()) + for (const prop of parent.getApparentProperties()) { // CHECK NODE IS A FORMAL PROPERTY const node: ts.PropertyDeclaration | undefined = (prop.getDeclarations() || [])[0] as ts.PropertyDeclaration | undefined; @@ -233,18 +237,22 @@ export namespace MetadataFactory // CHECK NOT PRIVATE OR PROTECTED MEMBER if (isClass) { - const kind = node.getChildren()[0]?.getChildren()[0]?.kind; + const kind: ts.SyntaxKind | undefined = node.getChildren()[0]?.getChildren()[0]?.kind; if (kind === ts.SyntaxKind.PrivateKeyword || kind === ts.SyntaxKind.ProtectedKeyword) continue; } - // DETERMINE PROPERTY TYPE BY ADDITIONAL EXPLORATION + // GET EXACT TYPE const key: string = node.name.getText(); - const type: ts.Type | null = node.type - ? checker.getTypeFromTypeNode(node.type) - : null; - - const child = type ? explore(collection, checker, type) : null; + const type: ts.Type = checker.getTypeOfSymbolAtLocation(prop, node); + + // CHILD METADATA BY ADDITIONAL EXPLORATION + const child = explore + ( + collection, + checker, + type, + ); if (child && node.questionToken) child.required = false; diff --git a/test/features/test_stringify_array_generic_alias.ts b/test/features/test_stringify_array_generic_alias.ts new file mode 100644 index 0000000000..cc378ccf49 --- /dev/null +++ b/test/features/test_stringify_array_generic_alias.ts @@ -0,0 +1,13 @@ +// import TSON from "../../src"; + +// export function test_stringify_array_generic_alias(): void +// { +// const alias: Alias = [1, 2, 3]; +// const json: string = TSON.stringify(alias); +// const expected: string = JSON.stringify(alias); + +// if (json !== expected) +// throw new Error("Bug on TSON.stringify(): failed to understand the generic array alias type."); +// } + +// type Alias = T[]; \ No newline at end of file diff --git a/test/features/test_stringify_object_generic.ts b/test/features/test_stringify_object_generic.ts new file mode 100644 index 0000000000..1e969ca48f --- /dev/null +++ b/test/features/test_stringify_object_generic.ts @@ -0,0 +1,30 @@ +import TSON from "../../src"; + +export function test_stringify_object_generic_alias(): void +{ + const something: ISomething = { + value: "value", + child: { + child_value: "child_value", + child_next: "child_next" + }, + elements: ["one", "two", "three"], + }; + const json: string = TSON.stringify>(something); + const expected: string = JSON.stringify(something); + + if (json !== expected) + throw new Error("Bug on TSON.stringify(): failed to understand the generic object type."); +} + +interface ISomething +{ + value: T; + child: IChild; + elements: T[]; +} +interface IChild +{ + child_value: U; + child_next: U; +} \ No newline at end of file diff --git a/test/features/test_stringify_object_generic_alias.ts b/test/features/test_stringify_object_generic_alias.ts new file mode 100644 index 0000000000..ed22b88ffb --- /dev/null +++ b/test/features/test_stringify_object_generic_alias.ts @@ -0,0 +1,22 @@ +import TSON from "../../src"; +import { Primitive } from "../internal/Primitive"; + +export function test_stringify_object_generic_alias(): void +{ + const alias: Alias = { + value: "Something" + }; + const stringify: (input: Alias) => string = TSON.createStringifier(); + + const json: string = stringify(alias); + const parsed: Alias = JSON.parse(json); + + if (Primitive.equal_to(alias, parsed) === false) + throw new Error("Bug on TSON.createStringifier(): failed to understand the object alias type."); +} + +interface ISomething +{ + value: T; +} +type Alias = ISomething; \ No newline at end of file diff --git a/test/features/test_stringify_object_generic_union.ts b/test/features/test_stringify_object_generic_union.ts new file mode 100644 index 0000000000..d9bca5b827 --- /dev/null +++ b/test/features/test_stringify_object_generic_union.ts @@ -0,0 +1,94 @@ +import { v4 } from "uuid"; + +import TSON from "../../src"; + +import { RandomGenerator } from "../internal/RandomGenerator"; + +export function test_stringify_object_generic_union(): void +{ + const question: ISaleQuestion = { + id: v4(), + writer: "robot", + contents: RandomGenerator.array(() => ({ + id: v4(), + title: RandomGenerator.string(), + body: RandomGenerator.string(), + files: RandomGenerator.array(() => ({ + id: v4(), + name: RandomGenerator.string(), + extension: RandomGenerator.string(), + url: RandomGenerator.string(), + })), + created_at: new Date().toString() + })), + answer: null, + created_at: new Date().toString(), + hit: 0, + }; + + const json: string = TSON.stringify(question); + const expected: string = JSON.stringify(question); + + if (json !== expected) + throw new Error("Bug on TSON.stringify(): failed to understand the generic union object type."); +} + +type ISaleEntireArticle = ISaleQuestion | ISaleReview; +type ISaleQuestion = ISaleInquiry; +namespace ISaleQuestion +{ + export type IContent = ISaleInquiry.IContent; +} +type ISaleReview = ISaleInquiry; +namespace ISaleReview +{ + export interface IContent extends ISaleInquiry.IContent + { + score: number; + } +} + +interface ISaleInquiry + extends ISaleArticle +{ + writer: string; + answer: ISaleAnswer | null; +} +namespace ISaleInquiry +{ + export type IContent = ISaleArticle.IContent; +} +type ISaleAnswer = ISaleArticle +namespace ISaleAnswer +{ + export type IContent = ISaleArticle.IContent; +} +interface ISaleArticle +{ + id: string; + hit: number; + contents: Content[]; + created_at: string; +} +namespace ISaleArticle +{ + export interface IContent extends IUpdate + { + id: string; + created_at: string; + } + export interface IUpdate + { + title: string; + body: string; + files: Omit[]; + } +} + +interface IAttachmentFile +{ + id: string; + name: string; + extension: string | null; + url: string; +} \ No newline at end of file diff --git a/test/manual.js b/test/manual.js index 642bf91a4a..1fedd47de0 100644 --- a/test/manual.js +++ b/test/manual.js @@ -7,7 +7,7 @@ runner.register({ compiler: "ttypescript" }); -const program = require("./features/test_stringify_object_implicit.ts"); +const program = require("./features/test_stringify_array_generic_alias.ts"); for (const value of Object.values(program)) if (typeof value === "function") value(); \ No newline at end of file