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]> {
|
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>
|
const key = this.nextKeySeed(kseed) as Nullable<TK>
|
||||||
if (key) {
|
if (key !== undefined) {
|
||||||
const value = this.nextValueSeed(vseed) as Nullable<TV>
|
const value = this.nextValueSeed(vseed) as Nullable<TV>
|
||||||
|
|
||||||
if (value) {
|
if (value !== undefined) {
|
||||||
return [key, value]
|
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 { Constructor, staticImplements } from '../utils'
|
||||||
import { GenericVisitor } from './generic'
|
import { GenericVisitor } from './generic'
|
||||||
import { Deserialize, Deserializer } from './interface'
|
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) {
|
export function deserialize<T, C extends Constructor>(constructor: C) {
|
||||||
@staticImplements<Deserialize<T>>()
|
@staticImplements<Deserialize<T>>()
|
||||||
class Deserializable extends constructor {
|
class Deserializable extends constructor {
|
||||||
|
static name = constructor.name
|
||||||
|
|
||||||
static deserialize<D extends Deserializer>(deserializer: D): T {
|
static deserialize<D extends Deserializer>(deserializer: D): T {
|
||||||
const visitor = new GenericVisitor<T>()
|
return deserializeWith(deserializer, this, getMetadata(this))
|
||||||
return deserializer.deserializeAny(visitor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
Deserializable[Serde] = (constructor as any)[Serde]
|
Deserializable[Serde] = (constructor as any)[Serde]
|
||||||
|
|
||||||
return Deserializable
|
return Deserializable
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ContainerOptions, PropertyOptions, SerdeOptions } from "./options"
|
import { ContainerOptions, PropertyOptions, SerdeOptions } from './options'
|
||||||
|
|
||||||
export const Serde = Symbol('Serde')
|
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)
|
serializeWith(this.ser, key)
|
||||||
|
this.ser.write(':')
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeValue<U extends Serializable>(value: U): void {
|
serializeValue<U extends Serializable>(value: U): void {
|
||||||
this.ser.write(':')
|
|
||||||
serializeWith(this.ser, value)
|
serializeWith(this.ser, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { CaseConvention, convertCase } from './case'
|
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 {
|
export interface RenameOptions {
|
||||||
|
@ -14,7 +16,7 @@ export interface RenameAllOptions {
|
||||||
|
|
||||||
export interface ContainerOptions {
|
export interface ContainerOptions {
|
||||||
// deserialization only
|
// deserialization only
|
||||||
default?: <T>() => T
|
default?: () => any
|
||||||
rename?: RenameOptions | string
|
rename?: RenameOptions | string
|
||||||
renameAll?: RenameAllOptions | CaseConvention
|
renameAll?: RenameAllOptions | CaseConvention
|
||||||
// deserialization only
|
// deserialization only
|
||||||
|
@ -34,12 +36,17 @@ export interface SkipOptions {
|
||||||
deserializing?: 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 {
|
export interface PropertyOptions {
|
||||||
alias?: string
|
alias?: string
|
||||||
|
// deserialization only
|
||||||
|
default?: () => any
|
||||||
flatten?: boolean
|
flatten?: boolean
|
||||||
rename?: RenameOptions | string
|
rename?: RenameOptions | string
|
||||||
serializeWith?: Morphism
|
//serializeWith?: CustomSerializer
|
||||||
deserializeWith: Morphism
|
//deserializeWith: CustomDeserializer
|
||||||
skip?: SkipOptions | boolean
|
skip?: SkipOptions | boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,4 +115,41 @@ export class SerdeOptions {
|
||||||
const name = options != null ? this.getPropertyRename(property, stage, options) : property
|
const name = options != null ? this.getPropertyRename(property, stage, options) : property
|
||||||
return this.getPropertyCase(name, stage)
|
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 { Serde } from '../decorator'
|
||||||
import { SerdeOptions, Stage } from '../options'
|
import { SerdeOptions, Stage } from '../options'
|
||||||
import { ifNull, isFunction, isIterable, Nullable, orElse } from '../utils'
|
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}'`)
|
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
|
let state
|
||||||
|
|
||||||
|
const objectSerializer = serializer.serializeObject!()
|
||||||
|
|
||||||
for (const [key, val] of value) {
|
for (const [key, val] of value) {
|
||||||
const name = options?.getPropertyName(key as string, Stage.Serialize) ?? key
|
const name = options?.getPropertyName(key as string, Stage.Serialize) ?? key
|
||||||
state = serializer.serializeKey(name)
|
state = objectSerializer.serializeKey(name)
|
||||||
state = serializer.serializeValue(val)
|
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)
|
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)) {
|
if (isIterable(value) && isFunction(serializer.serializeIterable)) {
|
||||||
return serializeIter(serializer.serializeIterable(), value)
|
return serializeIter(serializer.serializeIterable(), value)
|
||||||
} else if (isFunction(serializer.serializeObject)) {
|
} 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
|
} // deliberate fallthrough when the above fail
|
||||||
|
|
||||||
default: return serializeAny(value)
|
default: return serializeAny(value)
|
||||||
|
|
Loading…
Add table
Reference in a new issue