import { CaseConvention, convertCase } from './case' import { Deserializer } from './de' import { Serializer } from './ser' import { isFunction, isNumber, isString } from './utils' import { GlobalRegistry, Registry } from './registry' export interface RenameOptions { serialize?: string deserialize?: string } export interface RenameAllOptions { serialize?: CaseConvention deserialize?: CaseConvention } export interface ContainerOptions { // deserialization only default?: () => any // deserialization only denyUnknownFields?: boolean expecting?: string rename?: RenameOptions | string renameAll?: RenameAllOptions | CaseConvention registry?: Registry tag?: string content?: string untagged?: boolean } export interface ConditionalSkipOptions { if: (value: any) => boolean } export interface SkipOptions { serializing?: ConditionalSkipOptions | boolean deserializing?: ConditionalSkipOptions | boolean } export type CustomSerializer = >(value: V, serializer: S) => T export type CustomDeserializer = (deserializer: D) => T export interface PropertyOptions { alias?: string // deserialization only default?: () => any flatten?: boolean rename?: RenameOptions | string //serializeWith?: CustomSerializer //deserializeWith: CustomDeserializer skip?: SkipOptions | ConditionalSkipOptions | boolean } export const Stage = Object.freeze({ Serialize: 0, Deserialize: 1 } as const) export type Stage = typeof Stage[keyof typeof Stage] export class SerdeOptions { target: any readonly options: ContainerOptions readonly properties: Map get registry() { return this.options.registry || GlobalRegistry } constructor(options: ContainerOptions = {}, properties: Map = new Map()) { this.options = options this.properties = properties } static from(target: any) { return new this(target) } getClassName(stage: Stage) { if (isString(this.options.rename)) { return this.options.rename } else if (stage === Stage.Serialize && isString(this.options.rename?.serialize)) { return this.options.rename.serialize } else if (stage === Stage.Deserialize && isString(this.options.rename?.deserialize)) { return this.options.rename.deserialize } else { return this.target.constructor.name } } private getPropertyRename(property: string, stage: Stage, options: PropertyOptions) { if (options != null) { if (isString(options.rename)) { return options.rename } else if (stage === Stage.Serialize && isString(options.rename?.serialize)) { return options.rename.serialize } else if (stage === Stage.Deserialize && isString(options.rename?.deserialize)) { return options.rename.deserialize } } return property } private getPropertyCase(name: string, stage: Stage) { if (isNumber(this.options.renameAll)) { return convertCase(name, this.options.renameAll) } else if (stage === Stage.Serialize && isNumber(this.options.renameAll?.serialize)) { return convertCase(name, this.options.renameAll.serialize) } else if (stage === Stage.Deserialize && isNumber(this.options.renameAll?.deserialize)) { return convertCase(name, this.options.renameAll.deserialize) } else { return name } } getPropertyName(property: string, stage: Stage) { const options = this.properties.get(property) const name = options != null ? this.getPropertyRename(property, stage, options) : property return this.getPropertyCase(name, stage) } getSerializationName(property: string) { return this.getPropertyName(property, Stage.Serialize) } getDeserializationName(property: string) { return this.getPropertyName(property, Stage.Deserialize) } getDefault(property: string) { const options = this.properties.get(property) if (options && isFunction(options.default)) { return options.default() } else if (isFunction(this.options.default)) { return this.options.default() } } //getCustomImpl(property: string, stage: Stage) { // const options = this.properties.get(property) // if (options != null) { // if (stage === Stage.Serialize && isFunction(options.serializeWith)) { // return options.serializeWith // } else if (stage === Stage.Deserialize && isFunction(options.deserializeWith)) { // return options.deserializeWith // } // } //} //getSerializer(property: string): Nullable { // return this.getCustomImpl(property, Stage.Serialize) as CustomSerializer //} //getDeserializer(property: string): Nullable { // return this.getCustomImpl(property, Stage.Deserialize) as CustomDeserializer //} private isConditionalSkip(skip: any): skip is ConditionalSkipOptions { return 'if' in skip && isFunction(skip.if) } shouldSkip(property: string, value: any, stage: Stage): boolean { const options = this.properties.get(property) if (options != null && options.skip != null) { if (typeof options.skip === 'boolean') { return options.skip } else if (this.isConditionalSkip(options.skip)) { return options.skip.if(value) } else if (stage === Stage.Serialize && typeof options.skip.serializing === 'boolean') { return options.skip.serializing } else if (stage === Stage.Serialize && this.isConditionalSkip(options.skip.serializing)) { return options.skip.serializing.if(value) } else if (stage === Stage.Deserialize && typeof options.skip.deserializing === 'boolean') { return options.skip.deserializing } else if (stage === Stage.Deserialize && this.isConditionalSkip(options.skip.deserializing)) { return options.skip.deserializing.if(value) } } return false } shouldSkipSerializing(property: string, value: any): boolean { return this.shouldSkip(property, value, Stage.Serialize) } shouldSkipDeserializing(property: string, value: any): boolean { return this.shouldSkip(property, value, Stage.Deserialize) } }