i hate typescript generics!! i hate typescript generics!!
This commit is contained in:
parent
62e9563b8a
commit
b735066f1c
4 changed files with 83 additions and 7 deletions
43
README.md
Normal file
43
README.md
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# serde-ts
|
||||||
|
|
||||||
|
a library for serializing and deserializing javascript objects
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
this library makes no assumptions about formats and loosely follows the architecture of Rust's [serde](https://serde.rs/) crate. the major difference are the particular data types. serde-ts's internal data model contains the following types
|
||||||
|
|
||||||
|
## `serde-ts` data types
|
||||||
|
* null/undefined
|
||||||
|
* boolean
|
||||||
|
* number
|
||||||
|
* bigint
|
||||||
|
* string
|
||||||
|
* symbol
|
||||||
|
* function
|
||||||
|
* object
|
||||||
|
* iterable
|
||||||
|
* class
|
||||||
|
|
||||||
|
with the exception of iterables and classes, these types were chosen as they are the ones provided by Javascript's native `typeof` operator which is used internally for type checking.
|
||||||
|
|
||||||
|
there are four interfaces which are relevant to `serde-ts` users.
|
||||||
|
|
||||||
|
## `Serializer`
|
||||||
|
|
||||||
|
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`
|
||||||
|
|
||||||
|
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`
|
||||||
|
|
||||||
|
`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`
|
||||||
|
|
||||||
|
`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`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { ISerializeObject, ISerializer } from './interface'
|
import { ISerializeObject, ISerializer, Serialize } from './interface'
|
||||||
import { isPlainObject } from '../utils'
|
import { isObject, isPlainObject, PrimitivePrototype } from '../utils'
|
||||||
|
import { GlobalRegistry, Registry } from '../registry'
|
||||||
|
|
||||||
class UnhandledTypeError extends TypeError {
|
class UnhandledTypeError extends TypeError {
|
||||||
constructor(serializer: ISerializer<unknown>, value: any) {
|
constructor(serializer: ISerializer<unknown>, value: any) {
|
||||||
|
@ -7,7 +8,7 @@ class UnhandledTypeError extends TypeError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function serializeObject<T, V extends object, S extends ISerializeObject<T>>(serializer: S, obj: V): T {
|
function serializeObject<T, U extends object, S extends ISerializeObject<T>>(serializer: S, obj: U): T {
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
const value = obj[key]
|
const value = obj[key]
|
||||||
serializer.serializeEntry(key, value)
|
serializer.serializeEntry(key, value)
|
||||||
|
@ -16,13 +17,21 @@ function serializeObject<T, V extends object, S extends ISerializeObject<T>>(ser
|
||||||
return serializer.end()
|
return serializer.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
function serializeClass<T, V extends object, S extends ISerializer<T>>(serializer: S, value: V): T {
|
function serializeClass<T, U extends object, S extends ISerializer<T>>(serializer: S, value: U): T {
|
||||||
const name = value.constructor.name
|
const name = value.constructor.name
|
||||||
const ser = serializer.serializeClass(name)
|
const ser = serializer.serializeClass(name)
|
||||||
return serializeObject(ser, value)
|
return serializeObject(ser, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serialize<T, V, S extends ISerializer<T>>(serializer: S, value: V): T {
|
function getSerialize<T, U>(value: U, registry: Registry): Serialize<T> {
|
||||||
|
if (isObject(value)) {
|
||||||
|
return registry.serializers.get(value.constructor) as Serialize<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
return registry.serializers.get(PrimitivePrototype[typeof value]) || defaultSerialize as Serialize<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultSerialize<T, U, S extends ISerializer<T>>(serializer: S, value: U): T {
|
||||||
switch (typeof value) {
|
switch (typeof value) {
|
||||||
case 'string': return serializer.serializeString(value)
|
case 'string': return serializer.serializeString(value)
|
||||||
case 'number': return serializer.serializeNumber(value)
|
case 'number': return serializer.serializeNumber(value)
|
||||||
|
@ -40,3 +49,8 @@ export function serialize<T, V, S extends ISerializer<T>>(serializer: S, value:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function serialize<T, U, S extends ISerializer<T>>(serializer: S, value: U, registry: Registry = GlobalRegistry): T {
|
||||||
|
const ser = getSerialize<T, U>(value, registry)
|
||||||
|
return ser(serializer, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,6 @@ export class Serializer<T> implements ISerializer<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Serialize<T> {
|
export interface Serialize<T> {
|
||||||
<U, S extends Serializer<U>>(serializer: S, value: T): U
|
<U, S extends ISerializer<U>>(serializer: S, value: T): U
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
21
src/utils.ts
21
src/utils.ts
|
@ -10,7 +10,11 @@ export interface ToString {
|
||||||
toString(): string
|
toString(): string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isPlainObject(value: any): boolean {
|
export function isObject(value: any): value is object {
|
||||||
|
return typeof value === 'object'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPlainObject(value: any): value is object {
|
||||||
return Object.getPrototypeOf(value) === Object.prototype
|
return Object.getPrototypeOf(value) === Object.prototype
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,3 +46,18 @@ export class IterResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Null(...args: any) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PrimitivePrototype = Object.freeze({
|
||||||
|
undefined: Null,
|
||||||
|
boolean: Boolean,
|
||||||
|
number: Number,
|
||||||
|
bigint: BigInt,
|
||||||
|
string: String,
|
||||||
|
symbol: Symbol,
|
||||||
|
object: Object,
|
||||||
|
function: Function
|
||||||
|
} as const)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue