serde-ts/src/options.ts
2025-05-19 13:30:11 -05:00

192 lines
6.1 KiB
TypeScript

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 = <T, V, S extends Serializer<T>>(value: V, serializer: S) => T
export type CustomDeserializer = <T, D extends Deserializer>(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<string, PropertyOptions>
get registry() {
return this.options.registry || GlobalRegistry
}
constructor(options: ContainerOptions = {}, properties: Map<string, PropertyOptions> = 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<CustomSerializer> {
// return this.getCustomImpl(property, Stage.Serialize) as CustomSerializer
//}
//getDeserializer(property: string): Nullable<CustomDeserializer> {
// 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)
}
}