78 lines
3.2 KiB
TypeScript
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)
|
|
}
|
|
}
|
|
|