UNPKG

@badcafe/jsonizer

Version:

Structural reviving for JSON

1,181 lines (1,173 loc) โ€ข 42.1 kB
/** Errors utilities */ declare namespace Errors { /** Built-in error classes */ const errors: { readonly Error: ErrorConstructor; readonly EvalError: EvalErrorConstructor; readonly RangeError: RangeErrorConstructor; readonly ReferenceError: ReferenceErrorConstructor; readonly SyntaxError: SyntaxErrorConstructor; readonly TypeError: TypeErrorConstructor; readonly URIError: URIErrorConstructor; }; /** Built-in error class names */ type errors = keyof typeof errors; /** * An error may define additional custom properties. */ interface TypedError<Type = unknown> extends ErrorConstructor { new (message?: string): Type & Error; } /** * Dynamically create a user error class with the name given. * * @param name The name of the class. * @param unique if `true` (by default), a singleton with that * name will be returned ; if `false`, a new class will be returned * if that name was not previously used to define a singleton. * @param code Conveniently, an error code may be bound to the class, * because it is a common practice. If a code is defined, the singleton * must have the same code, otherwise another class will be generated. * @paramType Type - Additional custom properties (optional) allowed in the error. * The properties can be set after calling `new`. * @returns An error class * * @see [[getCode]] * @see [[getName]] */ function getClass<Type = unknown>(name: string, unique?: boolean, code?: string | number): TypedError<Type>; /** * If the error class was created dynamically, * return the error code if one was supplied. * * @param error The error class or instance * @returns Its error code * @paramType Code - Downcast the error code. * * @see [[getClass]] */ function getCode<Code = string | number | undefined>(error: Error | ErrorConstructor): Code; /** * Set an error code to some error. * * @param error The actual error * @param code Its code */ function setCode(error: Error, code: string | number): void; /** * Return the name of an error. * * @param error The error class or instance * @returns Its name * * @see [[getClass]] */ function getName(error: Error | ErrorConstructor): string; /** * Indicates whether a class is an Error class, * that is to say extends Error. * * @param err The error class to test. */ function isError(err: Class): boolean; /** * Stringify an error in Jsonizer style, e.g. `TypeError: Ooops !` * * @param err Any error */ function toString(err: Error): void; } /** * Decorator that defines the namespace of a class, and * registers it for later lookup by name. * * In case of conflict, classes can be relocated by using * this function not as a decorator, but as a normal function. * * > โ„น๏ธ This is unrelated to Typescript namespaces ! * * ## Usage as a decorator * * ``` * โ“Namespace('org.example.myApp') * class Foo {} * // ๐Ÿ‘† qualified name set to 'org.example.myApp.Foo' * * // setting a relative namespace : * โ“Namespace(Foo) * class Bar {} * // ๐Ÿ‘† qualified name set to 'org.example.myApp.Foo.Bar' * * class Baz {} * * โ“Namespace(Baz) * class Qux {} * // ๐Ÿ‘† qualified name set to 'Baz.Qux' * ``` * * ## Usage as a function * * ``` * // setting a namespace to an existing class : * import { Quux } from 'quuxLib'; * Namespace('org.example.myApp')(Quux) * // ๐Ÿ‘† qualified name set to * // 'org.example.myApp.Quux' * * // relocating a class * Namespace('org.example.myApp')(Baz) * // ๐Ÿ‘† qualified name set to * // 'org.example.myApp.Baz' * // and incidentally, Qux subordinate qualified name * // set to 'org.example.myApp.Baz.Qux' * * ``` * * ## Default namespace * * ``` * โ“Namespace('') * class Corge {} * // ๐Ÿ‘† qualified name set to 'Corge' * ``` * * ## Usage on interfaces and type aliases * * > The following requires to be compiled with `@badcafe/ts-plugin` * * ```typescript * interface Person { * public name: string, * public birthDate: Date * } * * Namespace<Person>('org.example.myApp')() // ๐Ÿ‘ˆ bind the namespace to the interface * ``` * * @see [[Namespace.getClass]] * @see [[Namespace.getQualifiedName]] * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=namespaces) */ declare function Namespace<Target>(ns: Class & Ext | string): <Type extends null | Class = null>(target?: Type) => Type extends null ? Class<Target> : void; /** * ## Hold the registry of classes * * The purpose of Jsonizer's namespaces is to let classes **knowing** their * fully qualified name. * * > Javascript and Typescript can group items together under namespaces, * > which lead roughly to a hierarchy of objects, but the items themselves * > when they are classes don't have the knowledge of the hierarchy they * > belong to. * * Each class has its own identity but in order **to refer them** * by name properly it is necessary to introduce namespaces. * * A namespace is mandatory for example to refer 2 classes * with the same name, e.g. `Category` (of a product) and * `Category` (of a movie). * * A namespace have to be unique within an application, therefore * classes that are designed to be exposed in a library should * be decorated with a universal unique namespace identifier, * e.g. `org.example.myApp`. Conversely, it is enough for * a standalone application to group subordinate classes * together using relative namespaces. * * * using a universal unique namespace identifier : * ``` * โ“Namespace('org.example.myApp') * class Foo {} * // ๐Ÿ‘† qualified name set to 'org.example.myApp.Foo' * ``` * * using relative namespaces : * ``` * โ“Namespace(Product) * class Category {} * // ๐Ÿ‘† qualified name set to 'Product.Category' * ``` * * * ``` * โ“Namespace(Movie) * class Category {} * // ๐Ÿ‘† qualified name set to 'Movie.Category' * ``` * * In the example above, even if an app imports 2 classes * with the same name `Product`, it is still possible to * relocate one (or both) of them : * * ``` * import { Product as Product1 } from './myApp'; * import { Product as Product2 } from 'someLib'; * โ“Namespace('org.example.myApp')(Product1) * // qualified name ๐Ÿ‘† * // set to 'org.example.myApp.Product' * โ“Namespace('com.example.products')(Product2) * // qualified name ๐Ÿ‘† * // set to 'com.example.products.Product' * ``` * * By transitivity, the `Category` is relocated to * `org.example.myApp.Product.Category` * * > โ„น๏ธ This is unrelated to Typescript namespaces ! * * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=namespaces) * * ## Code splitting (lazy loading) * * Modern Javascript bundlers allow in some circumstances * to split the code in several files that will be loaded * on demand (client side). * * Ensure that the code that do register each class is * visited before any registry lookup, otherwise you * might have mismatch lookups. * * Typically, this can be done by importing them in the * app entry point. */ declare namespace Namespace { /** * Metadata key. */ const $: unique symbol; /** * Indicates whether a class has a namespace or not. * * @param target The target class. */ function hasNamespace(target: object): boolean; /** * Return the qualified name of a class. * * @param target The target class. * @returns Its name preceded by its namespace, if any. */ function getQualifiedName(target: Class): string; /** * Lookup for a class by name * * @param qname The qualified name of the class. * @returns The class bound to that name. * @throws `Name Conflict` error when several classes are found : * can be fix by setting 2 different namespaces to the classes. * @throws `Missing Name` error when the class is not found in the registry. */ function getClass<T>(qname: string): Class<T>; /** * Lookup for a class by name. * * @param qname The qualified name of the class. * @returns The class bound to that name. * @throws `Name Conflict` error when several classes are found : * can be fix by setting 2 different namespaces to the classes. */ function hasClass<T>(qname: string): Class<T> | undefined; /** * Deduplicate a name in the namespace registry. * * If there are several entries bound to the given * qualified name, they are considered similar and * the first is kept. * * > When each class has a unique qualified name, * > this won't happen. When it happens, it has to * > be fixed by setting different qualified names. * > When it still happens, it's because the library * > is loaded several times ; in that case, the * > registry is still unique but duplicates may * > be found, typically for internal classes (this * > function was designed for them, you ought NOT * > use it). * * @param qname The qualified name to deduplicate if necessary. */ function dedup(qname: string): void; /** * Dump the namespace registry ; if an entry contains * duplicates, throws an error. * * Useful after all `@Namespace`s have been set. * * @param noFail Don't throw an error. */ function checkIntegrity(noFail?: boolean): Generator<readonly [string, ...(Class & Ext)[]], void, unknown>; /** * ## For advanced usage only * * Unregisters a class from the registry. * * Should never be used, except for classes that would be managed * dynamically with a life-span controlled by the user. */ function unregisterClass(target: Class): void; /** * ## For advanced usage only * * Do not use : use instead `Namespace(theClass)`. * * Force to register a class in the registry by a given name. * * Should never be used, except for classes that would be managed * dynamically with a life-span controlled by the user. */ function registerClassByQualifiedName(qn: string, target: Class): void; } /** * Base * * @module */ declare const IS_STRING: unique symbol; declare const CHILDREN: unique symbol; type Ext = { [CHILDREN]?: Class[]; [IS_STRING]?: true; }; /** * Any class, including abstract classes. * * @paramType Type - The class type * @paramType Args - The arguments of the constructor */ type Class<Type = any, Args extends any[] = any[]> = Class.Concrete<Type, Args> | Class.Abstract<Type>; /** * Abstract and Concrete classes. */ declare namespace Class { /** * Any concrete class, that is to say not abstract. */ type Concrete<Type = any, Args extends any[] = any[]> = new (...args: Args) => Type; /** * Any abstract class. */ type Abstract<Type = any> = { name: string; prototype: Type; }; /** * Set a name to a class or function, if necessary. * * > Helpful after creating a class or function with a * > dynamic name, some bundlers are somewhat destructive. * * If a wrapper is created, they differ by their namespace * and the children of the orginal are moved to the wrapper. * * @param fun The actual class or function * @param name The name to set * @returns The class itself if it already has the expected name, * or a wrapper around with the relevant name. */ function rename<F extends (Class | Function) & Ext>(fun: F & EXT_shader_texture_lod, name: string): F; } /** * [User guide](https://badcafe.github.io/jsonizer) * * @module */ /** * A plain object that describes the mappers of a class, an array, * a tuple, or a data structure. * * The `Mappers` object is the counterpart inert structure of the * [`Reviver` type](#reviver-1) (which is a function). * * ## Example * * ``` * // some class * class Thread { * constructor( * public date: Date, // built-in javascript Date * public label: string, * public comments: Comment[] // another custom class * ) * } * * // our custom mapper * const threadMappers: Mappers<Thread> = { * //๐Ÿ‘‡ the "Self" entry '.' indicates how to create a new instance * '.': ({date, label, comments}) => new Thread(date, label, comments) * //๐Ÿ‘‡ field mappers * date: Date // ๐Ÿ‘ˆ delegate to the built-in Date mappers * comments: { * //๐Ÿ‘‡ the "Any" entry '*' matches any indices of the array * '*': Comment // ๐Ÿ‘ˆ delegate to the custom Comment mappers * } * } * * // bind the mapper to the class * // (can also be used as a decorator) * Reviver(threadMappers)(Thread) * ``` * @see [Reviver()](#reviver-2) To bind the mapper to the class * @see [[Jsonizer.reviver]] To create a reviver from the mapper * * ## Description * * The mappers indicates how an object or array and each of its property or items has to be * converted (by default the properties or items are left as-is). An object property or * array item can also describe its own mappers and so-on. * * The more often (when Source and Target are the same) the properties of an object mapper * are the same of those of the object, or in case of an array mapper the indices in that * array (which is helpful for mapping tuples). * * Some additional keys are available in the mapper : * * * `'*'` that catches every other properties or indices that don't have a mapping, which * is helpful for arrays that contains the same kind of items * * `'.'` that indicates how to create the host instance from the properties or array * items **after** applying their mappings : it's a function that has `this` bound to a * stack of the ancestors of the actual item, itself passed as an argument, and that * returns the new instance expected * * `'/\\w+Date/'` which is a regexp key that can be set only on mappers for objects, * in this example it matches property names that end with 'Date' * * `'8-12'` which is a range key that can be set only on mappers for arrays, in this * example, it matches indexes from 8 to 12 included. * * > Unlike all other keys, the `'.'` key is not a field mapper, but a builder function. * * ### Built-in mappers * * Built-in class mappers already exist for `Date`, `Error`, and `RegExp`. * * ### Type parameters * * @paramType Target - The actual target class * @paramType Source - The JSON representation of `Target` is by default the structural `Target` itself * * ## Customization * * It is not recommended to override the following defaults, but yet possible : * * @paramType Any - The mapper for any other field is bound to the key `'*'` * @paramType Self - The builder of the target instance is bound to the key `'.'` * @paramType Delim - The RegExp delimiter is by default `'/'`, and the range delimiter * is by default `'-'` * * If one of the jokers `Any` (`'*'`), `Self` (`'.'`) or `Delim` (`'/' | '-'`) is changed, * then the mapper must contain an array of that jokers bound to the symbol `[Jokers.Key]` * * @see [[Jokers]] * @see [User guide - Revivers mappings](https://badcafe.github.io/jsonizer/#/README?id=revivers-mappings) * @see [User guide - Data Transfer Object](https://badcafe.github.io/jsonizer/#/README?id=dto) */ type Mappers<Target, Source = Target, Any extends string = '*', Self extends string = '.', Delim extends string = Source extends Array<any> ? '-' : '/'> = ({ [key in Self]: Reviver.Reference; } & { [index: number]: never; } & Mappers.Jokers.Custom<Any, Self, Delim>) | ({ [key in Self]?: ((this: any[], args: Source) => Target | any); } & (Source extends Array<infer Item> ? { 0?: Reviver.Reference | Mappers<Source[0]>; 1?: Reviver.Reference | Mappers<Source[1]>; 2?: Reviver.Reference | Mappers<Source[2]>; 3?: Reviver.Reference | Mappers<Source[3]>; 4?: Reviver.Reference | Mappers<Source[4]>; 5?: Reviver.Reference | Mappers<Source[5]>; 6?: Reviver.Reference | Mappers<Source[6]>; 7?: Reviver.Reference | Mappers<Source[7]>; 8?: Reviver.Reference | Mappers<Source[8]>; 9?: Reviver.Reference | Mappers<Source[9]>; } & { [index: number]: Reviver.Reference | Mappers<Item>; } & { [key in Mappers.Matcher.Range<Delim>]: Reviver.Reference | Mappers<Item>; } & { [key in Any]?: Reviver.Reference | Mappers<Item>; } & Mappers.Jokers.Custom<Any, Self, Delim> : Source extends string | number | boolean | null ? Mappers.Jokers.Custom<Any, Self, Delim> : { [key in keyof Source]?: Reviver.Reference | Mappers<Source[key]>; } & { [key in Mappers.Matcher.Regexp<Delim>]: Reviver.Reference | Mappers<any>; } & { [key in Any]?: Reviver.Reference | Mappers<any>; } & Mappers.Jokers.Custom<Any, Self, Delim>)); /** * Property related types for mappers. */ declare namespace Mappers { /** * A mapper may contain the entry `Self = '.'` for the host object * itself, and the entry `Any = '*'` for a catch-all mapping. * * Additionnally, field names that matches a RegExp or indices that * matches a range can also be bound to a mapper ; the RegExp delimiter * is by default `Delim = '/'`, and the range delimiter is by default * `Delim = '-'`. * * ## Customization * * It is not recommended (useless ?) to customize the jokers, although it is * still possible. * * ``` * // do you really want to use this ? * interface FooSource { * '*': string * '.': number * '/': boolean * // and other members * } * // ok, then rename the special jokers '*', '.' and '/' * // by -say- '**', 'that', and '~' : * ClassMapper<Foo, FooSource, '**', 'that', '~'> = { * // as soon as they are renamed, the next entry is mandatory : * [Mappers.Jokers.$]: ['**', 'that', '~'], * '**': { * // '**' is the optional mapper for *Any* other fields * } * // 'that' is the optional mapper for *Self* builder * 'that': ({'*': star, '.': dot}) => new Foo(star, dot) * // '~' is the delimiter for Regexp (or for ranges for an array) * '~\\w+~': AnotherMapper * } * ``` */ namespace Jokers { /** * A key that allow to customize the jokers of a mapper object. * * The default values are either `['*', '.', '/']` for a mapper * of an object, or `['*', '.', '-']` for a mapper of an array : * * * "Any" (`'*'`) denotes any entry * * "Self" (`'.'`) denotes the host builder entry * * "Delim" (`'/'`) when used as the Regexp delimiter, e.g. `'/\\w+Date/'` * * "Delim" (`'-'`) when used as the range delimiter, e.g. `'8-12'` * * @see [[Jokers]] */ const $: unique symbol; /** * Allow to customize jokers. * * When `Any` is not `'*'` or `Self` is not `'.'` or `Delim` is not `'/'` or `'-'`, * then this type contains an entry that must contain an array with the new values. * * @see [[Jokers]] */ type Custom<Any extends string, Self extends string, Delim extends string> = Self extends '.' ? Any extends '*' ? Delim extends '/' | '-' ? { [$]?: never; } : { [$]: [Any, Self, Delim]; } : { [$]: [Any, Self, Delim]; } : { [$]: [Any, Self, Delim]; }; } /** * A matcher is either a regular expression for matching properties * of an object, or a range for matching indexes of an array. */ type Matcher<Source, Delim extends string> = Source extends Array<any> ? Matcher.Range<Delim> : Matcher.Regexp<Delim>; /** * A matcher is either a regular expression for matching properties * of an object, or a range for matching indexes of an array. * * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=ranges-and-regexp) */ namespace Matcher { /** * A type for Regexp, e.g. `'/\\w+Date/'` * > RegExp keys are represented as strings, which may introduce additional * > escapes, e.g. `/\w+Date/.toString()` gives `'/\\w+Date/'` * * @see [[Jokers]] * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=regexp) */ type Regexp<Delim extends string = '/'> = `${Delim}${string}${Delim}`; /** * A type for arrays range , e.g. `'8-12'` (included) * * @see [[Jokers]] * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=ranges) */ type Range<Delim extends string = '-'> = `${number}${Delim}${number}`; /** * Return -if any- the Regexp mapper or range mapper that matches a property name * or an array index. * * > **Doesn't return the exact match neither the "Any" match.** * * @param mappers Contains the matching mappers. * @param prop The property name, or the array index. */ function getMatchingMapper(mappers: Mappers<any>, prop: string | number): Mappers<any> | undefined; /** * Indicates whether a key is a Regexp, e.g. `'/\\w+Date/'` * * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=regexp) */ function isRegExp(key: string, delim?: string): boolean; /** * Indicates whether a key is a range, e.g. `'8-12'` * * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=ranges) */ function isRange(key: string, delim?: string): boolean; } /** * Optimizable array mappers. */ type Optimizable = { /** * Array mappers */ mapper: Mappers<any>; /** * Increment for each json entry, this might be slightly different from * mapper keys since some keys might be merged. */ count: number; /** * mapping keys[], in order */ keys: Optimizable.Keys[]; }; /** * Optimizable mappers. * * > For advanced usage. */ namespace Optimizable { /** * Arrays can have their mappers optimized : consecutive indices * that have the same mappings are merged in a range */ type Keys = { /** indice or range, as a string, e.g. '2-5' */ key: string; /** indice */ from: number; /** or range */ to?: number; }; /** * Indicates whether a candidate key to add to an array mapper can be merge in a range. * This is a "surface" optimization, since the candidate value is compared to the mapper * value by identity. * * @param keys The known keys of the array mapper * @param mapper The array mapper * @param value The value to compare (mapper value) by object identity * @param keyIndex The key to check * @returns A tuple of `[key, inRange]`: * * `key` is the key (that can be a range) to set to the mapper * * `inRange` indicates that the new entry was merged in an existing range */ function keyInRange(keys: Keys[], mapper: Mappers<any>, value: any, keyIndex: number): [string, boolean]; /** * Merge adjacent ranges of an array mappers when possible. * This is a "deep" optimization, since the candidate value is compared structurally * to the mapper value. * * @param mappers An array mappers */ function optimizeRanges(mappers: Mappers.Optimizable): void; /** * Prune empty mappings. * * @param mappers The mappers to clear. */ function pruneEmptyMappings(mappers: Mappers<any>): void; } } /** * The **replacer** to use as a second argument in `JSON.stringify()`. * * After stringification, a reviver is available to restore the * original data structure. This function is a stateful function * that can be used once and only once in `JSON.stringify()`. * * @paramType Type The type to replace, to have it in the reviver. * * @see [[Replacer.getReviver]] * @see [[Jsonizer.replacer]] * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=reviver-generation) */ interface Replacer<Type = any> { /** * Just the signature of the replacer argument in `JSON.stringify()` */ (this: any, key: string, value: any): any; /** * Indicates if some mappings were collected. */ isEmpty(): boolean; /** * Get the revivers collected after `JSON.stringify()` when used with * this `Replacer` (can lead to an empty replacer), or `null` if * the valued stringified was a primitive. * * @paramType Target The target of the reviver */ getReviver<Target = Type>(): Reviver<Target>; /** * Get the mappers collected after `JSON.stringify()` when used with * this `Replacer` (can lead to an empty replacer), or `null` if * the valued stringified was a primitive. * * @paramType Target The target of the reviver * * @see [[getReviver]] */ getMappers<Target>(): Mappers<Target> | null; /** * The JSON string representation of this replacer, after being used * in `JSON.stringify()`, maybe `undefined`. */ toString(): string; } /** * The nested **reviver** to pass as the second argument of `JSON.parse()`. * * It can be created : * * * with [[Jsonizer.reviver]]() * * with [Reviver()](#reviver-2) as a class decorator or a function * * with a [[Replacer]] created with [[Jsonizer.replacer]]() after `JSON.stringify()` * * Can also be invoked directly on a data structure to augment. * * @see [User guide - `Jsonizer.reviver()`](https://badcafe.github.io/jsonizer/#/README?id=revivers-mappings) * @see [User guide - Classes](https://badcafe.github.io/jsonizer/#/README?id=classes) * @see [User guide - Replacer](https://badcafe.github.io/jsonizer/#/README?id=reviver-generation) */ type Reviver<Target = any> = { /** * Just the signature of the reviver argument in `JSON.parse()` */ (key: string, value: any): Target; /** * Do revive some data: apply recursively the mappers on a data * structure (not a JSON string) * * @param data The data structure to transform * @returns The transformed data structure */ (// this: any[], // stack data: object | any[] | string | number | boolean | null): Target; } & { [key: string]: Reviver<any>; } & { [index: number]: Reviver<any>; }; /** * A class decorator that contains recipes to revive an instance from * its JSON representation. * * ```typescript * โ“Reviver<Person>({ // ๐Ÿ‘ˆ bind the reviver to the class * '.': ({name, birthDate}) => new Person(name, birthDate), // ๐Ÿ‘ˆ instance builder * birthDate: Date // ๐Ÿ‘ˆ field mapper * }) * class Person { * constructor( // all fields are passed as arguments to the constructor * public name: string, * public birthDate: Date * ) {} * } * ``` * * It contains mappers for reviving individual fields to specific instances and * a builder function to construct the final object from revived fields. * * Basically, it performs the following tasks: * * * Calls `Reviver.create()` with the given arguments * * Bound the created reviver to the decorated class * * Set the decorated class in the default namespace if it has no namespace * * ## Usage on interfaces and type aliases * * > The following requires to be compiled with `@badcafe/ts-plugin` * * ```typescript * interface Person { * public name: string, * public birthDate: Date * } * * Reviver<Person>({ * birthDate: Date // ๐Ÿ‘ˆ field mapper * })() // ๐Ÿ‘ˆ bind the reviver to the interface * ``` * * Or to get a reference to the reviver : * * ```typescript * interface Person { * public name: string, * public birthDate: Date * } * * const Person = Reviver<Person>({ * birthDate: Date // ๐Ÿ‘ˆ field mapper * })() // ๐Ÿ‘ˆ bind the reviver to the interface * ``` * * @paramType Target - The actual class to revive * @paramType Source - The JSON representation of `Target` is by default the structural `Target` itself * * @param mappers The mappers of the source fields. * * @see [[Reviver.get]] * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=classes) */ declare function Reviver<Target, Source = Target, Any extends string = '*', Self extends string = '.', Delim extends string = Source extends Array<any> ? '-' : '/'>(mappers: Mappers<Target, Source, Any, Self, Delim>): <Type extends null | Class = null>(target?: Type) => Type extends null ? Class<Target> & { (this: any, key: string, value: any): Target; } : void; /** * Handle revivers. */ declare namespace Reviver { /** * @ignore * * Metadata key. */ const $: unique symbol; /** * Get the reviver of a class. * * @param target The target class or a deferred reference to the target class * e.g. `() => TheClass`, * by default will get the reviver's Reviver. */ function get<T = Reviver>(target?: Class<T> | (() => Class<T>)): Reviver<T>; /** * Revive a reviver from a JSON structure. * * @paramType Target - The type that the reviver can revive. * * @param value A reviver structure. * @returns A reviver instance. */ function revive<Target>(value: Mappers<internal.Reviver> | null): Reviver<Target>; /** * @ignore * * Either a class decorated with a reviver, or the qualified name of that class. * * @see [[Namespace]] */ type Reference = Class | (() => Class) | string; } /** * [User guide](https://badcafe.github.io/jsonizer) */ declare namespace Jsonizer { /** * The internal namespace is `'npm:@badcafe/jsonizer'`, * don't use it for your own libraries or app. */ const NAMESPACE = "npm:@badcafe/jsonizer"; /** * An alternative key for objects to bind to a custom * `toJSON()` function ; when serialized with * [[Jsonizer.replacer]] or with [[Jsonizer.REPLACER]], * the method bound to this symbol will be used instead * of the standard method. * * > If you want to left as is the default `toJSON()` function * > of an existing class, you may want instead create * > a custom `[Jsonizer.toJSON]()` function. * * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=jsonizertojson) */ const toJSON: unique symbol; /** * Create a nested reviver that can convert a value and its * nested values. * * ## Usage * * ```typescript * const personReviver = Jsonizer.reviver<typeof person>({ * birthDate: Date * }); * ``` * * @param mappers Mapper for the current value and children values * @return A reviver that can be passed to `JSON.parse()` or * that can be used as a reviver object on which one can * directly call `.revive()`. That reviver can also be * itself serialized and revived. * * @paramType Target - The actual class to revive * @paramType Source - The JSON representation of `Target` is by default the structural `Target` itself * * @see [User guide - Objects](https://badcafe.github.io/jsonizer/#/README?id=objects) * @see [User guide - Arrays](https://badcafe.github.io/jsonizer/#/README?id=arrays) * @see [User guide - Nested mapping](https://badcafe.github.io/jsonizer/#/README?id=nested-mappings) * @see [User guide - Tuples](https://badcafe.github.io/jsonizer/#/README?id=tuples) */ function reviver<Target, Source = Target, Any extends string = '*', Self extends string = '.', Delim extends string = Source extends Array<any> ? '-' : '/'>(mappers: Mappers<Target, Source, Any, Self, Delim>): Reviver<Target>; /** * Create a `replacer` function to use with `JSON.stringify()` ; * this replacer can capture the revivers. * * > During stringification, custom `[Jsonizer.toJSON]()` functions * will be used in preference over the standard `toJSON()` method. * * ## Capture phase * * ``` * // user structure to stringify : * const data = getDataSomehow(); // your code * // create a context for the capture : * const replacer = Jsonizer.replacer(); * // stringify with our replacer that also capture the mappings * const jsonData = JSON.stringify(data, replacer); * ``` * * Next, the `jsonData` string result can be send elsewhere with * its `reviver` object, that can be stringified too : * * ``` * // every class decorated with `@Reviver` were captured * const jsonReviver = replacer.toString() * // same as : * const jsonReviver = JSON.stringify(replacer.getReviver()); * * sendOrStoreOrWhatever(jsonData, jsonReviver); * ``` * * ## Revive phase * * To respawn the data, pass the reviver to `JSON.parse()` : * * ``` * function getData(jsonData: string, jsonReviver: string) { * // revive the reviver ๐Ÿ‘‡ get the reviver for revivers * const reviver = JSON.parse(jsonReviver, Reviver.get()); * // revive the data with the reviver * return JSON.parse(json, reviver); * } * ``` * * @paramType Type The type to replace, to have it in the reviver. * * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=replacer) */ function replacer<Type = any>(): Replacer<Type>; /** * Just a placeholder `replacer()` that tells to use customs * functions `[Jsonizer.toJSON]()` instead of the standard * method. * * > `JSON.stringify()` used without `Jsonizer.replacer()` * > or without `Jsonizer.REPLACER` won't use custom * > `[Jsonizer.toJSON]()` functions. * * ## Usage * * ```typescript * JSON.stringify(data, Jsonizer.REPLACER); * ``` * * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=jsonizertojson) */ const REPLACER: (key: string, value: any) => any; /** * Helper mapping functions for the `Self` key `'.'` */ namespace Self { /** * A mapper class that keep unchanged a value ; this * is useful when the `'*'` (Any) key is used to map * every value, except some of them that would be mapped * to the identity. * * ```typescript * @Reviver<Hobby>({ * '.': Jsonizer.Self.apply(Hobby), * '*': Date // ๐Ÿ‘ˆ matches any field... * hobby: Jsonizer.Self.Identity // ๐Ÿ‘ˆ ...except 'hobby', kept unchanged * }) * ``` * * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=pass-through-identity) */ const Identity: any; /** * A mapping function for the `Self` key `'.'` that * passes all the data in order to the constructor. * * ``` * { '.': Jsonizer.Self.apply(Foo) } * ``` * is the same as * ``` * { '.': args => new Foo(...Object.values(args)) } * ``` * * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=self-apply) */ function apply<T>(clazz: Class.Concrete<T>): (args: object) => T; /** * A mapping function for the `Self` key `'.'` that * assigns all the data properties to the new object. * * ``` * { '.': Jsonizer.Self.assign(Foo) } * ``` * is the same as * ``` * { '.': args => { * const foo = new Foo(); * Object.assign(foo, args); * return foo; * } * ``` * * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=self-assign) */ function assign<T>(clazz: Class.Concrete<T>): (args: object) => T; /** * A mapping function for the `Self` key `'.'` that * endorse the given class. * * ``` * { '.': Jsonizer.Self.endorse(Foo) } * ``` * is the same as * ``` * { '.': args => { * Object.setPrototypeOf(args, clazz.prototype); * return args; * } * ``` * * @see [User guide](https://badcafe.github.io/jsonizer/#/README?id=self-endorse) */ function endorse<T>(clazz: Class.Concrete<T>): (args: object) => T; } } declare const Mappers$: unique symbol; declare const Clazz: unique symbol; /** * Jsonizer algorithms */ declare namespace internal { /** * Reviver for Reviver, a.k.a. "meta" Reviver * Since Reviver is already a decorator function, * we are setting this internal class with a qualified name * in order to retrieve it easily ; and it can be serialized */ class Reviver extends Function { static $: symbol; [Mappers$]: Mappers<Reviver, any>; [Clazz]?: Class<any>; constructor(mappers: Mappers<Reviver, any> | null, clazz?: Class<any>); toJSON(key: string): any; static revive<Target>(ctxt: any[], // stack json: object | any[] | string | number | boolean | null, mappers: Mappers<Target>): Target; } function toJSON(mappers: Mappers<Reviver, any> | Reviver.Reference, recurse?: boolean): any; /** * Replacer implementation * * ## Internal detail * * Things to be aware of : * * ``` * JSON.stringify(new Date(), (k, v) => console.log(k, v, typeof v, v instanceof Date)) * 2021-05-18T14:01:10.995Z string false * ``` * * ...well, when the replacer is called with objects that has a `toJSON()` function, * the value passed is already transformed (the data is stringified), and this is * definitively what we don't want. Therefore, for the root there is a special handling * directly in `JSON.stringify()`. * * @see [[init]] */ class Replacer extends Function { end: boolean; stack: Replacer.Context[]; stringify: boolean; init(value: any): void; constructor(); getReviver<Target>(): ReviverAPI<Target> | null; getMappers<Target>(): Mappers<Target> | null; isEmpty(): boolean; static getReviverClass(value: any): string | undefined; /** * Set a mapper on top of the stack. Primitives are not mapped. * The top of the stack has to be an object. */ private setMapper; pushContext(context: Replacer.Context): Replacer.Context; /** * The actual 'replacer' function that will be invoked while * running `JSON.stringify()` */ replace(key: string, value: any): any; private unStack; toString(): string; } namespace Replacer { const UNMAPPED_KEYS: unique symbol; type Context = Mappers.Optimizable & { parent?: Context; isArray: boolean | undefined; size: number; key: string | number; before: any; }; } } declare const ReviverAPI: typeof Reviver; type ReviverAPI<Target> = Reviver<Target>; /** * A replacement function of `JSON.stringify()`. * * > For internal use only, except if you see such a warning in the console : * > ``` * > "Unable to patch JSON.stringify(), use stringify() instead" * > ``` * > it means that your runtime environment doesn't allow patching then * > use this function instead of `JSON.stringify()`. * * ## Note * * This function is just a wrapper around `JSON.stringify()` * used to handle properly the root element. */ declare function stringify(value: any, replacer?: (number | string)[] | ((this: any, key: string, value: any) => any) | null | undefined, space?: string | number | undefined): string; /** * A rewrite of `JSON.parse()` signature : if the reviver of `JSON.parse()` * is a nested reviver, we may know the return type. */ declare global { interface JSON { /** * Converts a JavaScript Object Notation (JSON) string into an object. * @param text A valid JSON string. * @param reviver A function that transforms the results. This function is called for each member of the object. * If a member contains nested objects, the nested objects are transformed before the parent object is. */ parse<Target = any>(text: string, reviver?: Target extends Reviver ? Target : ((this: any, key: string, value: any) => any)): Target extends Reviver<infer Revived> ? Revived : Target; } } declare global { interface Error { [Jsonizer.toJSON](): string; } } declare global { interface RegExp { [Jsonizer.toJSON](): string; } } //# sourceMappingURL=builtin.d.ts.map export { Class, Errors, Jsonizer, Mappers, Namespace, Replacer, Reviver, stringify };