From 912ec7ba8dee043696e0b4ee90b85b8f81ad5caa Mon Sep 17 00:00:00 2001 From: LuciferHuang <784863142@qq.com> Date: Sun, 17 Mar 2024 14:17:08 +0800 Subject: [PATCH] feat: add serialize --- README.md | 264 ++++++++++++++++++++++++++++++++----------- package.json | 2 +- src/index.ts | 166 ++++++++++++++++----------- src/lib/transform.ts | 29 +++-- src/lib/utils.ts | 95 +++++++--------- src/types/index.ts | 10 +- tests/index.test.ts | 152 +++++++++++++++++++++---- tsconfig.json | 2 +- 8 files changed, 496 insertions(+), 224 deletions(-) diff --git a/README.md b/README.md index 98b44fd..ab4a975 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Instead of directly using api data, we definitely require an adapter layer to tr ## Doc -[中文文档](https://mp.weixin.qq.com/s/YVOYZHTDbAfwcGaUUMld0Q) +[中文文档](https://juejin.cn/post/7221497108008419388) ## Get Started @@ -24,57 +24,101 @@ Here is a complex example, hopefully could give you an idea of how to use it: import { mapperProperty, deepMapperProperty, filterMapperProperty } from "type-json-mapper"; class Lesson { - @mapperProperty("ClassName", "string") + @mapperProperty('ClassName', 'string') public name: string; - @mapperProperty("Teacher", "string") + + @mapperProperty('Teacher', 'string') public teacher: string; - @mapperProperty("DateTime", "datetime") + + @mapperProperty('DateTime', 'datetime') public datetime: string; + @mapperProperty('Date', 'date') + public date: string; + + @mapperProperty('Time', 'time') + public time: string; + + @mapperProperty('Compulsory', 'boolean') + public compulsory: boolean; + constructor() { - this.name = ""; - this.teacher = ""; - this.datetime = ""; + this.name = ''; + this.teacher = ''; + this.datetime = ''; + this.date = ''; + this.time = ''; + this.compulsory = false; } } class Address { + @mapperProperty('province', 'string') public province: string; + @mapperProperty('city', 'string') public city: string; - @mapperProperty("full_address", "string") + @mapperProperty('full_address', 'string') public fullAddress: string; constructor() { - this.province = ""; - this.city = ""; - this.fullAddress = ""; + this.province = ''; + this.city = ''; + this.fullAddress = ''; } } class Student { - @mapperProperty("StudentID", "string") + @mapperProperty('StudentID', 'string') public id: string; - @mapperProperty("StudentName", "string") + + @mapperProperty('StudentName', 'string') public name: string; - @mapperProperty("StudentAge", "int") + + @mapperProperty('StudentAge', 'int') public age: number; - @filterMapperProperty("StudentSex", (val = 0) => { - const map = { 0: '保密' 1: '男生', 2: '女生'}; - return map[val]; - }) - public sex: string; - @deepMapperProperty("Address", Address) + + @mapperProperty('NotFloat', 'float') + public notFloat: number; + + @mapperProperty('NotANum', 'int') + public notNum: number; + + @mapperProperty('UnknownType', 'String') + public unknownType: string; + + @mapperProperty('Grade', 'float') + public grade: number; + + @mapperProperty('AddressIds', 'string') + public addressIds?: string[]; + + @deepMapperProperty('Address', Address) public address?: Address; - @deepMapperProperty("Lessons", Lesson) + + @deepMapperProperty('Lessons', Lesson) public lessons?: Lesson[]; + @filterMapperProperty('State', (val = 0) => { + const map = { '0': '未知', '1': '读书中', '2': '辍学', '3': '毕业' }; + return map[`${val}`]; + }) + public status: string; + + public extra: string; + constructor() { - this.id = ""; - this.name = ""; + this.id = ''; + this.name = ''; this.age = 0; - this.sex = 0; - this.address = undefined; - this.lessons = undefined; + this.notNum = 0; + this.notFloat = 0; + this.unknownType = ''; + this.grade = 0; + this.addressIds = []; + this.address = null; + this.lessons = []; + this.status = ''; + this.extra = ''; } } ``` @@ -82,41 +126,130 @@ class Student { Now here is what API server return, assume it is already parsed to JSON object. ```typescript -let json = { - StudentID: "123456", - StudentName: "李子明", - StudentAge: "9", - StudentSex: "1", +const json = { + StudentID: '123456', + StudentName: '李子明', + StudentAge: '10', + NotANum: 'lol', + NotFloat: 'def', + UnknownType: 'funny', + Grade: '98.6', + AddressIds: [1, 2], Address: { - province: "广东", - city: "深圳", - full_address: "xxx小学三年二班", + province: '广东', + city: '深圳', + full_address: 'xxx小学三年二班' }, Lessons: [ { - ClassName: "中国上下五千年", - Teacher: "建国老师", + ClassName: '中国上下五千年', + Teacher: '建国老师', DateTime: 1609430399000, + Date: 1609430399000, + Time: 1609430399000, + Compulsory: 1 }, + { + ClassName: '古筝的魅力', + Teacher: '美丽老师', + DateTime: '' + } ], -}; + State: 1, + extra: '额外信息' + } ``` +### Deserialize + Simply, just map it use following code. ```typescript import { deserialize } from 'type-json-mapper'; try { const student = deserialize(Student, json); - console.dir(student); + console.log(student); +} catch(err) { + console.error(err); +} +``` + +the result + +```ts +{ + id: '123456', + name: '李子明', + age: 10, + notNum: 'lol', + notFloat: 'def', + unknownType: 'funny', + grade: 98.6, + addressIds: ['1', '2'], + address: { province: '广东', city: '深圳', fullAddress: 'xxx小学三年二班' }, + lessons: [ + { + name: '中国上下五千年', + teacher: '建国老师', + datetime: '2020-12-31 23:59:59', + date: '2020-12-31', + time: '23:59:59', + compulsory: true + }, + { + name: '古筝的魅力', + teacher: '美丽老师', + datetime: '', + date: undefined, + time: undefined, + compulsory: undefined + } + ], + status: '读书中', + extra: '额外信息' +} +``` + +### Serialize + +```typescript +import { serialize } from 'type-json-mapper'; +try { + const params = serialize(Student, student); + console.log(params); } catch(err) { console.error(err); } ``` -![result.png](https://i.loli.net/2021/04/09/kPJW6Nn5gduBZXq.png) +```typescript +{ + StudentID: '123456', + StudentName: '李子明', + StudentAge: 10, + NotANum: 'lol', + NotFloat: 'def', + UnknownType: 'funny', + Grade: 98.6, + AddressIds: [ '1', '2' ], + Address: { province: '广东', city: '深圳', full_address: 'xxx小学三年二班' }, + Lessons: [ + { + ClassName: '中国上下五千年', + Teacher: '建国老师', + DateTime: '2020-12-31 23:59:59', + Date: '2020-12-31', + Time: '23:59:59', + Compulsory: true + }, + { ClassName: '古筝的魅力', Teacher: '美丽老师', DateTime: '' } + ], + State: '读书中', + extra: '额外信息' +} +``` -Mock data +### Mock data ```typescript import { mock } from 'type-json-mapper'; @@ -125,42 +258,43 @@ console.dir(res); ``` ```js -// console.dir(res); { - id: 'QGBLBA', - name: 'KTFH6d', - age: 4, - sex: '保密', - grade: 76.15, - address: { province: 'qvbCte', city: 'DbHfFZ', fullAddress: 'BQ4uIL' }, + id: 'uBP2ug', + name: 'EmcuTs', + age: 7, + notNum: 4, + notFloat: 624.775, + unknownType: '', + grade: 537.3, + addressIds: 'cjcpoC', + address: { province: '1VSl9Z', city: 'tCtF9K', fullAddress: 'Ves3rZ' }, lessons: [ { - name: 'JDtNMx', - teacher: 'AeI6hB', - datetime: '2023-2-18 15:00:07', - date: '2023-2-18', - time: '15:00:07', - compulsory: true + name: 'm2WqeY', + teacher: 'UdIuLT', + datetime: '2024-03-17 03:54:59', + date: '2024-03-17', + time: '03:54:59', + compulsory: false }, { - name: 'BIggA8', - teacher: '8byaId', - datetime: '2023-2-18 15:00:07', - date: '2023-2-18', - time: '15:00:07', + name: 'dCEoGG', + teacher: 'gJrmkV', + datetime: '2024-03-17 03:54:59', + date: '2024-03-17', + time: '03:54:59', compulsory: false }, { - name: 'pVda1n', - teacher: 'BPCmwa', - datetime: '2023-2-18 15:00:07', - date: '2023-2-18', - time: '15:00:07', + name: 'JDVZqP', + teacher: 'dV8ITf', + datetime: '2024-03-17 03:54:59', + date: '2024-03-17', + time: '03:54:59', compulsory: false } ], - status: '', - position: '', + status: '未知', extra: '' } ``` diff --git a/package.json b/package.json index 6cac58e..50e5bea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "type-json-mapper", - "version": "1.2.7", + "version": "1.2.8", "description": "Instead of directly using api data, we definitely require an adapter layer to transform data as needed. Furthermore, the adapter inverse the the data dependency from API server(API Server is considered uncontrollable and highly unreliable as data structure may be edit by backend coder for some specific purposes)to our adapter which becomes reliable. Thus, this library is created as the adapter make use of es7 reflect decorator.", "license": "MIT", "author": "LuciferHuang", diff --git a/src/index.ts b/src/index.ts index fd07741..4ab6cec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,49 +7,40 @@ const TAG = '[type-json-mapper'; /** * 属性装饰器 - * @param {string} value - 键名 - * @param {TYPE_NAME} typeName - 转换类型 - * @return {(target:Object, targetKey:string | symbol)=> void} decorator function + * @param {string} key - 键名 + * @param {TYPE_NAME} type - 转换类型 */ -export function mapperProperty(value: string, typeName?: TYPE_NAME): (target: Object, targetKey: string | symbol) => void { - const metadata: MetadataObject = { - typeName, - localKey: value - }; - return setProperty(metadata); -} +export const mapperProperty = (key: string, type?: TYPE_NAME) => + setProperty({ + key, + type + }); /** * 深层属性装饰器 - * @param {string} value - 键名 + * @param {string} key - 键名 * @param Clazz - * @return {(target:Object, targetKey:string | symbol)=> void} decorator function */ -export function deepMapperProperty(value: string, Clazz): (target: Object, targetKey: string | symbol) => void { - const metadata: MetadataDeepObject = { - localKey: value, +export const deepMapperProperty = (key: string, Clazz) => + setProperty({ + key, Clazz - }; - return setProperty(metadata); -} + }); /** * 自定义属性装饰器 - * @param {string} value - 键名 + * @param {string} key - 键名 * @param {Function} filter - * @return {(target:Object, targetKey:string | symbol)=> void} decorator function */ -export function filterMapperProperty(value: string, filter: Function): (target: Object, targetKey: string | symbol) => void { - const metadata: MetadataFilterObject = { - localKey: value, +export const filterMapperProperty = (key: string, filter: Function) => + setProperty({ + key, filter - }; - return setProperty(metadata); -} + }); /** * 反序列化 */ -export function deserialize(Clazz: { new (): T }, json: GenericObject) { +export const deserialize = (Clazz: { new (): T }, json: GenericObject) => { if (hasAnyNullOrUndefined(Clazz, json)) { throw new Error(`${TAG}/deserialize]: missing Clazz or json`); } @@ -58,32 +49,22 @@ export function deserialize(Clazz: { new (): T }, json: throw new Error(`${TAG}/deserialize]: json is not a object`); } - let result = {}; - const instance: GenericObject = new Clazz(); + const result = instance; - const keys = Object.keys(instance); + for (const localKey of Object.keys(instance)) { + let value = json[localKey]; + const metaObj = getJsonProperty(instance, localKey) || {}; - result = instance; - - for (const key of keys) { - let value = json[key]; - let metaObj: GenericObject = {}; - - metaObj = getJsonProperty(instance, key); - if (typeof metaObj === 'undefined') { - metaObj = {}; - } - - const { typeName, localKey } = metaObj; - if (!['', 0, undefined].includes(localKey)) { - value = json[localKey]; + const { type, key } = metaObj as MetadataObject; + if (!['', 0, undefined].includes(key)) { + value = json[key]; if (typeof value !== 'undefined') { - value = transType(value, typeName); + value = transType(value, type); } } - const { filter } = metaObj; + const { filter } = metaObj as MetadataFilterObject; if (typeof filter === 'function') { const tempVal = filter(value); if (typeof tempVal !== 'undefined') { @@ -91,7 +72,7 @@ export function deserialize(Clazz: { new (): T }, json: } } - const { Clazz: childClazz } = metaObj; + const { Clazz: childClazz } = metaObj as MetadataDeepObject; if (typeof childClazz !== 'undefined') { if (isObject(value)) { value = deserialize(childClazz, value); @@ -102,47 +83,102 @@ export function deserialize(Clazz: { new (): T }, json: } } - result[key] = value; + if (typeof value !== 'undefined') { + result[localKey] = value; + } } return result as T; -} +}; /** * 数组反序列化 */ -export function deserializeArr(Clazz: { new (): GenericObject }, list: GenericObject[]) { +export const deserializeArr = (Clazz: { new (): T }, list: GenericObject[]) => { if (hasAnyNullOrUndefined(Clazz, list)) { throw new Error(`${TAG}/deserializeArr]: missing Clazz or list`); } - return list.map((ele: GenericObject) => deserialize(Clazz, ele)); + return list.map((ele: GenericObject) => deserialize(Clazz, ele)); } -export function mock(Clazz: { new (): T }, options: MockOptions = {}) { - let result = {}; +/** + * 序列化 + */ +export const serialize = (Clazz: { new (): GenericObject }, json: GenericObject) => { + if (hasAnyNullOrUndefined(Clazz, json)) { + throw new Error(`${TAG}/serialize]: missing Clazz or json`); + } + + if (!isObject(json)) { + throw new Error(`${TAG}/serialize]: json is not a object`); + } + + const result = {}; const instance: GenericObject = new Clazz(); - const keys = Object.keys(instance); + for (const localKey of Object.keys(instance)) { + let value = json[localKey]; - result = instance; + if (typeof value === 'undefined') { + continue; + } - for (const key of keys) { - const { fieldLength = {}, arrayFields = [] } = options; + const metaObj = getJsonProperty(instance, localKey) || {} - let value: any = ''; - let metaObj: GenericObject = {}; + const { Clazz: childClazz } = metaObj as MetadataDeepObject; + if (typeof childClazz !== 'undefined') { + if (isObject(value)) { + value = serialize(childClazz, value); + } - metaObj = getJsonProperty(instance, key); - if (typeof metaObj === 'undefined') { - metaObj = {}; + if (isArray(value)) { + value = serializeArr(childClazz, value); + } } - const { typeName } = metaObj; + const { key } = metaObj as MetadataObject; + result[key ? key : localKey] = value; + } + + return result; +} + +/** + * 数组序列化 + */ +export const serializeArr = (Clazz: { new (): GenericObject }, list: GenericObject[]) => { + if (hasAnyNullOrUndefined(Clazz, list)) { + throw new Error(`${TAG}/serializeArr]: missing Clazz or list`); + } + + return list.map((ele: GenericObject) => serialize(Clazz, ele)); +} + +export function mock(Clazz: { new (): T }, options?: MockOptions) { + if (hasAnyNullOrUndefined(Clazz)) { + throw new Error(`${TAG}/mock]: missing Clazz`); + } + + if(!options || typeof options !== 'object') { + options = {}; + } + + const instance: GenericObject = new Clazz(); + + const result = instance; + + for (const key of Object.keys(instance)) { + const { fieldLength = {}, arrayFields = [] } = options; + + let value: any = ''; + const metaObj = getJsonProperty(instance, key) || {}; + + const { type } = metaObj; - if (typeName) { + if (type) { const length = fieldLength[key] || 6; - value = mockByType(typeName, length); + value = mockByType(type, length); } const { filter } = metaObj; diff --git a/src/lib/transform.ts b/src/lib/transform.ts index 9edab3d..2aa0f70 100644 --- a/src/lib/transform.ts +++ b/src/lib/transform.ts @@ -4,16 +4,19 @@ import { formatDate, getRandomFloat, getRandomInt, getRandomString, isNotBasicTy /** * 类型转换 * @param oriData 目标数据 - * @param {TYPE_NAME} typeName 转换类型 + * @param type 转换类型 * @return */ -export function transType(oriData, typeName?: TYPE_NAME) { +export const transType = (oriData: any, type?: TYPE_NAME) => { + if (Array.isArray(oriData) && oriData.every((val) => !isNotBasicType(val))) { + return transArrayType(oriData, type); + } if (isNotBasicType(oriData)) { return oriData; } let value = null; try { - switch (typeName) { + switch (type) { case 'string': value = `${oriData}`; break; @@ -50,18 +53,24 @@ export function transType(oriData, typeName?: TYPE_NAME) { } catch (error) { value = oriData; } - return value as T; + return value; } +/** + * 转换数组元素类型 + * @param oriData + * @param type + */ +export const transArrayType = (oriData: Array, type?: TYPE_NAME) => oriData.map(val => transType(val, type)) + /** * 根据类型获取随机数据 - * @param {TYPE_NAME} typeName 转换类型 - * @param {number} length 字符长度 - * @return + * @param type 转换类型 + * @param length 字符长度 */ -export function mockByType(typeName: TYPE_NAME, length: number) { +export const mockByType = (type: TYPE_NAME, length: number) => { let value: any = ''; - switch (typeName) { + switch (type) { case 'string': value = getRandomString(length); break; @@ -87,5 +96,5 @@ export function mockByType(typeName: TYPE_NAME, length: number) { value = ''; break; } - return value as T; + return value; } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 34fab95..e0460f3 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,123 +1,104 @@ import 'reflect-metadata'; +import { MetadataDeepObject, MetadataFilterObject, MetadataObject } from '../types'; /** * Reflect key */ -export const META_KEY = 'TransKey'; +export const META_KEY = '__TRANSKEY__'; /** * 获取对象meta值 * @param target - * @param {string} propertyKey + * @param propertyKey * @return */ export const getJsonProperty = (target, propertyKey: string) => - typeof Reflect.getMetadata === 'function' ? Reflect.getMetadata(META_KEY, target, propertyKey) : undefined; + typeof Reflect.getMetadata === 'function' && Reflect.getMetadata(META_KEY, target, propertyKey); /** * 设置对象meta值 * @param value - * @return {(target:Object, targetKey:string | symbol)=> void} decorator function + * @return */ -export const setProperty = (value: any): ((target: Object, targetKey: string | symbol) => void) => - typeof Reflect.metadata === 'function' && Reflect.metadata(META_KEY, value); +export const setProperty = (value: MetadataObject | MetadataDeepObject | MetadataFilterObject) => + typeof Reflect.metadata === 'function' && Reflect.metadata(META_KEY, value) /** * 判断目标变量是否是对象 * @param target - * @return {Boolean} + * @return */ -export const isObject = (target): boolean => Object.prototype.toString.call(target) === '[object Object]'; +export const isObject = (target: any) => Object.prototype.toString.call(target) === '[object Object]'; /** * 判断目标变量是否是数组 * @param target - * @return {Boolean} + * @return */ -export const isArray = (target: any): boolean => Object.prototype.toString.call(target) === '[object Array]'; +export const isArray = (target: any) => Object.prototype.toString.call(target) === '[object Array]'; -/** - * @param {...args:any[]} any arguments - * @return {Boolean} - */ -export const hasAnyNullOrUndefined = (...args: any[]): boolean => args.some((arg: any) => arg === null || arg === undefined); +export const hasAnyNullOrUndefined = (...args: any[]) => args.some((arg: any) => arg === null || arg === undefined); -/** - * @param target - * @return {Boolean} - */ -export const isNotBasicType = (target): boolean => ['string', 'number', 'boolean'].every((type) => type !== typeof target); +export const isNotBasicType = (target) => ['string', 'number', 'boolean'].every((type) => type !== typeof target); /** * 判断是否为Date对象 * @param date - * @return {boolean} + * @return */ export const isInvalidDate = (date: any): boolean => date instanceof Date && isNaN(date.getTime()); /** * 格式化日期时间 * @param timestamp - * @param {string} format - * @return {string} + * @param format + * @return */ -export function formatDate(timestamp: string | number | Date, format = 'Y-M-D h:m:s') { +export const formatDate = (timestamp: string | number | Date, format = 'Y-M-D h:m:s') => { const date = new Date(timestamp); if (isInvalidDate(date)) { return timestamp; } - const dateInfo = { - Y: `${date.getFullYear()}`, - M: `${date.getMonth() + 1}`, - D: `${date.getDate()}`, - h: date.getHours(), - m: date.getMinutes(), - s: date.getSeconds() - }; const formatNumber = (n: number) => (n >= 10 ? `${n}` : `0${n}`); return format - .replace('Y', dateInfo.Y) - .replace('M', dateInfo.M) - .replace('D', dateInfo.D) - .replace('h', formatNumber(dateInfo.h)) - .replace('m', formatNumber(dateInfo.m)) - .replace('s', formatNumber(dateInfo.s)); -} + .replace('Y', `${date.getFullYear()}`) + .replace('M', formatNumber(date.getMonth() + 1)) + .replace('D', formatNumber(date.getDate())) + .replace('h', formatNumber(date.getHours())) + .replace('m', formatNumber(date.getMinutes())) + .replace('s', formatNumber(date.getSeconds())); +}; /** * 获取随机整数 - * @param {number} min 最小值 - * @param {number} max 最大值 - * @returns {number} + * @param min 最小值 + * @param max 最大值 + * @returns */ -export function getRandomInt(min: number, max: number): number { +export const getRandomInt = (min: number, max: number) => { if (!min || !max) { return 0; } const range = max - min; const rand = Math.random(); return min + Math.round(rand * range); -} +}; /** * 获取随机字符串 - * @param {number} length 字符串长度 - * @param {string} chars 字符集 - * @returns {string} + * @param length 字符串长度 + * @param chars 字符集 + * @returns */ -export function getRandomString(length: number, chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'): string { - if (!length) { - return ''; - } - return new Array(length).fill(0).reduce((res) => `${res}${chars.charAt(Math.floor(Math.random() * chars.length))}`, ''); -} +export const getRandomString = (length: number, chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890') => + !length ? '' : new Array(length).fill(0).reduce((res) => `${res}${chars.charAt(Math.floor(Math.random() * chars.length))}`, ''); /** * 获取随机小数 - * @param {number} length 字符长度 - * @returns {number} + * @param length 字符长度 + * @returns */ -export function getRandomFloat(length: number): number { +export const getRandomFloat = (length: number) => { if (!length) { return 0; } @@ -125,4 +106,4 @@ export function getRandomFloat(length: number): number { const numStr = getRandomString(length, '123456789'); const floatStr = parseFloat(numStr.split('').reduce((res, char, index) => `${res}${length - index === decimal ? '.' : ''}${char}`, '')); return floatStr; -} +}; diff --git a/src/types/index.ts b/src/types/index.ts index b2cc11e..6ee50b6 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -8,16 +8,16 @@ export interface GenericObject { } export interface MetadataObject { - localKey: string; - typeName: TYPE_NAME | undefined; + key: string; + type: TYPE_NAME | undefined; } export interface MetadataDeepObject { - localKey: string; + key: string; Clazz: any; } export interface MetadataFilterObject { - localKey: string; + key: string; filter: Function; } @@ -28,4 +28,4 @@ export interface FieldLengthOption { export interface MockOptions { fieldLength?: FieldLengthOption arrayFields?: string[] -} \ No newline at end of file +} diff --git a/tests/index.test.ts b/tests/index.test.ts index 2c526fd..535e9b8 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,17 +1,31 @@ -import { mapperProperty, deepMapperProperty, filterMapperProperty, deserializeArr, deserialize, mock } from '../src/index'; +import { + mapperProperty, + deepMapperProperty, + filterMapperProperty, + deserializeArr, + deserialize, + mock, + serializeArr, + serialize +} from '../src/index'; import { getRandomInt, getRandomString, getRandomFloat, formatDate } from '../src/lib/utils'; class Lesson { @mapperProperty('ClassName', 'string') public name: string; + @mapperProperty('Teacher', 'string') public teacher: string; + @mapperProperty('DateTime', 'datetime') public datetime: string; + @mapperProperty('Date', 'date') public date: string; + @mapperProperty('Time', 'time') public time: string; + @mapperProperty('Compulsory', 'boolean') public compulsory: boolean; @@ -43,34 +57,40 @@ class Address { class Student { @mapperProperty('StudentID', 'string') public id: string; + @mapperProperty('StudentName', 'string') public name: string; + @mapperProperty('StudentAge', 'int') public age: number; + @mapperProperty('NotFloat', 'float') public notFloat: number; + @mapperProperty('NotANum', 'int') public notNum: number; // @ts-ignore @mapperProperty('UnknownType', 'String') public unknownType: string; - @filterMapperProperty('StudentSex', (val = 0) => { - const map = { 0: '未知', 1: '男生', 2: '女生' }; - return map[val]; - }) - public sex: string; + @mapperProperty('Grade', 'float') public grade: number; + + @mapperProperty('AddressIds', 'string') + public addressIds?: string[]; + @deepMapperProperty('Address', Address) public address?: Address; + @deepMapperProperty('Lessons', Lesson) public lessons?: Lesson[]; + @filterMapperProperty('State', (val = 0) => { const map = { '0': '未知', '1': '读书中', '2': '辍学', '3': '毕业' }; return map[`${val}`]; }) - // @ts-ignore public status: string; + public extra: string; constructor() { @@ -79,11 +99,11 @@ class Student { this.age = 0; this.notNum = 0; this.notFloat = 0; - this.sex = ''; this.unknownType = ''; this.grade = 0; - this.address = undefined; - this.lessons = undefined; + this.addressIds = []; + this.address = null; + this.lessons = []; this.status = ''; this.extra = ''; } @@ -94,11 +114,11 @@ const Students = [ StudentID: '123456', StudentName: '李子明', StudentAge: '10', - StudentSex: 1, NotANum: 'lol', NotFloat: 'def', UnknownType: 'funny', Grade: '98.6', + AddressIds: [1, 2], Address: { province: '广东', city: '深圳', @@ -126,7 +146,6 @@ const Students = [ StudentID: '888888', StudentName: '丁仪', StudentAge: '18', - StudentSex: 2, Grade: null, Address: { province: '浙江', @@ -140,6 +159,14 @@ const Students = [ const [first, second] = deserializeArr(Student, Students); +delete second.notFloat; + +const [oriFirst] = serializeArr(Student, [first, second]); + +const mockData1 = mock(Student, { fieldLength: { age: 20, grade: 4, name: 6 } }); + +const mockData2 = mock(Student, { fieldLength: { age: 20, grade: 4, name: 6 }, arrayFields: ['lessons'] }); + describe('transformer', () => { test('name', () => { expect(first.id).toBe('123456'); @@ -151,10 +178,22 @@ describe('transformer', () => { expect(second.grade).toBeNull(); }); + test('sample', () => { + const { addressIds = [] } = first; + const [target] = addressIds; + expect(typeof target).toBe('string'); + }); + test('deep', () => { const { address = { city: '' } } = first; expect(address.city).toBe('深圳'); }); + + test('deep arr', () => { + const { lessons = [] } = first; + const [target] = lessons; + expect(target.name).toBe('中国上下五千年'); + }); }); describe('filter', () => { @@ -170,7 +209,6 @@ describe('filter', () => { }); test('custom', () => { - expect(first.sex).toBe('男生'); expect(second.status).toBe('辍学'); }); }); @@ -183,7 +221,6 @@ describe('boundary', () => { test('deserialize first parameter illegal input', () => { let flag = 0; try { - // @ts-ignore deserialize(null, {}); flag = 1; } catch (err) { @@ -208,7 +245,6 @@ describe('boundary', () => { test('deserializeArr illegal input', () => { let flag = 0; try { - // @ts-ignore deserializeArr(null, []); flag = 1; } catch (err) { @@ -217,13 +253,89 @@ describe('boundary', () => { } expect(flag).toBe(2); }); + + test('serialize first parameter illegal input', () => { + let flag = 0; + try { + serialize(null, {}); + flag = 1; + } catch (err) { + expect(err.message).toBe('[type-json-mapper/serialize]: missing Clazz or json'); + flag = 2; + } + expect(flag).toBe(2); + }); + + test('serialize second parameter illegal input', () => { + let flag = 0; + try { + serialize(Student, []); + flag = 1; + } catch (err) { + expect(err.message).toBe('[type-json-mapper/serialize]: json is not a object'); + flag = 2; + } + expect(flag).toBe(2); + }); + + test('serializeArr illegal input', () => { + let flag = 0; + try { + serializeArr(null, []); + flag = 1; + } catch (err) { + expect(err.message).toBe('[type-json-mapper/serializeArr]: missing Clazz or list'); + flag = 2; + } + expect(flag).toBe(2); + }); + + test('mock first parameter illegal input', () => { + let flag = 0; + try { + mock(null); + flag = 1; + } catch (err) { + expect(err.message).toBe('[type-json-mapper/mock]: missing Clazz'); + flag = 2; + } + expect(flag).toBe(2); + }); + + test('mock second parameter illegal input', () => { + let flag = 0; + try { + mock(Student, null); + flag = 1; + } catch (err) { + flag = 2; + } + expect(flag).toBe(1); + }); +}); + +describe('serialize', () => { + test('deep', () => { + const { Lessons = [] } = oriFirst as any; + const [target] = Lessons; + const { ClassName } = target || {}; + expect(ClassName).toBe('中国上下五千年'); + }); + test('array', () => { + const { AddressIds = [] } = oriFirst as any; + const [target] = AddressIds; + expect(target).toBe('1'); + }); }); describe('mock', () => { test('generate', () => { - const res = mock(Student, { fieldLength: { age: 20, grade: 4, name: 6 }, arrayFields: ['lessons'] }); - expect(res.name.length).toBe(6); - expect(Object.prototype.toString.call(res.lessons)).toBe('[object Array]'); + expect(mockData1.name.length).toBe(6); + }); + + test('generate arr', () => { + expect(Object.prototype.toString.call(mockData1.lessons)).toBe('[object Object]'); + expect(Object.prototype.toString.call(mockData2.lessons)).toBe('[object Array]'); }); }); @@ -232,11 +344,11 @@ describe('tools', () => { // @ts-ignore const num = getRandomInt(); expect(num).toBe(0); - + // @ts-ignore const string = getRandomString(); expect(string).toBe(''); - + // @ts-ignore const float = getRandomFloat(); expect(float).toBe(0); diff --git a/tsconfig.json b/tsconfig.json index 41c9c87..a77357b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,6 +33,6 @@ "dom" ] }, - "include": ["src/**/*"], + "include": ["src/**/*", "tests/*"], "exclude": ["node_modules"] }