UNPKG

deneric2

Version:

JSON parser

225 lines (201 loc) 7.31 kB
import get from 'lodash/get' import set from 'lodash/set' import cloneDeep from 'lodash/cloneDeep' import isObject from 'lodash/isObject' type CommonType = StringConstructor | NumberConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor type SingleType = CommonType | Deneric | typeof Deneric type TDataType = SingleType | ComplexDataType type Newable = { new(value?: any): Deneric } export type DenericSchema = { [key: string]: [dataPath: string, dataType: TDataType, jsonIgnore?: boolean, defaultValue?: any] } const COMMON_DATA_TYPES: CommonType[] = [String, Number, Boolean, Array, Object] const DENERIC_SCHEMA_PATH: string = '__deneric_schema' abstract class ComplexDataType { abstract itemType: SingleType | ComplexDataType isArray: boolean = false isMap: boolean = false } class ArrayDataType extends ComplexDataType { isArray = true constructor(public itemType: SingleType | ComplexDataType) { super() } } class MapDataType extends ComplexDataType { isMap = true constructor(public itemType: SingleType | ComplexDataType) { super() } } const Utils = Object.freeze({ getValue(data: any, dataPath: string) { if (!!dataPath) { return get(data, dataPath) } return data }, getValueFromJson(data: any, dataType: TDataType, defaultValue: any, strict: boolean): any { // Handle dataType is Deneric Instance if ((dataType as typeof Deneric).prototype instanceof Deneric) { return new (dataType as unknown as Newable)(data) } // Handle dataType is ComplexDataType if (dataType instanceof ComplexDataType) { const complexDataType = dataType as ComplexDataType if (complexDataType.isArray) { return Array.isArray(data) ? data.map(item => Utils.getValueFromJson(item, complexDataType.itemType, Utils.getDefaultValue(complexDataType.itemType), strict)) : defaultValue } if (complexDataType.isMap && isObject(data)) { return Object.keys(data).reduce((prev, key) => { set(prev, key, Utils.getValueFromJson(Utils.getValue(data, key), complexDataType.itemType, Utils.getDefaultValue(complexDataType.itemType), strict)) return prev }, {}) } return defaultValue } const dataIsNil = data === null || data === undefined // Handle dataType is not common types if (!COMMON_DATA_TYPES.includes(dataType as CommonType)) { return dataIsNil ? defaultValue : data } // Handle dataType is common types with strict rule if (strict) { switch (dataType) { case String: return typeof data === 'string' ? data : defaultValue case Number: return typeof data === 'number' ? data : defaultValue case Boolean: return typeof data === 'boolean' ? data : defaultValue case Array: return Array.isArray(data) ? data : defaultValue case Object: return isObject(data) ? data : defaultValue } } // Handle dataType is common types with no-strict rule and data is not Nil if (!strict && !dataIsNil) { switch (dataType) { case String: return String(data) case Number: return Number(data) case Boolean: return Boolean(data) case Array: return [].concat(data) case Object: return Object(data) } } return defaultValue }, getValueFromDeneric(data: any, dataType: TDataType, defaultValue: any): any { switch (dataType) { case String: return typeof data === 'string' ? data : defaultValue case Number: return typeof data === 'number' ? data : defaultValue case Boolean: return typeof data === 'boolean' ? data : defaultValue case Array: return Array.isArray(data) ? data : defaultValue case Object: return isObject(data) ? cloneDeep(data) : defaultValue } if (dataType instanceof ComplexDataType) { const complexDataType = dataType as ComplexDataType if (complexDataType.isArray) { return Array.isArray(data) ? data.map(item => Utils.getValueFromDeneric(item, complexDataType.itemType, Utils.getDefaultValue(complexDataType.itemType))) : defaultValue } if (complexDataType.isMap) { data = isObject(data) ? data : {} return Object.keys(data).reduce((prev, key) => { set(prev, key, Utils.getValueFromDeneric(data[key], complexDataType.itemType, Utils.getDefaultValue(complexDataType.itemType))) return prev }, {}) } } if ((dataType as typeof Deneric).prototype instanceof Deneric) { return (data as Deneric).toJson() } return data }, getDefaultValue(dataType: TDataType) { if (dataType instanceof ComplexDataType) { const complexDataType = dataType as ComplexDataType if (complexDataType.isArray) { return [] } if (complexDataType.isMap) { return {} } } switch (dataType) { case String: return '' case Number: return 0 case Boolean: return false case Array: return [] case Object: return {} } return undefined }, setSchema(instance: Deneric, schema: DenericSchema): void { // if (!Object.getOwnPropertyNames(instance).includes(DENERIC_SCHEMA_PATH)) { // Object.defineProperty(instance, DENERIC_SCHEMA_PATH,{ // enumerable: false, // writable: true, // }) // } set(instance, DENERIC_SCHEMA_PATH, schema) }, getSchema(instance: Deneric): DenericSchema { return get(instance, DENERIC_SCHEMA_PATH) || {} as DenericSchema } }) abstract class Deneric { static Array = (dataType: TDataType) => new ArrayDataType(dataType) static Map = (valueDataType: TDataType) => new MapDataType(valueDataType) static Utils = Utils // private __proto__!: { schema: DenericSchema } constructor(schema: DenericSchema) { if (!schema) { throw new TypeError('Invalid schema: ' + this.constructor.name) } Utils.setSchema(this, schema) } clone<T extends Deneric>(): T { return cloneDeep(this) as unknown as T } fromJson<T extends Deneric>(data: any, strict: boolean = true): T { const schema: DenericSchema = Utils.getSchema(this) Object.keys(schema).forEach(key => { const [dataPath, dataType] = schema[key] let defaultValue = get(this, key) ?? Utils.getDefaultValue(dataType) if (schema[key][3] === undefined) { schema[key][3] = defaultValue } else { defaultValue = schema[key][3] ?? Utils.getDefaultValue(dataType) } const value = Utils.getValueFromJson(cloneDeep(Utils.getValue(data, dataPath)), dataType, defaultValue, strict) set(this, key, value) }) return this as unknown as T } toJson(): object { const schema: DenericSchema = Utils.getSchema(this) const json = {} Object.keys(schema).forEach(key => { const [dataPath, dataType, jsonIgnore, defaultValueFromSchema] = schema[key] if (!jsonIgnore) { set(json, dataPath, Utils.getValueFromDeneric(get(this, key), dataType, defaultValueFromSchema || Utils.getDefaultValue(dataType))) } }) return json } } export default Deneric