diff --git a/package.json b/package.json index 908e558322..878be0a601 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typescript-json", - "version": "2.0.3", + "version": "2.0.4", "description": "Faster JSON stringify with only one line", "main": "lib/index.js", "typings": "lib/index.d.ts", diff --git a/src/factories/MetadataFactory.ts b/src/factories/MetadataFactory.ts index 026e2bcd3f..6ebb261892 100644 --- a/src/factories/MetadataFactory.ts +++ b/src/factories/MetadataFactory.ts @@ -1,12 +1,12 @@ import crypto from "crypto"; import ts from "typescript"; import { HashMap } from "tstl/container/HashMap"; +import { IPointer } from "tstl/functional/IPointer"; import { Pair } from "tstl/utility/Pair"; import { Singleton } from "tstl/thread/Singleton"; import { IMetadata } from "../structures/IMetadata"; import { TypeFactory } from "./TypeFactry"; -import { IPointer } from "tstl/functional/IPointer"; export namespace MetadataFactory { @@ -62,11 +62,16 @@ export namespace MetadataFactory counter: IPointer, schema: IMetadata, type: ts.Type, + parentEscaped: boolean = false ): boolean { - type = TypeFactory.escape(checker, type); + const [converted, partialEscaped] = TypeFactory.escape(checker, type); + if (partialEscaped === true) + type = converted; + + const escaped: boolean = partialEscaped || parentEscaped;; if (type.isUnion()) - return type.types.every(t => iterate(checker, dict, counter, schema, t)); + return type.types.every(t => iterate(checker, dict, counter, schema, t, escaped)); const node: ts.TypeNode | undefined = checker.typeToTypeNode(type, undefined, undefined); if (!node) @@ -87,18 +92,21 @@ export namespace MetadataFactory 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; + return escaped ? false : schema.nullable = true; else if (filter(ts.TypeFlags.Undefined)) - return !(schema.required = false); + return escaped ? false : !(schema.required = false); // ATOMIC VALUE TYPES for (const [flag, literal, className] of ATOMICS.get()) if (check(flag, literal, className) === true) - return true; + return escaped ? false : true; // WHEN ARRAY if (ts.isArrayTypeNode(node)) { + if (escaped) + return false; + const elemType: ts.Type | null = checker.getTypeArguments(type as ts.TypeReference)[0] || null; const elemSchema: IMetadata | null = explore(checker, dict, counter, elemType); @@ -109,7 +117,7 @@ export namespace MetadataFactory // WHEN TUPLE else if (ts.isTupleTypeNode(node)) { - if (node.elements.length === 0) + if (escaped || node.elements.length === 0) return false; const elemSchema: IMetadata | null = explore @@ -151,7 +159,7 @@ export namespace MetadataFactory const key: string = emplace(checker, dict, counter, type, schema.nullable); schema.objects.add(`external#/${key}`); } - return true; + return !escaped; } function emplace diff --git a/src/factories/TypeFactry.ts b/src/factories/TypeFactry.ts index 6c3ceb21e7..557bb97d26 100644 --- a/src/factories/TypeFactry.ts +++ b/src/factories/TypeFactry.ts @@ -2,9 +2,10 @@ import ts from "typescript"; export namespace TypeFactory { - export function escape(checker: ts.TypeChecker, type: ts.Type): ts.Type + export function escape(checker: ts.TypeChecker, type: ts.Type): [ts.Type, boolean] { - return get_return_type(checker, type, "toJSON") ?? type; + const converted: ts.Type | null = get_return_type(checker, type, "toJSON"); + return [converted || type, !!converted]; } export function is_function(node: ts.Node): boolean diff --git a/test/features/test_stringify_class_public.ts b/test/features/test_stringify_class_public.ts index 79f3234dc5..80a215b784 100644 --- a/test/features/test_stringify_class_public.ts +++ b/test/features/test_stringify_class_public.ts @@ -2,9 +2,9 @@ import TSON from "../../src"; export function test_stringify_class_public(): void { - const accessor: Accessor = new Accessor("public", "public", "protected", "private"); + const accessor: Accessor = new Accessor("implicit", "public", "protected", "private"); const json: string = TSON.stringify(accessor); - const expected: string = JSON.stringify({ implicit: "public", shown: "public" }); + const expected: string = JSON.stringify({ implicit: "implicit", shown: "public" }); if (json !== expected) throw new Error("Bug on TSON.stringify(): failed to understand the private class member."); diff --git a/test/features/test_stringify_to_json_object_closure.ts b/test/features/test_stringify_to_json_object_closure.ts deleted file mode 100644 index a6a17e5cb4..0000000000 --- a/test/features/test_stringify_to_json_object_closure.ts +++ /dev/null @@ -1,13 +0,0 @@ -import TSON from "../../src"; - -export function test_stringify_to_json_object_closure(): void -{ - const obj = { - value: 9, - toJSON: () => 2 + 3 + 4 - }; - - const json = TSON.stringify(obj); - if (json !== "9") - throw new Error("Bug on TSON.stringify(): failed to detect the toJSON() closure function."); -} \ No newline at end of file diff --git a/test/features/test_stringify_to_json_to_array..ts b/test/features/test_stringify_to_json_to_array..ts new file mode 100644 index 0000000000..b820eacb29 --- /dev/null +++ b/test/features/test_stringify_to_json_to_array..ts @@ -0,0 +1,25 @@ +import TSON from "../../src"; + +export function test_stringify_to_json_to_array(): void +{ + const something: Something = new Something(10); + const json: string = TSON.stringify(something); + const expected: string = JSON.stringify(something); + + if (json !== expected) + throw new Error("Bug on TSON.stringify(): failed to undertand the Object.toJSON() returning array."); +} + +class Something +{ + public constructor(public readonly value: number) + { + } + + public toJSON(): number[] + { + return new Array(this.value) + .fill(0) + .map((_, index) => index + 1); + } +} \ No newline at end of file diff --git a/test/features/test_stringify_to_json_class.ts b/test/features/test_stringify_to_json_to_atomic.ts similarity index 75% rename from test/features/test_stringify_to_json_class.ts rename to test/features/test_stringify_to_json_to_atomic.ts index 4667225aba..a2390e2d90 100644 --- a/test/features/test_stringify_to_json_class.ts +++ b/test/features/test_stringify_to_json_to_atomic.ts @@ -1,12 +1,12 @@ import TSON from "../../src"; -export function test_stringify_to_json_class(): void +export function test_stringify_to_json_to_atomic(): void { const operator: Operator = new Operator(2, 3, 4); const json: string = TSON.stringify(operator); if (json !== "9") - throw new Error("Bug on TSON.stringify(): failed to detect the toJSON() method."); + throw new Error("Bug on TSON.stringify(): failed to understand the atomic value."); } class Operator diff --git a/test/features/test_stringify_to_json_to_atomic_nullable.ts b/test/features/test_stringify_to_json_to_atomic_nullable.ts new file mode 100644 index 0000000000..ad76b78d7b --- /dev/null +++ b/test/features/test_stringify_to_json_to_atomic_nullable.ts @@ -0,0 +1,31 @@ +import TSON from "../../src"; + +export function test_stringify_to_json_to_atomic_nullable(): void +{ + for (const value of [1, 2]) + test(value); +} + +function test(value: number): void +{ + const something: Something = new Something(value); + const json: string = TSON.stringify(something); + const expected: string = JSON.stringify(something); + + if (json !== expected) + throw new Error("Bug on TSON.stringify(): failed to undertand the Object.toJSON() returning nullable atomic value."); +} + +class Something +{ + public constructor(public readonly value: number) + { + } + + public toJSON(): null | number + { + return this.value % 2 === 0 + ? this.value + : null; + } +} \ No newline at end of file diff --git a/test/features/test_stringify_to_json_to_atomic_union.ts b/test/features/test_stringify_to_json_to_atomic_union.ts new file mode 100644 index 0000000000..0c4db0077b --- /dev/null +++ b/test/features/test_stringify_to_json_to_atomic_union.ts @@ -0,0 +1,32 @@ +import TSON from "../../src"; + +export function test_stringify_to_json_to_atomic_union(): void +{ + for (const value of [1, 2]) + test(value); + +} + +function test(value: number): void +{ + const something: Something = new Something(value); + const json: string = TSON.stringify(something); + const expected: string = JSON.stringify(something); + + if (json !== expected) + throw new Error("Bug on TSON.stringify(): failed to undertand the Object.toJSON() returning atomic union value."); +} + +class Something +{ + public constructor(public readonly value: number) + { + } + + public toJSON(): number | string + { + return this.value % 2 === 0 + ? this.value + : this.value.toString(); + } +} \ No newline at end of file diff --git a/test/features/test_stringify_to_json_class_closure.ts b/test/features/test_stringify_to_json_to_class_closure.ts similarity index 61% rename from test/features/test_stringify_to_json_class_closure.ts rename to test/features/test_stringify_to_json_to_class_closure.ts index 9f8a068e89..1da7b46d12 100644 --- a/test/features/test_stringify_to_json_class_closure.ts +++ b/test/features/test_stringify_to_json_to_class_closure.ts @@ -1,12 +1,13 @@ import TSON from "../../src"; -export function test_stringify_to_json_class_closure(): void +export function test_stringify_to_json_to_class_closure(): void { const operator: Operator = new Operator(2, 3, 4); const json: string = TSON.stringify(operator); + const expected: string = JSON.stringify(operator); - if (json !== "9") - throw new Error("Bug on TSON.stringify(): failed to detect the toJSON() closure function."); + if (json !== expected) + throw new Error("Bug on TSON.stringify(): failed to understand the Object.toJSON() closure method."); } class Operator diff --git a/test/features/test_stringify_to_json_to_complicate_union.ts b/test/features/test_stringify_to_json_to_complicate_union.ts new file mode 100644 index 0000000000..e06c4507e8 --- /dev/null +++ b/test/features/test_stringify_to_json_to_complicate_union.ts @@ -0,0 +1,49 @@ +import TSON from "../../src"; + +export function test_stringify_to_json_to_complicated_union(): void +{ + new Array(5) + .fill(0) + .forEach((_, index) => test(index + 1)); +} + +function test(value: number): void +{ + const something: Something = new Something(value); + 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 Object.toJSON() returning complicate union."); +} + +class Something +{ + public constructor(public readonly value: number) + { + } + + public toJSON(): IX | IY | number[] | string | null + { + if (this.value === 1) + return { x: 1 }; + else if (this.value === 2) + return { y: "2" }; + else if (this.value === 3) + return [1, 2, 3]; + else if (this.value === 4) + return "4"; + else + return null; + } +} + +interface IX +{ + x: number; +} + +interface IY +{ + y: string; +} \ No newline at end of file diff --git a/test/features/test_stringify_to_json_to_object.ts b/test/features/test_stringify_to_json_to_object.ts new file mode 100644 index 0000000000..cbae66ad02 --- /dev/null +++ b/test/features/test_stringify_to_json_to_object.ts @@ -0,0 +1,33 @@ +import TSON from "../../src"; + +export function test_stringify_to_json_to_object(): void +{ + const operator: Operator = new Operator(2, 3, 4); + const json: string = TSON.stringify(operator); + const expected: string = JSON.stringify(operator); + + if (json !== expected) + throw new Error("Bug on TSON.stringify(): failed to understand the Object.toJSON() returning object."); +} + +class Operator +{ + public constructor + ( + public readonly x: number, + public readonly y: number, + public readonly z: number + ) + { + } + + public toJSON() + { + return { + x: this.x, + y: this.y, + z: this.z, + solution: this.x + this.y + this.z + }; + } +} \ No newline at end of file diff --git a/test/features/test_stringify_to_json_to_object_closure.ts b/test/features/test_stringify_to_json_to_object_closure.ts new file mode 100644 index 0000000000..e39a706a49 --- /dev/null +++ b/test/features/test_stringify_to_json_to_object_closure.ts @@ -0,0 +1,14 @@ +import TSON from "../../src"; + +export function test_stringify_to_json_to_object_closure(): void +{ + const obj = { + value: 9, + toJSON: () => 2 + 3 + 4 + }; + const json: string = TSON.stringify(obj); + const expected: string = JSON.stringify(obj); + + if (json !== expected) + throw new Error("Bug on TSON.stringify(): failed to understand the primitive toJSON() closure function."); +} \ No newline at end of file diff --git a/test/features/test_stringify_to_json_to_object_union.ts b/test/features/test_stringify_to_json_to_object_union.ts new file mode 100644 index 0000000000..290a832ff8 --- /dev/null +++ b/test/features/test_stringify_to_json_to_object_union.ts @@ -0,0 +1,41 @@ +import TSON from "../../src"; + +export function test_stringify_to_json_to_object_union(): void +{ + for (const value of [1, 2]) + test(value); +} + +function test(value: number): void +{ + const something: Something = new Something(value); + const json: string = TSON.stringify(something); + const expected: string = JSON.stringify(something); + + if (json !== expected) + throw new Error("Bug on TSON.stringify(): failed to undertand the Object.toJSON() returning union object."); +} + +class Something +{ + public constructor(public readonly value: number) + { + } + + public toJSON(): IX | IY + { + return this.value % 2 === 0 + ? { x: this.value } + : { y: this.value.toString() }; + } +} + +interface IX +{ + x: number; +} + +interface IY +{ + y: string; +} \ No newline at end of file