serde-ts/src/ser/impl.ts
2025-05-18 21:42:39 -05:00

78 lines
3.2 KiB
TypeScript

import { IterableSerializer, ObjectSerializer, Serializable, Serializer } from './interface'
import { SerdeOptions, Stage } from '../options'
import { ifNull, isFunction, isIterable, isPlainObject, Nullable, orElse } from '../utils'
const unhandledType = (serializer: any, value: any) => new TypeError(`'${serializer.constructor.name}' has no method for value type '${typeof value}'`)
function serializeEntries<T, K extends string, V extends Serializable, E extends Iterable<[K, V]>>(serializer: ObjectSerializer<T>, value: E, options?: SerdeOptions) {
let state
for (const [key, val] of value) {
if (options?.shouldSkipSerializing(key, val)) {
continue
}
const name = options?.getPropertyName(key as string, Stage.Serialize) ?? key
state = serializer.serializeKey(name)
state = serializer.serializeValue(val)
}
return serializer.end()
}
function serializeClass<T, K extends string, V extends Serializable, R extends Record<K, V>>(serializer: Serializer<T>, value: R, options?: SerdeOptions) {
const classSerializer = serializer.serializeClass!(value.constructor.name)
return serializeEntries(classSerializer, Object.entries(value) as Iterable<[K, V]>, options)
}
function serializeObject<T, K extends string, V extends Serializable, R extends Record<K, V>>(serializer: Serializer<T>, value: R, options?: SerdeOptions) {
return serializeEntries(serializer.serializeObject!(), Object.entries(value) as Iterable<[K, V]>, options)
}
function serializeIter<T, V extends Iterable<any>>(serializer: IterableSerializer<T>, value: V, options?: SerdeOptions) {
let state
for (const val of value) {
state = serializer.serializeElement(val)
}
return serializer.end()
}
function defaultOptions(value: any) {
return value.constructor[Symbol.metadata]
}
// dispatches in the order of serializeType -> serializeAny -> throw TypeError
export function serializeWith<T>(serializer: Serializer<T>, value: Serializable, optionsGetter: (value: any) => Nullable<SerdeOptions> = defaultOptions): Nullable<T> {
// prepare fallback methods
const serializeAny = orElse(
serializer,
serializer.serializeAny,
(value: Serializable) => unhandledType(serializer, value)
)
const serialize = ifNull(serializer, serializeAny, value)
switch (typeof value) {
case 'string': return serialize(serializer.serializeString)
case 'number': return serialize(serializer.serializeNumber)
case 'bigint': return serialize(serializer.serializeBigInt)
case 'boolean': return serialize(serializer.serializeBoolean)
case 'symbol': return serialize(serializer.serializeSymbol)
case 'undefined': return serialize(serializer.serializeNull)
case 'object':
const options = optionsGetter(value)
if (isIterable(value) && isFunction(serializer.serializeIterable)) {
return serializeIter(serializer.serializeIterable(), value, options)
} if (!isPlainObject(value)) {
return serializeClass(serializer, value as Record<PropertyKey, any>, options)
} else if (isFunction(serializer.serializeObject)) {
return serializeObject(serializer, value as Record<PropertyKey, any>, options)
} // deliberate fallthrough when the above fail
default: return serializeAny(value)
}
}