parse()
functions
export namespace json {
export function isParse<T>(input: string): Primitive<T> | null;
export function assertParse<T>(input: string): Primitive<T>;
export function validateParse<T>(input: string): IValidation<Primitive<T>>;
}
export class TypeGuardError extends Error {
public readonly method: string;
public readonly path: string | undefined;
public readonly expected: string;
public readonly value: any;
}
export type IValidation<T> = IValidation.ISuccess<T> | IValidation.IFailure;
export namespace IValidation {
export interface ISuccess<T> {
success: true;
data: T;
}
export interface IFailure {
success: false;
errors: IError[];
data: unknown;
}
export interface IError {
path: string;
expected: string;
value: any;
}
}
/**
* Primitive type of JSON.
*
* `Primitive<T>` is a TMP (Type Meta Programming) type which converts
* its argument as a primitive type within framework JSON.
*
* If the target argument is a built-in class which returns its origin primitive type
* through the `valueOf()` method like the `String` or `Number`, its return type would
* be the `string` or `number`. Otherwise, the built-in class does not have the
* `valueOf()` method, the return type would be an empty object (`{}`).
*
* Otherwise, the target argument is a type of custom class, all of its custom method
* would be erased and its prototype would be changed to the primitive `object`.
* Therefore, return type of the TMP type finally be the primitive object.
*
* In addition, if the target argument is a type of custom class and it has a special
* method `toJSON()`, return type of this `Primitive` would be not `Primitive<Instance>`
* but `Primitive<ReturnType<Instance.toJSON>>`.
*
* Before | After
* ------------------------|----------------------------------------
* `Boolean` | `boolean`
* `Number` | `number`
* `String` | `string`
* `Class` | `object`
* `Class` with `toJSON()` | `Primitive<ReturnType<Class.toJSON>>`
* Native Class | never
* Others | No change
*
* @template T Target argument type.
* @author Jeongho Nam - https://github.com/samchon
* @author Kyungsu Kang - https://github.com/kakasoo
* @author Michael - https://github.com/8471919
*/
export type Primitive<T> = Equal<T, PrimitiveMain<T>> extends true
? T
: PrimitiveMain<T>;
type Equal<X, Y> = X extends Y ? (Y extends X ? true : false) : false;
type PrimitiveMain<Instance> = Instance extends [never]
? never // (special trick for jsonable | null) type
: ValueOf<Instance> extends bigint
? never
: ValueOf<Instance> extends boolean | number | string
? ValueOf<Instance>
: Instance extends Function
? never
: ValueOf<Instance> extends object
? Instance extends object
? Instance extends NativeClass
? never
: Instance extends IJsonable<infer Raw>
? ValueOf<Raw> extends object
? Raw extends object
? PrimitiveObject<Raw> // object would be primitified
: never // cannot be
: ValueOf<Raw> // atomic value
: PrimitiveObject<Instance> // object would be primitified
: never // cannot be
: ValueOf<Instance>;
type PrimitiveObject<Instance extends object> = Instance extends Array<infer T>
? IsTuple<Instance> extends true
? PrimitiveTuple<Instance>
: PrimitiveMain<T>[]
: {
[P in keyof Instance]: PrimitiveMain<Instance[P]>;
};
type PrimitiveTuple<T extends readonly any[]> = T extends []
? []
: T extends [infer F]
? [PrimitiveMain<F>]
: T extends [infer F, ...infer Rest extends readonly any[]]
? [PrimitiveMain<F>, ...PrimitiveTuple<Rest>]
: T extends [(infer F)?]
? [PrimitiveMain<F>?]
: T extends [(infer F)?, ...infer Rest extends readonly any[]]
? [PrimitiveMain<F>?, ...PrimitiveTuple<Rest>]
: [];
type ValueOf<Instance> = IsValueOf<Instance, Boolean> extends true
? boolean
: IsValueOf<Instance, Number> extends true
? number
: IsValueOf<Instance, String> extends true
? string
: Instance;
type NativeClass =
| Set<any>
| Map<any, any>
| WeakSet<any>
| WeakMap<any, any>
| Uint8Array
| Uint8ClampedArray
| Uint16Array
| Uint32Array
| BigUint64Array
| Int8Array
| Int16Array
| Int32Array
| BigInt64Array
| Float32Array
| Float64Array
| ArrayBuffer
| SharedArrayBuffer
| DataView;
type IsTuple<T extends readonly any[] | { length: number }> = [T] extends [
never,
]
? false
: T extends readonly any[]
? number extends T["length"]
? false
: true
: false;
type IsValueOf<Instance, Object extends IValueOf<any>> = Instance extends Object
? Object extends IValueOf<infer U>
? Instance extends U
? false
: true // not Primitive, but Object
: false // cannot be
: false;
interface IValueOf<T> {
valueOf(): T;
}
interface IJsonable<T> {
toJSON(): T;
}
Type safe JSON parser.
Unlike native JSON.parse()
function which returns any typed instance without type checking, typia.json.assertParse<T>()
function validates instance type after the parsing. If the parsed value is not following the promised type T
, it throws TypeGuardError
with the first type error info.
If you want to know every type error infos detaily, you can use typia.json.validateParse<T>()
function instead. Otherwise, you just only want to know whether the parsed value is following the type T
or not, just call typia.json.isParse<T>()
function.
typia.json.isParse<T>()
:JSON.parse()
+typia.is<T>()
typia.json.assertParse<T>()
:JSON.parse()
+typia.assert<T>()
typia.json.validateParse<T>()
:JSON.parse()
+typia.validate<T>()
Look at the below code, then you may understand how the typia.json.assertParse<T>()
function works.
import typia, { tags } from "typia";
const json: string = JSON.stringify(typia.random<IMember>());
const parsed: IMember = typia.json.assertParse<IMember>(json);
console.log(json === JSON.stringify(parsed)); // true
interface IMember {
id: string & tags.Format<"uuid">;
email: string & tags.Format<"email">;
age: number &
tags.Type<"uint32"> &
tags.ExclusiveMinimum<19> &
tags.Maximum<100>;
}
import * as __typia_transform__randomFormatUuid from "typia/lib/internal/_randomFormatUuid.js";
import * as __typia_transform__randomFormatEmail from "typia/lib/internal/_randomFormatEmail.js";
import * as __typia_transform__randomInteger from "typia/lib/internal/_randomInteger.js";
import * as __typia_transform__isFormatUuid from "typia/lib/internal/_isFormatUuid.js";
import * as __typia_transform__isFormatEmail from "typia/lib/internal/_isFormatEmail.js";
import * as __typia_transform__isTypeUint32 from "typia/lib/internal/_isTypeUint32.js";
import * as __typia_transform__assertGuard from "typia/lib/internal/_assertGuard.js";
import typia from "typia";
const json = JSON.stringify(
(() => {
const _ro0 = (_recursive = false, _depth = 0) => ({
id: (
_generator?.uuid ??
__typia_transform__randomFormatUuid._randomFormatUuid
)(),
email: (
_generator?.email ??
__typia_transform__randomFormatEmail._randomFormatEmail
)(),
age: (
_generator?.integer ?? __typia_transform__randomInteger._randomInteger
)({
type: "integer",
exclusiveMinimum: true,
minimum: 19,
maximum: 100,
}),
});
let _generator;
return (generator) => {
_generator = generator;
return _ro0();
};
})()(),
);
const parsed = (() => {
const _io0 = (input) =>
"string" === typeof input.id &&
__typia_transform__isFormatUuid._isFormatUuid(input.id) &&
"string" === typeof input.email &&
__typia_transform__isFormatEmail._isFormatEmail(input.email) &&
"number" === typeof input.age &&
__typia_transform__isTypeUint32._isTypeUint32(input.age) &&
19 < input.age &&
input.age <= 100;
const _ao0 = (input, _path, _exceptionable = true) =>
(("string" === typeof input.id &&
(__typia_transform__isFormatUuid._isFormatUuid(input.id) ||
__typia_transform__assertGuard._assertGuard(
_exceptionable,
{
method: "typia.json.assertParse",
path: _path + ".id",
expected: 'string & Format<"uuid">',
value: input.id,
},
_errorFactory,
))) ||
__typia_transform__assertGuard._assertGuard(
_exceptionable,
{
method: "typia.json.assertParse",
path: _path + ".id",
expected: '(string & Format<"uuid">)',
value: input.id,
},
_errorFactory,
)) &&
(("string" === typeof input.email &&
(__typia_transform__isFormatEmail._isFormatEmail(input.email) ||
__typia_transform__assertGuard._assertGuard(
_exceptionable,
{
method: "typia.json.assertParse",
path: _path + ".email",
expected: 'string & Format<"email">',
value: input.email,
},
_errorFactory,
))) ||
__typia_transform__assertGuard._assertGuard(
_exceptionable,
{
method: "typia.json.assertParse",
path: _path + ".email",
expected: '(string & Format<"email">)',
value: input.email,
},
_errorFactory,
)) &&
(("number" === typeof input.age &&
(__typia_transform__isTypeUint32._isTypeUint32(input.age) ||
__typia_transform__assertGuard._assertGuard(
_exceptionable,
{
method: "typia.json.assertParse",
path: _path + ".age",
expected: 'number & Type<"uint32">',
value: input.age,
},
_errorFactory,
)) &&
(19 < input.age ||
__typia_transform__assertGuard._assertGuard(
_exceptionable,
{
method: "typia.json.assertParse",
path: _path + ".age",
expected: "number & ExclusiveMinimum<19>",
value: input.age,
},
_errorFactory,
)) &&
(input.age <= 100 ||
__typia_transform__assertGuard._assertGuard(
_exceptionable,
{
method: "typia.json.assertParse",
path: _path + ".age",
expected: "number & Maximum<100>",
value: input.age,
},
_errorFactory,
))) ||
__typia_transform__assertGuard._assertGuard(
_exceptionable,
{
method: "typia.json.assertParse",
path: _path + ".age",
expected:
'(number & Type<"uint32"> & ExclusiveMinimum<19> & Maximum<100>)',
value: input.age,
},
_errorFactory,
));
const __is = (input) =>
"object" === typeof input && null !== input && _io0(input);
let _errorFactory;
const __assert = (input, errorFactory) => {
if (false === __is(input)) {
_errorFactory = errorFactory;
((input, _path, _exceptionable = true) =>
((("object" === typeof input && null !== input) ||
__typia_transform__assertGuard._assertGuard(
true,
{
method: "typia.json.assertParse",
path: _path + "",
expected: "IMember",
value: input,
},
_errorFactory,
)) &&
_ao0(input, _path + "", true)) ||
__typia_transform__assertGuard._assertGuard(
true,
{
method: "typia.json.assertParse",
path: _path + "",
expected: "IMember",
value: input,
},
_errorFactory,
))(input, "$input", true);
}
return input;
};
return (input, errorFactory) => __assert(JSON.parse(input), errorFactory);
})()(json);
console.log(json === JSON.stringify(parsed)); // true
/**
* Primitive type of JSON.
*
* `Primitive<T>` is a TMP (Type Meta Programming) type which converts
* its argument as a primitive type within framework JSON.
*
* If the target argument is a built-in class which returns its origin primitive type
* through the `valueOf()` method like the `String` or `Number`, its return type would
* be the `string` or `number`. Otherwise, the built-in class does not have the
* `valueOf()` method, the return type would be an empty object (`{}`).
*
* Otherwise, the target argument is a type of custom class, all of its custom method
* would be erased and its prototype would be changed to the primitive `object`.
* Therefore, return type of the TMP type finally be the primitive object.
*
* In addition, if the target argument is a type of custom class and it has a special
* method `toJSON()`, return type of this `Primitive` would be not `Primitive<Instance>`
* but `Primitive<ReturnType<Instance.toJSON>>`.
*
* Before | After
* ------------------------|----------------------------------------
* `Boolean` | `boolean`
* `Number` | `number`
* `String` | `string`
* `Class` | `object`
* `Class` with `toJSON()` | `Primitive<ReturnType<Class.toJSON>>`
* Native Class | never
* Others | No change
*
* @template T Target argument type.
* @author Jeongho Nam - https://github.com/samchon
* @author Kyungsu Kang - https://github.com/kakasoo
* @author Michael - https://github.com/8471919
*/
export type Primitive<T> = Equal<T, PrimitiveMain<T>> extends true
? T
: PrimitiveMain<T>;
type Equal<X, Y> = X extends Y ? (Y extends X ? true : false) : false;
type PrimitiveMain<Instance> = Instance extends [never]
? never // (special trick for jsonable | null) type
: ValueOf<Instance> extends bigint
? never
: ValueOf<Instance> extends boolean | number | string
? ValueOf<Instance>
: Instance extends Function
? never
: ValueOf<Instance> extends object
? Instance extends object
? Instance extends NativeClass
? never
: Instance extends IJsonable<infer Raw>
? ValueOf<Raw> extends object
? Raw extends object
? PrimitiveObject<Raw> // object would be primitified
: never // cannot be
: ValueOf<Raw> // atomic value
: PrimitiveObject<Instance> // object would be primitified
: never // cannot be
: ValueOf<Instance>;
type PrimitiveObject<Instance extends object> = Instance extends Array<infer T>
? IsTuple<Instance> extends true
? PrimitiveTuple<Instance>
: PrimitiveMain<T>[]
: {
[P in keyof Instance]: PrimitiveMain<Instance[P]>;
};
type PrimitiveTuple<T extends readonly any[]> = T extends []
? []
: T extends [infer F]
? [PrimitiveMain<F>]
: T extends [infer F, ...infer Rest extends readonly any[]]
? [PrimitiveMain<F>, ...PrimitiveTuple<Rest>]
: T extends [(infer F)?]
? [PrimitiveMain<F>?]
: T extends [(infer F)?, ...infer Rest extends readonly any[]]
? [PrimitiveMain<F>?, ...PrimitiveTuple<Rest>]
: [];
type ValueOf<Instance> = IsValueOf<Instance, Boolean> extends true
? boolean
: IsValueOf<Instance, Number> extends true
? number
: IsValueOf<Instance, String> extends true
? string
: Instance;
type NativeClass =
| Set<any>
| Map<any, any>
| WeakSet<any>
| WeakMap<any, any>
| Uint8Array
| Uint8ClampedArray
| Uint16Array
| Uint32Array
| BigUint64Array
| Int8Array
| Int16Array
| Int32Array
| BigInt64Array
| Float32Array
| Float64Array
| ArrayBuffer
| SharedArrayBuffer
| DataView;
type IsTuple<T extends readonly any[] | { length: number }> = [T] extends [
never,
]
? false
: T extends readonly any[]
? number extends T["length"]
? false
: true
: false;
type IsValueOf<Instance, Object extends IValueOf<any>> = Instance extends Object
? Object extends IValueOf<infer U>
? Instance extends U
? false
: true // not Primitive, but Object
: false // cannot be
: false;
interface IValueOf<T> {
valueOf(): T;
}
interface IJsonable<T> {
toJSON(): T;
}
Reusable functions
export namespace json {
export function createIsParse<T>(): (input: string) => Primitive<T> | null;
export function createAssertParse<T>(): (input: string) => Primitive<T>;
export function createValidateParse<T>(): (
input: string,
) => IValidation<Primitive<T>>;
}
export class TypeGuardError extends Error {
public readonly method: string;
public readonly path: string | undefined;
public readonly expected: string;
public readonly value: any;
}
export type IValidation<T> = IValidation.ISuccess<T> | IValidation.IFailure;
export namespace IValidation {
export interface ISuccess<T> {
success: true;
data: T;
}
export interface IFailure {
success: false;
errors: IError[];
data: unknown;
}
export interface IError {
path: string;
expected: string;
value: any;
}
}
Reusable typia.json.isParse<T>()
function generators.
If you repeat to call typia.json.isParse<T>()
function on the same type, size of JavaScript files would be larger because of duplicated AOT compilation. To prevent it, you can generate reusable function through typia.createIsParse<T>()
function.
Just look at the code below, then you may understand how to use it.
import typia, { tags } from "typia";
export const parseMember = typia.json.createIsParse<IMember>();
interface IMember {
id: string & tags.Format<"uuid">;
email: string & tags.Format<"email">;
age: number &
tags.Type<"uint32"> &
tags.ExclusiveMinimum<19> &
tags.Maximum<100>;
}
import * as __typia_transform__isFormatUuid from "typia/lib/internal/_isFormatUuid.js";
import * as __typia_transform__isFormatEmail from "typia/lib/internal/_isFormatEmail.js";
import * as __typia_transform__isTypeUint32 from "typia/lib/internal/_isTypeUint32.js";
import typia from "typia";
export const parseMember = (() => {
const _io0 = (input) =>
"string" === typeof input.id &&
__typia_transform__isFormatUuid._isFormatUuid(input.id) &&
"string" === typeof input.email &&
__typia_transform__isFormatEmail._isFormatEmail(input.email) &&
"number" === typeof input.age &&
__typia_transform__isTypeUint32._isTypeUint32(input.age) &&
19 < input.age &&
input.age <= 100;
const __is = (input) =>
"object" === typeof input && null !== input && _io0(input);
return (input) => {
input = JSON.parse(input);
return __is(input) ? input : null;
};
})();