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>(serializer: ObjectSerializer, 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>(serializer: Serializer, value: R, options?: SerdeOptions) { const classSerializer = serializer.serializeClass!(value.constructor.name) return serializeEntries(classSerializer, Object.entries(value) as Iterable<[K, V]>, options) } function serializeObject>(serializer: Serializer, value: R, options?: SerdeOptions) { return serializeEntries(serializer.serializeObject!(), Object.entries(value) as Iterable<[K, V]>, options) } function serializeIter>(serializer: IterableSerializer, 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(serializer: Serializer, value: Serializable, optionsGetter: (value: any) => Nullable = defaultOptions): Nullable { // 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, options) } else if (isFunction(serializer.serializeObject)) { return serializeObject(serializer, value as Record, options) } // deliberate fallthrough when the above fail default: return serializeAny(value) } }