refactor Serialize a bit, add to readme
This commit is contained in:
parent
5d4591e63c
commit
584f4f1bb7
15 changed files with 202 additions and 36 deletions
91
README.md
91
README.md
|
@ -24,20 +24,111 @@ there are four interfaces which are relevant to `serde-ts` users.
|
|||
|
||||
## `Serializer`
|
||||
|
||||
```ts
|
||||
interface ISerializer<T> {
|
||||
serializeAny?(value: any): T
|
||||
serializeBoolean(value: boolean): T
|
||||
serializeNumber(value: number): T
|
||||
serializeBigInt(value: bigint): T
|
||||
serializeString(value: string): T
|
||||
serializeSymbol(value: symbol): T
|
||||
serializeNull(): T
|
||||
serializeObject(): ISerializeObject<T>
|
||||
serializeClass(name: string): ISerializeObject<T>
|
||||
}
|
||||
```
|
||||
|
||||
a `Serializer` is responsible for transforming the internal `serde-ts` data model into the target serialization format. this means that it will have a `serialize<Type>` function for each of `serde-ts`'s data types.
|
||||
|
||||
## `Deserializer`
|
||||
```ts
|
||||
interface IDeserializer {
|
||||
deserializeAny<T>(visitor: Partial<IVisitor<T>>): T
|
||||
deserializeBoolean<T>(visitor: Partial<IVisitor<T>>): T
|
||||
deserializeNumber<T>(visitor: Partial<IVisitor<T>>): T
|
||||
deserializeBigInt<T>(visitor: Partial<IVisitor<T>>): T
|
||||
deserializeString<T>(visitor: Partial<IVisitor<T>>): T
|
||||
deserializeSymbol<T>(visitor: Partial<IVisitor<T>>): T
|
||||
deserializeNull<T>(visitor: Partial<IVisitor<T>>): T
|
||||
deserializeObject<T>(visitor: Partial<IVisitor<T>>): T
|
||||
deserializeIterable<T>(visitor: Partial<IVisitor<T>>): T
|
||||
deserializeFunction<T>(visitor: Partial<IVisitor<T>>): T
|
||||
}
|
||||
```
|
||||
|
||||
a `Deserializer` is responsible for transforming the target serialization format into the internal `serde-ts` data model. it will have a `deserialize<Type>` for each of the `serde-ts`'s data types.
|
||||
|
||||
## `Serialize`
|
||||
```ts
|
||||
interface Serialize<T> {
|
||||
<U>(serializer: ISerializer<T>, value: U): T
|
||||
}
|
||||
```
|
||||
|
||||
`Serialize` is responsible for transforming a value *to* the internal data model. for example, a `Vector2` may be internally serialized as an iterable of numbers.
|
||||
|
||||
## `Deserialize`
|
||||
```ts
|
||||
interface Deserialize<T> {
|
||||
(deserializer: IDeserializer): T
|
||||
}
|
||||
```
|
||||
|
||||
`Deserialize` is responsible for transforming a value *from* the internal data model. it is the inverse of `Serialize`. depending on the `Deserialize` target, it may accept an iterable of numbers and produce a `Vector2`.
|
||||
|
||||
|
||||
# Example
|
||||
|
||||
in simple cases, a formatter for `serde-ts` will expose a pair of functions like `toString` and `fromString` which will handle serializing and deserializing for you.
|
||||
|
||||
in more complex cases, you still won't be implementing `Serializer`/`Deserializer`, only consuming them with `Serialize`/`Deserialize`. assuming we're using a format which reads/writes JSON
|
||||
|
||||
```ts
|
||||
import { registerSerialize, registerDeserialize } from 'serde'
|
||||
import { ISerializer } from 'serde/ser'
|
||||
import { IDeserializer, IIterableAccess } from 'serde/de'
|
||||
import { fromString, toString } from 'serde-json-ts'
|
||||
|
||||
class Vector2 {
|
||||
x: number
|
||||
y: number
|
||||
|
||||
constructor(x: number, y: number) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
}
|
||||
}
|
||||
|
||||
// we're registering to the global serde registry
|
||||
registerSerialize(Vector2, (serializer: ISerializer<void>, value: Vector2) => {
|
||||
const iter = serializer.serializeIterable() // returns an ISerializeIterable<void>
|
||||
iter.serializeElement(value.x)
|
||||
iter.serializeElement(value.y)
|
||||
return iter.end()
|
||||
})
|
||||
|
||||
registerDeserialize(Vector2, (deserializer: IDeserializer) => deserializer.deserializeIterable({
|
||||
// we could implement visitNumber here, but we'll let the default
|
||||
// deserializer handle it
|
||||
visitIterable(access: IIterableAccess) {
|
||||
const elements = []
|
||||
|
||||
for (const item of access) {
|
||||
elements.push(item as number)
|
||||
}
|
||||
|
||||
return new Vector2(elements[0], elements[1])
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const one = new Vector2(1, 1)
|
||||
const serialized = toString(one)
|
||||
console.log(serialized)
|
||||
// "[1, 1]"
|
||||
|
||||
const deserializedOne = fromString(serialized, Vector2)
|
||||
console.log(deserializedOne)
|
||||
// Vector2 { x: 1, y: 1 }
|
||||
```
|
||||
|
||||
|
|
8
dist/de/interface.d.ts
vendored
8
dist/de/interface.d.ts
vendored
|
@ -16,8 +16,8 @@ export declare abstract class MapAccess {
|
|||
abstract nextKeySeed<T, K extends Deserialize<T>>(seed: K): IteratorResult<T>;
|
||||
abstract nextValueSeed<T, V extends Deserialize<T>>(seed: V): IteratorResult<T>;
|
||||
nextEntrySeed<TK, TV, K extends Deserialize<TK>, V extends Deserialize<TV>>(kseed: K, vseed: V): IteratorResult<[TK, TV]>;
|
||||
abstract nextKey<T>(): IteratorResult<T>;
|
||||
abstract nextValue<V>(): IteratorResult<V>;
|
||||
nextKey<T>(): IteratorResult<T>;
|
||||
nextValue<V>(): IteratorResult<V>;
|
||||
nextEntry<K, V>(): IteratorResult<[K, V]>;
|
||||
private generate;
|
||||
keys<T, K extends Deserialize<T>>(seed?: K): Iterator<T>;
|
||||
|
@ -26,12 +26,14 @@ export declare abstract class MapAccess {
|
|||
[Symbol.iterator]<K, V>(): Iterator<K, V>;
|
||||
}
|
||||
export interface IIterableAccess {
|
||||
nextElementSeed<T, D extends Deserialize<T>>(seed: D): IteratorResult<T>;
|
||||
nextElement<T>(): IteratorResult<T>;
|
||||
sizeHint?(): Nullable<number>;
|
||||
[Symbol.iterator]<T>(): Iterator<T>;
|
||||
}
|
||||
export declare abstract class IterableAccess implements IIterableAccess {
|
||||
abstract nextElement<T>(): IteratorResult<T>;
|
||||
abstract nextElementSeed<T, D extends Deserialize<T>>(seed: D): IteratorResult<T>;
|
||||
nextElement<T>(): IteratorResult<T>;
|
||||
[Symbol.iterator]<T>(): Iterator<T>;
|
||||
}
|
||||
export declare function isVisitor<T>(visitor: any): visitor is IVisitor<T>;
|
||||
|
|
14
dist/de/interface.js
vendored
14
dist/de/interface.js
vendored
|
@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|||
exports.IterableAccess = exports.MapAccess = void 0;
|
||||
exports.isVisitor = isVisitor;
|
||||
const utils_1 = require("../utils");
|
||||
const generic_1 = require("./generic");
|
||||
class MapAccess {
|
||||
nextEntrySeed(kseed, vseed) {
|
||||
const key = this.nextKeySeed(kseed);
|
||||
|
@ -14,6 +15,12 @@ class MapAccess {
|
|||
}
|
||||
return utils_1.IterResult.Done();
|
||||
}
|
||||
nextKey() {
|
||||
return this.nextKeySeed(generic_1.GenericSeed.deserialize);
|
||||
}
|
||||
nextValue() {
|
||||
return this.nextValueSeed(generic_1.GenericSeed.deserialize);
|
||||
}
|
||||
nextEntry() {
|
||||
const key = this.nextKey();
|
||||
if (!key.done) {
|
||||
|
@ -51,9 +58,12 @@ class MapAccess {
|
|||
}
|
||||
exports.MapAccess = MapAccess;
|
||||
class IterableAccess {
|
||||
*[Symbol.iterator]() {
|
||||
nextElement() {
|
||||
return this.nextElementSeed(generic_1.GenericSeed.deserialize);
|
||||
}
|
||||
[Symbol.iterator]() {
|
||||
return {
|
||||
next: this.nextElement
|
||||
next: this.nextElement.bind(this)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
10
dist/registry.d.ts
vendored
10
dist/registry.d.ts
vendored
|
@ -1,11 +1,11 @@
|
|||
import { Deserialize } from './de';
|
||||
import { Serialize } from './ser';
|
||||
export declare class Registry {
|
||||
serializers: Map<Function, Serialize<any>>;
|
||||
serializers: Map<Function, Serialize<any, any>>;
|
||||
deserializers: Map<Function, Deserialize<any>>;
|
||||
registerSerializer<T>(ctor: Function, serialize: Serialize<T>): void;
|
||||
registerDeserializer<T>(ctor: Function, deserialize: Deserialize<T>): void;
|
||||
registerSerialize<T, U>(ctor: Function, serialize: Serialize<T, U>): void;
|
||||
registerDeserialize<T>(ctor: Function, deserialize: Deserialize<T>): void;
|
||||
}
|
||||
export declare const GlobalRegistry: Registry;
|
||||
export declare const registerSerializer: <T>(ctor: Function, serialize: Serialize<T>) => void;
|
||||
export declare const registerDeserializer: <T>(ctor: Function, deserialize: Deserialize<T>) => void;
|
||||
export declare const registerSerialize: <T, U>(ctor: Function, serialize: Serialize<T, U>) => void;
|
||||
export declare const registerDeserialize: <T>(ctor: Function, deserialize: Deserialize<T>) => void;
|
||||
|
|
10
dist/registry.js
vendored
10
dist/registry.js
vendored
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.registerDeserializer = exports.registerSerializer = exports.GlobalRegistry = exports.Registry = void 0;
|
||||
exports.registerDeserialize = exports.registerSerialize = exports.GlobalRegistry = exports.Registry = void 0;
|
||||
class Registry {
|
||||
constructor() {
|
||||
Object.defineProperty(this, "serializers", {
|
||||
|
@ -16,14 +16,14 @@ class Registry {
|
|||
value: new Map()
|
||||
});
|
||||
}
|
||||
registerSerializer(ctor, serialize) {
|
||||
registerSerialize(ctor, serialize) {
|
||||
this.serializers.set(ctor, serialize);
|
||||
}
|
||||
registerDeserializer(ctor, deserialize) {
|
||||
registerDeserialize(ctor, deserialize) {
|
||||
this.deserializers.set(ctor, deserialize);
|
||||
}
|
||||
}
|
||||
exports.Registry = Registry;
|
||||
exports.GlobalRegistry = new Registry();
|
||||
exports.registerSerializer = exports.GlobalRegistry.registerSerializer.bind(exports.GlobalRegistry);
|
||||
exports.registerDeserializer = exports.GlobalRegistry.registerDeserializer.bind(exports.GlobalRegistry);
|
||||
exports.registerSerialize = exports.GlobalRegistry.registerSerialize.bind(exports.GlobalRegistry);
|
||||
exports.registerDeserialize = exports.GlobalRegistry.registerDeserialize.bind(exports.GlobalRegistry);
|
||||
|
|
3
dist/ser/impl.d.ts
vendored
3
dist/ser/impl.d.ts
vendored
|
@ -1,2 +1,3 @@
|
|||
import { ISerializer } from './interface';
|
||||
export declare function serialize<T, V, S extends ISerializer<T>>(serializer: S, value: V): T;
|
||||
import { Registry } from '../registry';
|
||||
export declare function serialize<T, U, S extends ISerializer<T>>(serializer: S, value: U, registry?: Registry): T;
|
||||
|
|
13
dist/ser/impl.js
vendored
13
dist/ser/impl.js
vendored
|
@ -2,6 +2,7 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.serialize = serialize;
|
||||
const utils_1 = require("../utils");
|
||||
const registry_1 = require("../registry");
|
||||
class UnhandledTypeError extends TypeError {
|
||||
constructor(serializer, value) {
|
||||
super(`unhandled type: '${typeof value}' for serializer ${serializer.constructor.name}`);
|
||||
|
@ -19,7 +20,13 @@ function serializeClass(serializer, value) {
|
|||
const ser = serializer.serializeClass(name);
|
||||
return serializeObject(ser, value);
|
||||
}
|
||||
function serialize(serializer, value) {
|
||||
function getSerialize(value, registry) {
|
||||
if ((0, utils_1.isObject)(value)) {
|
||||
return registry.serializers.get(value.constructor);
|
||||
}
|
||||
return registry.serializers.get(utils_1.PrimitivePrototype[typeof value]) || defaultSerialize;
|
||||
}
|
||||
function defaultSerialize(serializer, value) {
|
||||
switch (typeof value) {
|
||||
case 'string': return serializer.serializeString(value);
|
||||
case 'number': return serializer.serializeNumber(value);
|
||||
|
@ -36,3 +43,7 @@ function serialize(serializer, value) {
|
|||
default: throw new UnhandledTypeError(serializer, value);
|
||||
}
|
||||
}
|
||||
function serialize(serializer, value, registry = registry_1.GlobalRegistry) {
|
||||
const ser = getSerialize(value, registry);
|
||||
return ser(serializer, value);
|
||||
}
|
||||
|
|
6
dist/ser/interface.d.ts
vendored
6
dist/ser/interface.d.ts
vendored
|
@ -27,9 +27,11 @@ export interface ISerializer<T> {
|
|||
serializeSymbol(value: symbol): T;
|
||||
serializeNull(): T;
|
||||
serializeObject(): ISerializeObject<T>;
|
||||
serializeIterable(): ISerializeIterable<T>;
|
||||
serializeClass(name: string): ISerializeObject<T>;
|
||||
}
|
||||
export declare class Serializer<T> implements ISerializer<T> {
|
||||
serializeIterable(): ISerializeIterable<T>;
|
||||
serializeAny(_value: any): T;
|
||||
serializeBoolean(_value: boolean): T;
|
||||
serializeNumber(_value: number): T;
|
||||
|
@ -40,6 +42,6 @@ export declare class Serializer<T> implements ISerializer<T> {
|
|||
serializeObject(): ISerializeObject<T>;
|
||||
serializeClass(_name: string): ISerializeObject<T>;
|
||||
}
|
||||
export interface Serialize<T> {
|
||||
<U, S extends Serializer<U>>(serializer: S, value: T): U;
|
||||
export interface Serialize<T, U> {
|
||||
(serializer: ISerializer<T>, value: U): T;
|
||||
}
|
||||
|
|
3
dist/ser/interface.js
vendored
3
dist/ser/interface.js
vendored
|
@ -12,6 +12,9 @@ class SerializeIterable {
|
|||
}
|
||||
exports.SerializeIterable = SerializeIterable;
|
||||
class Serializer {
|
||||
serializeIterable() {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
serializeAny(_value) {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
|
14
dist/utils.d.ts
vendored
14
dist/utils.d.ts
vendored
|
@ -6,7 +6,8 @@ export type Primitive = string | number | boolean | symbol | bigint | null | und
|
|||
export interface ToString {
|
||||
toString(): string;
|
||||
}
|
||||
export declare function isPlainObject(value: any): boolean;
|
||||
export declare function isObject(value: any): value is object;
|
||||
export declare function isPlainObject(value: any): value is object;
|
||||
export declare function isFunction(value: any): value is Function;
|
||||
export declare function isIterable(value: any): value is Iterable<any>;
|
||||
export declare function isString(value: any): value is string;
|
||||
|
@ -16,3 +17,14 @@ export declare class IterResult {
|
|||
static Next<T>(value: T): IteratorResult<T>;
|
||||
static Done<T>(): IteratorResult<T>;
|
||||
}
|
||||
export declare function Null(...args: any): null;
|
||||
export declare const PrimitivePrototype: Readonly<{
|
||||
readonly undefined: typeof Null;
|
||||
readonly boolean: BooleanConstructor;
|
||||
readonly number: NumberConstructor;
|
||||
readonly bigint: BigIntConstructor;
|
||||
readonly string: StringConstructor;
|
||||
readonly symbol: SymbolConstructor;
|
||||
readonly object: ObjectConstructor;
|
||||
readonly function: FunctionConstructor;
|
||||
}>;
|
||||
|
|
20
dist/utils.js
vendored
20
dist/utils.js
vendored
|
@ -1,11 +1,16 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.IterResult = void 0;
|
||||
exports.PrimitivePrototype = exports.IterResult = void 0;
|
||||
exports.isObject = isObject;
|
||||
exports.isPlainObject = isPlainObject;
|
||||
exports.isFunction = isFunction;
|
||||
exports.isIterable = isIterable;
|
||||
exports.isString = isString;
|
||||
exports.isNumber = isNumber;
|
||||
exports.Null = Null;
|
||||
function isObject(value) {
|
||||
return typeof value === 'object';
|
||||
}
|
||||
function isPlainObject(value) {
|
||||
return Object.getPrototypeOf(value) === Object.prototype;
|
||||
}
|
||||
|
@ -30,3 +35,16 @@ class IterResult {
|
|||
}
|
||||
}
|
||||
exports.IterResult = IterResult;
|
||||
function Null(...args) {
|
||||
return null;
|
||||
}
|
||||
exports.PrimitivePrototype = Object.freeze({
|
||||
undefined: Null,
|
||||
boolean: Boolean,
|
||||
number: Number,
|
||||
bigint: BigInt,
|
||||
string: String,
|
||||
symbol: Symbol,
|
||||
object: Object,
|
||||
function: Function
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { IterResult, Nullable } from '../utils'
|
||||
import { GenericSeed } from './generic'
|
||||
|
||||
export interface IMapAccess {
|
||||
nextKeySeed<T, K extends Deserialize<T>>(seed: K): IteratorResult<T>
|
||||
|
@ -32,8 +33,13 @@ export abstract class MapAccess {
|
|||
return IterResult.Done()
|
||||
}
|
||||
|
||||
abstract nextKey<T>(): IteratorResult<T>
|
||||
abstract nextValue<V>(): IteratorResult<V>
|
||||
nextKey<T>(): IteratorResult<T> {
|
||||
return this.nextKeySeed(GenericSeed.deserialize)
|
||||
}
|
||||
|
||||
nextValue<V>(): IteratorResult<V> {
|
||||
return this.nextValueSeed(GenericSeed.deserialize)
|
||||
}
|
||||
|
||||
nextEntry<K, V>(): IteratorResult<[K, V]> {
|
||||
const key = this.nextKey() as IteratorResult<K>
|
||||
|
@ -87,17 +93,22 @@ export abstract class MapAccess {
|
|||
}
|
||||
|
||||
export interface IIterableAccess {
|
||||
nextElementSeed<T, D extends Deserialize<T>>(seed: D): IteratorResult<T>
|
||||
nextElement<T>(): IteratorResult<T>
|
||||
sizeHint?(): Nullable<number>
|
||||
[Symbol.iterator]<T>(): Iterator<T>
|
||||
}
|
||||
|
||||
export abstract class IterableAccess implements IIterableAccess {
|
||||
abstract nextElement<T>(): IteratorResult<T>
|
||||
abstract nextElementSeed<T, D extends Deserialize<T>>(seed: D): IteratorResult<T>
|
||||
|
||||
*[Symbol.iterator]<T>(): Iterator<T> {
|
||||
nextElement<T>(): IteratorResult<T> {
|
||||
return this.nextElementSeed(GenericSeed.deserialize)
|
||||
}
|
||||
|
||||
[Symbol.iterator]<T>(): Iterator<T> {
|
||||
return {
|
||||
next: this.nextElement
|
||||
next: this.nextElement.bind(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,19 +2,19 @@ import { Deserialize } from './de'
|
|||
import { Serialize } from './ser'
|
||||
|
||||
export class Registry {
|
||||
serializers: Map<Function, Serialize<any>> = new Map()
|
||||
serializers: Map<Function, Serialize<any, any>> = new Map()
|
||||
deserializers: Map<Function, Deserialize<any>> = new Map()
|
||||
|
||||
registerSerializer<T>(ctor: Function, serialize: Serialize<T>) {
|
||||
registerSerialize<T, U>(ctor: Function, serialize: Serialize<T, U>) {
|
||||
this.serializers.set(ctor, serialize)
|
||||
}
|
||||
|
||||
registerDeserializer<T>(ctor: Function, deserialize: Deserialize<T>) {
|
||||
registerDeserialize<T>(ctor: Function, deserialize: Deserialize<T>) {
|
||||
this.deserializers.set(ctor, deserialize)
|
||||
}
|
||||
}
|
||||
|
||||
export const GlobalRegistry = new Registry()
|
||||
export const registerSerializer = GlobalRegistry.registerSerializer.bind(GlobalRegistry)
|
||||
export const registerDeserializer = GlobalRegistry.registerDeserializer.bind(GlobalRegistry)
|
||||
export const registerSerialize = GlobalRegistry.registerSerialize.bind(GlobalRegistry)
|
||||
export const registerDeserialize = GlobalRegistry.registerDeserialize.bind(GlobalRegistry)
|
||||
|
||||
|
|
|
@ -23,12 +23,12 @@ function serializeClass<T, U extends object, S extends ISerializer<T>>(serialize
|
|||
return serializeObject(ser, value)
|
||||
}
|
||||
|
||||
function getSerialize<T, U>(value: U, registry: Registry): Serialize<T> {
|
||||
function getSerialize<T, U>(value: U, registry: Registry): Serialize<T, U> {
|
||||
if (isObject(value)) {
|
||||
return registry.serializers.get(value.constructor) as Serialize<T>
|
||||
return registry.serializers.get(value.constructor) as Serialize<T, U>
|
||||
}
|
||||
|
||||
return registry.serializers.get(PrimitivePrototype[typeof value]) || defaultSerialize as Serialize<T>
|
||||
return registry.serializers.get(PrimitivePrototype[typeof value]) || defaultSerialize as Serialize<T, U>
|
||||
}
|
||||
function defaultSerialize<T, U, S extends ISerializer<T>>(serializer: S, value: U): T {
|
||||
switch (typeof value) {
|
||||
|
|
|
@ -35,10 +35,15 @@ export interface ISerializer<T> {
|
|||
serializeSymbol(value: symbol): T
|
||||
serializeNull(): T
|
||||
serializeObject(): ISerializeObject<T>
|
||||
serializeIterable(): ISerializeIterable<T>
|
||||
serializeClass(name: string): ISerializeObject<T>
|
||||
}
|
||||
|
||||
export class Serializer<T> implements ISerializer<T> {
|
||||
serializeIterable(): ISerializeIterable<T> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
serializeAny(_value: any): T {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
@ -76,7 +81,7 @@ export class Serializer<T> implements ISerializer<T> {
|
|||
}
|
||||
}
|
||||
|
||||
export interface Serialize<T> {
|
||||
<U>(serializer: ISerializer<T>, value: U): T
|
||||
export interface Serialize<T, U> {
|
||||
(serializer: ISerializer<T>, value: U): T
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue