/** * 文件定义:数据处理方法 * 包含:1、数据类型检查。2、数据转换。3、日期处理。4、数据校验。5、数据过滤与重组。6、特殊场景处理 */ import * as R from 'ramda' // =============== 数据转换 =============== /** * 将对象的所有值转换为字符串 * @param {Record} obj - 要转换的对象 * @returns {Record} 转换后的对象 */ export const objectToString = R.map(String) /** * 将数组转换为对象,使用指定的 key * @param {string} key - 要转换的 key * @param {Record[]} array - 要转换的数组 * @returns {Record>} 转换后的对象 */ export const arrayToObject = R.curry((key: string, array: Record[]) => R.indexBy(R.prop(key), array)) as < T extends Record, >( key: string, array: T[], ) => Record /** * 深度扁平化对象(建议深度嵌套的对象使用) * @param {Record} obj - 要扁平化的对象 * @returns {Record} 扁平化后的对象 */ export const flattenObject = (obj: Record): Record => { const result: Record = {} const flatten = (obj: Record, prefix: string = '') => { for (const key in obj) { const value = obj[key] const newKey = prefix ? `${prefix}.${key}` : key if (value && typeof value === 'object' && !Array.isArray(value)) { flatten(value, newKey) } else { result[newKey] = value } } } flatten(obj) return result } /** * 验证字符串是否符合正则表达式 * @param {RegExp} pattern - 要验证的正则表达式 * @param {string} str - 要验证的字符串 * @returns {boolean} 如果字符串符合正则表达式,则返回 true,否则返回 false */ export const matchesPattern = R.curry((pattern: RegExp, str: string) => R.test(pattern, str)) as ( pattern: T, str: string, ) => boolean /** * 验证对象是否包含所有必需的键 * @param {Record} obj - 要验证的对象 * @param {string[]} requiredKeys - 要验证的键 * @returns {boolean} 如果对象包含所有必需的键,则返回 true,否则返回 false */ export const hasRequiredKeys = R.curry((obj: Record, requiredKeys: string[]) => R.all(R.flip(R.has)(obj), requiredKeys), ) as { (obj: Record): (requiredKeys: string[]) => boolean (obj: Record, requiredKeys: string[]): boolean } // ... existing code ... /** * 验证值是否在指定范围内 * @param {number} min - 最小值 * @param {number} max - 最大值 * @param {number} value - 要验证的值 * @returns {boolean} 如果值在指定范围内,则返回 true,否则返回 false */ export const isInRange = R.curry((min: number, max: number, value: number) => R.both(R.gte(R.__, min), R.lte(R.__, max))(value), ) as (min: T, max: T, value: T) => boolean // =============== 数据过滤与重组 =============== /** * 根据条件过滤对象的属性 * @param {Function} predicate - 要过滤的条件 * @param {Record} obj - 要过滤的对象 * @returns {Record} 过滤后的对象 */ export const filterObject = R.curry( >(predicate: (value: T[keyof T]) => boolean, obj: T) => Object.fromEntries(Object.entries(obj).filter(([_, value]) => predicate(value))), ) as { >(predicate: (value: T[keyof T]) => boolean): (obj: T) => Partial >(predicate: (value: T[keyof T]) => boolean, obj: T): Partial } /** * 按照指定的键对数组进行分组 * @param {string} key - 要分组的键 * @param {Record[]} array - 要分组的数组 * @returns {Record[]>} 分组后的对象 */ export const groupByKey = R.curry(>(key: string, array: T[]) => R.groupBy(R.prop(key), array), ) as >(key: string, array: T[]) => Record /** * 从对象数组中提取指定的键值 * @param {string[]} path - 要提取的键 * @param {Record[]} list - 要提取的对象数组 * @returns {Record[]} 提取后的对象数组 */ export const pluckDeep = R.curry((path: string[], list: T[]) => R.map(R.path(path), list)) as < T extends Record, >( path: string[], list: T[], ) => T[] /** * 对嵌套数组进行扁平化和去重 * @param {any[]} array - 要扁平化和去重的数组 * @returns {any[]} 扁平化和去重后的数组 */ export const flattenAndUniq = R.pipe(R.flatten, R.uniq) as (array: T[]) => T[] // =============== 数据映射 =============== type MapperOption = { inherit?: string[] // 继承字段 deep?: boolean // 深度映射 ignore?: string[] // 忽略字段 } type MapperType = [string, string][] | Record type DataType = Record | Record[] /** * 对象/数组映射,根据映射表,将数组或对象映射为新的对象和数组 * 支持继承/过滤,通过参数继承/过滤,选取自己需要的数据 * 增加异常处理,如果值不存在,则抛出异常。 * 返回新的对象/数组 */ export const mapData = (mapper: MapperType, data: DataType, options: MapperOption = { deep: true }): DataType => { const { inherit, deep, ignore } = options // 验证 inherit 和 ignore 不能同时使用 if (inherit && ignore) { throw new Error('inherit 和 ignore 选项不能同时使用') } // 将 mapper 转换为对象形式 const mapperObj = Array.isArray(mapper) ? mapper.reduce>((acc, [key, value]) => ({ ...acc, [key]: value }), {}) : mapper // 处理数组 if (Array.isArray(data)) { return data.map((item) => mapData(mapperObj, item, options) as Record) } // 处理对象 if (typeof data === 'object' && data !== null) { // 根据选项过滤 mapper let finalMapper = { ...mapperObj } if (inherit) { finalMapper = Object.entries(mapperObj) .filter(([key]) => inherit.includes(key)) .reduce>((acc, [key, value]) => ({ ...acc, [key]: value }), {}) } else if (ignore) { finalMapper = Object.entries(mapperObj) .filter(([key]) => !ignore.includes(key)) .reduce>((acc, [key, value]) => ({ ...acc, [key]: value }), {}) } return Object.entries(finalMapper).reduce>((result, [sourceKey, targetKey]) => { // 处理嵌套路径 const value = sourceKey.split('.').reduce((obj, key) => { if (obj === undefined || obj === null) { throw new Error(`映射键 "${sourceKey}" 不存在于源数据中`) } return (obj as Record)[key] }, data) // 处理值不存在的情况 if (value === undefined) { throw new Error(`映射键 "${sourceKey}" 的值不存在`) } // 处理深度映射 if (deep && typeof value === 'object' && value !== null) { const nestedMapper = Object.entries(mapperObj) .filter(([key]) => key.startsWith(`${sourceKey}.`)) .reduce>( (acc, [key, val]) => ({ ...acc, [key.slice(sourceKey.length + 1)]: val, }), {}, ) if (Object.keys(nestedMapper).length > 0) { return { ...result, [targetKey]: mapData(nestedMapper, value as Record, options), } } } // 处理嵌套目标路径 const targetPath = (targetKey as string).split('.') const finalKey = targetPath.pop()! const targetObj = targetPath.reduce>((obj, key) => { if (!(key in obj)) { obj[key] = {} } return obj[key] as Record }, result) if (finalKey && targetObj) { targetObj[finalKey] = value } return result }, {}) } return data } /** * @description 生成映射表,将所有字段转换为小驼峰 * @param {Record} obj - 要转换的对象 * @returns {Record} 转换后的对象 */ export const generateMapper = (obj: Record) => { return Object.entries(obj).map(([key, value]) => [ key.toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()), value, ]) } /** * 将对象转换为查询字符串 * @param {Record} obj - 要转换的对象 * @returns {string} 转换后的查询字符串 */ export const objectToQueryString = (obj: Record) => { return Object.entries(obj) .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) .join('&') } /** * 深度合并两个对象 * @param {Record} target - 目标对象 * @param {Record} source - 源对象 * @returns {Record} 合并后的对象 */ export const deepMerge = >(target: T, source: T, isMergeArray: boolean = true): T => { const result = { ...target } for (const key in source) { if (source.hasOwnProperty(key)) { const sourceValue = source[key] const targetValue = target[key] if (Array.isArray(sourceValue) && Array.isArray(targetValue)) { // 如果是数组,则合并数组 result[key] = isMergeArray ? [...targetValue, ...sourceValue] : sourceValue } else if (isObject(sourceValue) && isObject(targetValue)) { // 如果是对象,则递归合并 result[key] = deepMerge(targetValue, sourceValue) } else { // 其他情况直接覆盖 result[key] = sourceValue } } } return result } /** * 判断是否为对象 * @param {any} value - 要判断的值 * @returns {boolean} 是否为对象 */ const isObject = (value: any): boolean => { return value !== null && typeof value === 'object' && !Array.isArray(value) }