@badcafe/jsonizer
Version:
Structural reviving for JSON
1,181 lines (1,173 loc) โข 42.1 kB
text/typescript
/** 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 };