tupleson
Version:
A hackable JSON serializer/deserializer
134 lines (113 loc) • 3.08 kB
text/typescript
const brand = Symbol("branded");
export type TsonBranded<TType, TBrand> = TType & { [brand]: TBrand };
export type TsonNonce = TsonBranded<string, "TsonNonce">;
export type TsonTypeHandlerKey = TsonBranded<string, "TsonTypeHandlerKey">;
export type TsonSerializedValue = unknown;
export type TsonTuple = [TsonTypeHandlerKey, TsonSerializedValue, TsonNonce];
// there's probably a better way of getting this type
export type TsonAllTypes =
| "bigint"
| "boolean"
| "function"
| "number"
| "object"
| "string"
| "symbol"
| "undefined";
type SerializedType =
| Record<string, unknown>
| boolean
| number
| string
| unknown[];
export interface TsonTransformerNone {
async?: false;
deserialize?: never;
/**
* The key to use when serialized
*/
key?: never;
serialize?: never;
serializeIterator?: never;
}
export interface TsonTransformerSerializeDeserialize<
TValue,
TSerializedType extends SerializedType,
> {
async?: false;
/**
* From JSON-serializable value
*/
deserialize: (v: TSerializedType) => TValue;
/**
* The key to use when serialized
*/
key: string;
/**
* JSON-serializable value
*/
serialize: (v: TValue) => TSerializedType;
serializeIterator?: never;
}
export type TsonTransformer<TValue, TSerializedType extends SerializedType> =
| TsonTransformerNone
| TsonTransformerSerializeDeserialize<TValue, TSerializedType>;
export interface TsonTypeTesterPrimitive {
/**
* The type of the primitive
*/
primitive: TsonAllTypes;
/**
* Test if the value is of this type
*/
test?: (v: unknown) => boolean;
}
export interface TsonTypeTesterCustom {
/**
* The type of the primitive
*/
primitive?: never;
/**
* Test if the value is of this type
*/
test: (v: unknown) => boolean;
}
type TsonTypeTester = TsonTypeTesterCustom | TsonTypeTesterPrimitive;
export type TsonType<
/**
* The type of the value
*/
TValue,
/**
* JSON-serializable value how it's stored after it's serialized
*/
TSerializedType extends SerializedType,
> = TsonTypeTester & TsonTransformer<TValue, TSerializedType>;
export interface TsonOptions {
/**
* The nonce function every time we start serializing a new object
* Should return a unique value every time it's called
* @default `${crypto.randomUUID} if available, otherwise a random string generated by Math.random`
*/
nonce?: () => number | string;
/**
* The list of types to use
*/
types: (TsonType<any, any> | TsonType<any, never>)[];
}
export const serialized = Symbol("serialized");
export interface TsonSerialized<TValue = unknown> {
json: TsonSerializedValue;
nonce: TsonNonce;
[serialized]: TValue;
}
export type TsonSerializeFn = <TValue>(obj: TValue) => TsonSerialized<TValue>;
export type TsonDeserializeFn = <TValue>(
data: TsonSerialized<TValue>,
) => TValue;
export type TsonStringified<TValue> = string & { [serialized]: TValue };
export type TsonStringifyFn = <TValue>(
obj: TValue,
space?: number | string,
) => TsonStringified<TValue>;
export type TsonParseFn = <TValue>(string: TsonStringified<TValue>) => TValue;