Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,14 @@ export type UniNetworkRequestWithoutCallback = Omit<
> &
Omit<UniApp.DownloadFileOption, 'success' | 'fail' | 'complete'> &
Omit<UniApp.UploadFileOption, 'success' | 'fail' | 'complete'>

/**
* 序列化选项
*/
export interface SerializeOptions {
/**
* 如果设置为 true,对象中的数组值将被连接成一个逗号分隔的字符串。
* @default false
*/
asStrings?: boolean
}
98 changes: 97 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { AxiosHeaders } from 'axios'
import type {
MethodType,
ResolvedOptions,
SerializeOptions,
UniNetworkRequestWithoutCallback,
UserOptions,
} from './types'
Expand Down Expand Up @@ -43,7 +44,7 @@ export function resolveUniAppRequestOptions(config: AxiosRequestConfig, _options

const { headers, baseURL, ...requestConfig } = config

const requestHeaders = AxiosHeaders.from(headers as any).normalize(false)
const requestHeaders = AxiosHeaders.from(serializeObject(headers)).normalize(false)

if (config.auth) {
const username = config.auth.username || ''
Expand Down Expand Up @@ -114,3 +115,98 @@ export function progressEventReducer(listener: (progressEvent: AxiosProgressEven
listener(data)
}
}

/**
* ### 对象序列化
* - 将一个对象序列化为一个纯净的、类似JSON的新对象。
* - 此过程会过滤掉值为 null、undefined 或 false 的属性。
*
* @link https://github.com/axios/axios/blob/ef36347fb559383b04c755b07f1a8d11897fab7f/lib/core/AxiosHeaders.js#L238-L246
* @param {Record<string, any> | null | undefined} sourceObj 要进行序列化的源对象。
* @param {SerializeOptions} [options] 序列化选项。
* @returns {Record<string, any>} 返回一个新的、纯净的 JavaScript 对象。
*/
export function serializeObject(
sourceObj: Record<string, any> | null | undefined,
options?: SerializeOptions,
): Record<string, any> {
const resultObj: Record<string, any> = Object.create(null)

if (!sourceObj)
return resultObj

const { asStrings = false } = options || {}

forEach(sourceObj, (value, key) => {
if (value != null && value !== false) {
// 如果 asStrings 为 true 且值是数组,则拼接成字符串,否则直接使用原值
resultObj[key] = asStrings && Array.isArray(value) ? value.join(', ') : value
}
})

return resultObj
}

/**
* Iterates over an Array, invoking a function for each item.
*
* @param {T[]} obj The array to iterate over.
* @param {(value: T, index: number, array: T[]) => void} fn The callback to invoke for each item.
* @param {{allOwnKeys?: boolean}} [options] Optional options.
*/
export function forEach<T>(obj: T[], fn: (value: T, index: number, array: T[]) => void, options?: { allOwnKeys?: boolean }): void

/**
* Iterates over an Object, invoking a function for each item.
*
* @param {T} obj The object to iterate over.
* @param {(value: T[keyof T], key: keyof T, object: T) => void} fn The callback to invoke for each property.
* @param {{allOwnKeys?: boolean}} [options] Optional options.
*/
export function forEach<T extends object>(obj: T, fn: (value: T[keyof T], key: keyof T, object: T) => void, options?: { allOwnKeys?: boolean }): void

/**
* Iterate over an Array or an Object invoking a function for each item.
*
* If `obj` is an Array callback will be called passing
* the value, index, and complete array for each item.
*
* If 'obj' is an Object callback will be called passing
* the value, key, and complete object for each property.
*
* @link https://github.com/axios/axios/blob/v1.x/lib/utils.js#L240
*
* @param {object | Array} obj The object to iterate
* @param {Function} fn The callback to invoke for each item
* @param {object} [options]
* @param {boolean} [options.allOwnKeys]
*/
export function forEach(obj: any, fn: Function, { allOwnKeys = false }: { allOwnKeys?: boolean } = {}): void {
// Don't bother if no value provided
if (obj === null || typeof obj === 'undefined')
return

// Force an array if not already something iterable
if (typeof obj !== 'object')
obj = [obj]

if (Array.isArray(obj)) {
// Iterate over array values
for (let i = 0, l = obj.length; i < l; i++) {
// fn(value, index, array)
fn(obj[i], i, obj)
}
}
else {
// Iterate over object keys
const keys = allOwnKeys ? Object.getOwnPropertyNames(obj) : Object.keys(obj)
const len = keys.length
let key

for (let i = 0; i < len; i++) {
key = keys[i]
// fn(value, key, object)
fn(obj[key], key, obj)
}
}
}
133 changes: 131 additions & 2 deletions test/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { getMethodType, resolveUniAppRequestOptions } from '../src/utils'
import { describe, expect, it, vi } from 'vitest'
import { forEach, getMethodType, resolveUniAppRequestOptions, serializeObject } from '../src/utils'

describe('getMethodType', () => {
it('request', () => {
Expand Down Expand Up @@ -64,3 +64,132 @@ describe('resolveUniAppRequestOptions', () => {
`)
})
})

describe('serializeObject', () => {
it('should filter out null, undefined, and false values', () => {
const obj = {
a: 1,
b: 'hello',
c: true,
d: null,
e: undefined,
f: false,
g: 0,
h: '',
}
expect(serializeObject(obj)).toEqual({
a: 1,
b: 'hello',
c: true,
g: 0,
h: '',
})
})

it('should handle null or undefined input by returning an empty object', () => {
expect(serializeObject(null)).toEqual({})
expect(serializeObject(undefined)).toEqual({})
})

it('should handle an empty object input', () => {
expect(serializeObject({})).toEqual({})
})

it('should convert array values to strings if asStrings is true', () => {
const obj = {
arr: ['a', 'b', 3],
str: 'string',
}
const result = serializeObject(obj, { asStrings: true })
expect(result).toEqual({
arr: 'a, b, 3',
str: 'string',
})
})

it('should keep array values as arrays if asStrings is false or not provided', () => {
const obj = {
arr: ['a', 'b', 3],
}
// asStrings 未提供
expect(serializeObject(obj)).toEqual({
arr: ['a', 'b', 3],
})
// asStrings 为 false
expect(serializeObject(obj, { asStrings: false })).toEqual({
arr: ['a', 'b', 3],
})
})

it('should return an object that does not inherit from Object.prototype', () => {
const result = serializeObject({ a: 1 })
// 验证对象的原型是 null => Object.create(null) 纯净的对象
expect(Object.getPrototypeOf(result)).toBeNull()
})
})

describe('forEach', () => {
it('should iterate over an array, providing value, index, and array to the callback', () => {
const arr = ['a', 1, true]
const callback = vi.fn()

forEach(arr, callback)

expect(callback).toHaveBeenCalledTimes(3)
expect(callback).toHaveBeenCalledWith('a', 0, arr)
expect(callback).toHaveBeenCalledWith(1, 1, arr)
expect(callback).toHaveBeenCalledWith(true, 2, arr)
})

it('should iterate over an object\'s own enumerable properties', () => {
const obj = { a: 1, b: 2 }
// 添加一个不可枚举的属性
Object.defineProperty(obj, 'c', { value: 3, enumerable: false })
// 添加一个原型链上的属性
Object.setPrototypeOf(obj, { d: 4 })

const callback = vi.fn()
forEach(obj, callback)

expect(callback).toHaveBeenCalledTimes(2) // 只应调用 2 次
expect(callback).toHaveBeenCalledWith(1, 'a', obj)
expect(callback).toHaveBeenCalledWith(2, 'b', obj)
expect(callback).not.toHaveBeenCalledWith(3, 'c', obj) // 不应包含不可枚举的属性
expect(callback).not.toHaveBeenCalledWith(4, 'd', obj) // 不应包含原型链上的属性
})

it('should iterate over all own properties when allOwnKeys is true', () => {
const obj = { a: 1 }
Object.defineProperty(obj, 'b', { value: 2, enumerable: false })
const callback = vi.fn()

forEach(obj, callback, { allOwnKeys: true })

expect(callback).toHaveBeenCalledTimes(2)
expect(callback).toHaveBeenCalledWith(1, 'a', obj)
expect(callback).toHaveBeenCalledWith(2, 'b', obj)
})

it('should handle non-object values by wrapping them in an array', () => {
const callback = vi.fn()

forEach(123, callback)
expect(callback).toHaveBeenCalledOnce()
expect(callback).toHaveBeenCalledWith(123, 0, [123])

callback.mockClear()

forEach('test', callback)
expect(callback).toHaveBeenCalledOnce()
expect(callback).toHaveBeenCalledWith('test', 0, ['test'])
})

it('should not call the callback for null or undefined input', () => {
const callback = vi.fn()

forEach(null, callback)
forEach(undefined, callback)

expect(callback).not.toHaveBeenCalled()
})
})