i hate typescript generics!! i hate typescript generics!!

This commit is contained in:
Rowan 2025-05-24 17:14:32 -05:00
parent 62e9563b8a
commit b735066f1c
4 changed files with 83 additions and 7 deletions

43
README.md Normal file
View 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`.

View file

@ -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)
}

View file

@ -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
} }

View file

@ -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)