axios-case-converter
Version:
Axios transformer/interceptor that converts snake_case/camelCase
157 lines (148 loc) • 4.92 kB
text/typescript
import { camelCase as camelCaseString } from 'camel-case';
import { snakeCase as snakeCaseString } from 'snake-case';
import { headerCase as headerCaseString } from 'header-case';
import { applyCaseOptions, preserveSpecificKeys } from './decorators';
import { isFormData, isTransformable, isURLSearchParams } from './util';
import {
CaseFunction,
CaseFunctions,
CaseFunctionTypes,
CreateObjectTransformer,
CreateObjectTransformerOf,
CreateObjectTransformers,
ObjectTransformerOptions,
ObjectTransformers,
Transformable,
} from './types';
const caseFunctions: CaseFunctions = {
snake: snakeCaseString,
camel: camelCaseString,
header: headerCaseString,
};
const transformObjectUsingCallbackRecursive = (
data: unknown,
fn: CaseFunction,
overwrite: ObjectTransformerOptions['overwrite']
): unknown => {
if (!isTransformable(data)) {
return data;
}
/* eslint-disable no-console */
// Check FormData/URLSearchParams compatibility
if (
(isFormData(data) || isURLSearchParams(data)) &&
(!data.entries || (overwrite && !data.delete))
) {
const type = isFormData(data) ? 'FormData' : 'URLSearchParams';
const polyfill = isFormData(data)
? 'https://github.com/jimmywarting/FormData'
: 'https://github.com/jerrybendy/url-search-params-polyfill';
if (
typeof navigator !== 'undefined' &&
navigator.product === 'ReactNative'
) {
// You cannot transform FormData/URLSearchParams on React Native
console.warn(
`Be careful that ${type} cannot be transformed on React Native. If you intentionally implemented, ignore this kind of warning: https://facebook.github.io/react-native/docs/debugging.html`
);
} else {
if (!data.entries) {
// You need to polyfill `entries` method
console.warn(
`You must use polyfill of ${type}.prototype.entries() on Internet Explorer or Safari: ${polyfill}`
);
}
if (overwrite && !data.delete) {
// You need to polyfill `delete` method for overwriting
console.warn(
`You must use polyfill of ${type}.prototype.delete() on Internet Explorer or Safari: ${polyfill}`
);
}
}
return data;
}
/* eslint-enable no-console */
const prototype = Object.getPrototypeOf(data);
// Storage of new values.
// New instances are created when overwriting is disabled.
const store: Transformable = overwrite
? data
: !prototype
? Object.create(null)
: new prototype.constructor();
// We need to clean up all entries before overwriting.
let series:
| Iterable<[unknown, unknown]>
| IterableIterator<[unknown, unknown]>;
if (isFormData(data) || isURLSearchParams(data)) {
// Create native iterator from FormData/URLSearchParams
series = data.entries();
if (overwrite) {
// When overwriting, native iterator needs to be copied as array.
series = [...series];
for (const [key] of series) {
data.delete(key as string);
}
}
} else {
// Create array from objects
series = Object.entries(data);
// Array keys never change, so we don't need to clean up
if (overwrite && !Array.isArray(data)) {
for (const [key] of series) {
delete data[key as string];
}
}
}
for (const [key, value] of series) {
if (isFormData(store) || isURLSearchParams(store)) {
store.append(fn(key as string), value as string & File);
} else if (key !== '__proto__') {
store[Array.isArray(data) ? Number(key) : fn(`${key}`)] =
transformObjectUsingCallbackRecursive(value, fn, overwrite);
}
}
return store;
};
const transformObjectUsingCallback = (
data: unknown,
fn: CaseFunction,
options?: ObjectTransformerOptions
): unknown => {
fn = applyCaseOptions(fn, {
stripRegexp: /[^A-Z0-9[\]]+/gi,
...options?.caseOptions,
});
if (options?.preservedKeys) {
fn = preserveSpecificKeys(fn, options.preservedKeys);
}
return transformObjectUsingCallbackRecursive(
data,
fn,
options?.overwrite || false
);
};
export const createObjectTransformer: CreateObjectTransformer = (fn) => {
return (data, options): ReturnType<ReturnType<CreateObjectTransformer>> => {
return transformObjectUsingCallback(data, fn, options);
};
};
export const createObjectTransformerOf: CreateObjectTransformerOf = (
functionName,
options
) => {
return createObjectTransformer(
options?.[functionName] || caseFunctions[functionName]
);
};
export const createObjectTransformers: CreateObjectTransformers = (options) => {
const functionNames = Object.keys(caseFunctions) as CaseFunctionTypes[];
const objectTransformers = {} as ObjectTransformers;
for (const functionName of functionNames) {
objectTransformers[functionName] = createObjectTransformerOf(
functionName,
options
);
}
return objectTransformers;
};