denyUnknownFields
This commit is contained in:
parent
548c206afb
commit
0c654000ec
6 changed files with 94 additions and 18 deletions
|
@ -16,10 +16,10 @@ export abstract class DefaultMapAccessImpl implements MapAccess {
|
|||
|
||||
nextEntrySeed<TK, TV, K extends Deserialize<TK>, V extends Deserialize<TV>>(kseed: K, vseed: V): Nullable<[TK, TV]> {
|
||||
const key = this.nextKeySeed(kseed) as Nullable<TK>
|
||||
if (key) {
|
||||
if (key !== undefined) {
|
||||
const value = this.nextValueSeed(vseed) as Nullable<TV>
|
||||
|
||||
if (value) {
|
||||
if (value !== undefined) {
|
||||
return [key, value]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,42 @@
|
|||
import { Serde } from '../decorator'
|
||||
import { getMetadata, Serde } from '../decorator'
|
||||
import { SerdeOptions, Stage } from '../options'
|
||||
import { Constructor, staticImplements } from '../utils'
|
||||
import { GenericVisitor } from './generic'
|
||||
import { Deserialize, Deserializer } from './interface'
|
||||
|
||||
type DeserializeConstructor<T> = Deserialize<T> & { new(): Deserialize<T> }
|
||||
|
||||
function deserializeWith<T, D extends Deserializer, E extends DeserializeConstructor<T>>(deserializer: D, into: E, options: SerdeOptions): T {
|
||||
const visitor = new GenericVisitor<T>()
|
||||
const obj = deserializer.deserializeObject(visitor) as any
|
||||
const target = new into()
|
||||
const newObject = {} as any
|
||||
|
||||
for (const property in target) {
|
||||
const name = options.getPropertyName(property, Stage.Deserialize)
|
||||
const value = obj[name] || options.getDefault(property)
|
||||
newObject[property] = value
|
||||
delete obj[name]
|
||||
}
|
||||
|
||||
if (options.options.denyUnknownFields && Object.keys(obj).length > 0) {
|
||||
throw new TypeError(`Unexpected fields: ${Object.keys(obj).join(', ')}`)
|
||||
}
|
||||
|
||||
return Object.assign(target, newObject)
|
||||
}
|
||||
|
||||
export function deserialize<T, C extends Constructor>(constructor: C) {
|
||||
@staticImplements<Deserialize<T>>()
|
||||
class Deserializable extends constructor {
|
||||
static name = constructor.name
|
||||
|
||||
static deserialize<D extends Deserializer>(deserializer: D): T {
|
||||
const visitor = new GenericVisitor<T>()
|
||||
return deserializer.deserializeAny(visitor)
|
||||
return deserializeWith(deserializer, this, getMetadata(this))
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
Deserializable[Serde] = (constructor as any)[Serde]
|
||||
|
||||
return Deserializable
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ContainerOptions, PropertyOptions, SerdeOptions } from "./options"
|
||||
import { ContainerOptions, PropertyOptions, SerdeOptions } from './options'
|
||||
|
||||
export const Serde = Symbol('Serde')
|
||||
|
||||
|
@ -38,3 +38,7 @@ export function serde(options: ContainerOptions | PropertyOptions) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getMetadata(value: any) {
|
||||
return value[Serde]
|
||||
}
|
||||
|
|
|
@ -244,10 +244,10 @@ class JSONObjectSerializer implements ObjectSerializer<void> {
|
|||
}
|
||||
|
||||
serializeWith(this.ser, key)
|
||||
this.ser.write(':')
|
||||
}
|
||||
|
||||
serializeValue<U extends Serializable>(value: U): void {
|
||||
this.ser.write(':')
|
||||
serializeWith(this.ser, value)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { CaseConvention, convertCase } from './case'
|
||||
import { isNumber, isString, Morphism } from './utils'
|
||||
import { Deserializer } from './de'
|
||||
import { Serializer } from './ser'
|
||||
import { isFunction, isNumber, isString, Morphism, Nullable } from './utils'
|
||||
|
||||
|
||||
export interface RenameOptions {
|
||||
|
@ -14,7 +16,7 @@ export interface RenameAllOptions {
|
|||
|
||||
export interface ContainerOptions {
|
||||
// deserialization only
|
||||
default?: <T>() => T
|
||||
default?: () => any
|
||||
rename?: RenameOptions | string
|
||||
renameAll?: RenameAllOptions | CaseConvention
|
||||
// deserialization only
|
||||
|
@ -34,12 +36,17 @@ export interface SkipOptions {
|
|||
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?: Morphism
|
||||
deserializeWith: Morphism
|
||||
//serializeWith?: CustomSerializer
|
||||
//deserializeWith: CustomDeserializer
|
||||
skip?: SkipOptions | boolean
|
||||
}
|
||||
|
||||
|
@ -108,4 +115,41 @@ export class SerdeOptions {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
import { Serde } from '../decorator'
|
||||
import { SerdeOptions, Stage } from '../options'
|
||||
import { ifNull, isFunction, isIterable, Nullable, orElse } from '../utils'
|
||||
import { IterableSerializer, ObjectSerializer, Serializable, Serializer } from './interface'
|
||||
import { IterableSerializer, Serializable, Serializer } from './interface'
|
||||
|
||||
const unhandledType = (serializer: any, value: any) => new TypeError(`'${serializer.constructor.name}' has no method for value type '${typeof value}'`)
|
||||
|
||||
function serializeEntries<T, K extends Serializable, V extends Serializable, E extends Iterable<[K, V]>>(serializer: ObjectSerializer<T>, value: E, options?: SerdeOptions) {
|
||||
function serializeEntries<T, K extends string, V extends Serializable, E extends Iterable<[K, V]>>(serializer: Serializer<T>, value: E, options?: SerdeOptions) {
|
||||
let state
|
||||
|
||||
const objectSerializer = serializer.serializeObject!()
|
||||
|
||||
for (const [key, val] of value) {
|
||||
const name = options?.getPropertyName(key as string, Stage.Serialize) ?? key
|
||||
state = serializer.serializeKey(name)
|
||||
state = serializer.serializeValue(val)
|
||||
state = objectSerializer.serializeKey(name)
|
||||
state = objectSerializer.serializeValue(val)
|
||||
}
|
||||
|
||||
return serializer.end()
|
||||
return objectSerializer.end()
|
||||
}
|
||||
|
||||
function serializeObject<T, K extends PropertyKey, V extends Serializable, R extends Record<K, V>>(serializer: ObjectSerializer<T>, value: R, options?: SerdeOptions) {
|
||||
function serializeObject<T, K extends string, V extends Serializable, R extends Record<K, V>>(serializer: Serializer<T>, value: R, options?: SerdeOptions) {
|
||||
return serializeEntries(serializer, Object.entries(value) as Iterable<[K, V]>, options)
|
||||
}
|
||||
|
||||
|
@ -58,7 +60,7 @@ export function serializeWith<T>(serializer: Serializer<T>, value: Serializable,
|
|||
if (isIterable(value) && isFunction(serializer.serializeIterable)) {
|
||||
return serializeIter(serializer.serializeIterable(), value)
|
||||
} else if (isFunction(serializer.serializeObject)) {
|
||||
return serializeObject(serializer.serializeObject!(), value as Record<PropertyKey, any>, optionsGetter(value))
|
||||
return serializeObject(serializer, value as Record<PropertyKey, any>, optionsGetter(value))
|
||||
} // deliberate fallthrough when the above fail
|
||||
|
||||
default: return serializeAny(value)
|
||||
|
|
Loading…
Add table
Reference in a new issue