UNPKG

@signalwire/core

Version:
126 lines (112 loc) 3.51 kB
import { SnakeToCamelCase, ConverToExternalTypes } from '../types/utils' const toDateObject = (timestamp?: number) => { if (typeof timestamp === 'undefined') { return timestamp } const date = new Date(timestamp * 1000) /** * If for some reason we can't convert to a valid date * we'll return the original value */ if (isNaN(date.getTime())) { return timestamp } return date } const DEFAULT_OPTIONS = { /** * Properties coming from the server where their value will be * converted to camelCase */ propsToUpdateValue: [ 'updated', 'layers', 'members', 'recordings', 'playbacks', ], } /** * Follows the same convention as `src/types/utils/IsTimestamp` */ const isTimestampProperty = (prop: string) => { return prop.endsWith('At') } export type ToExternalJSONResult<T> = { [Property in NonNullable<keyof T> as SnakeToCamelCase< Extract<Property, string> >]: ConverToExternalTypes<Extract<Property, string>, T[Property]> } /** * Converts a record (a JSON coming from the server) to a JSON meant * to be consumed by our users. This mostly mean converting properties * from snake_case to camelCase along with some other minor case * convertions to guarantee that our JS users will always interact * with camelCase properties. * * It's worth noting that this util is suited exactly to meet our * needs and won't (propertly) handle cases where the input record * doesn't have all its properties with casing other than snake_case. * This is on purpose to keep this util as light and fast as possible * since we have the guarantee that the server will always send their * payloads formatted this way. * @internal */ export const toExternalJSON = <T extends object>( input: T, options: typeof DEFAULT_OPTIONS = DEFAULT_OPTIONS ) => { // @ts-expect-error if (input?.__sw_symbol || input?.__sw_proxy) { // Return if the input is a BaseComponent or a Proxy return input as unknown as ToExternalJSONResult<T> } return Object.entries(input).reduce((reducer, [key, value]) => { const prop = fromSnakeToCamelCase(key) as any const propType = typeof value /** * While this check won't be enough to detect all possible objects * it would cover our needs here since we just need to detect that * it's not a primitive value */ if (propType === 'object' && value) { if (Array.isArray(value)) { if (options.propsToUpdateValue.includes(key)) { reducer[prop] = value.map((v) => { if (typeof v === 'string') { return fromSnakeToCamelCase(v) } return toExternalJSON(v) }) } else { reducer[prop] = value } } else { reducer[prop] = toExternalJSON(value as T) } } else { if (isTimestampProperty(prop)) { reducer[prop] = toDateObject(value) } else { reducer[prop] = value } } return reducer }, {} as Record<string, unknown>) as ToExternalJSONResult<T> } /** * Converts values from snake_case to camelCase * @internal */ export const fromSnakeToCamelCase = (input: string) => { if (!input.includes('_')) { return input } return input.split('_').reduce((reducer, part, index) => { const fc = part.trim().charAt(0) const remainingChars = part.substr(1).toLowerCase() return `${reducer}${ index === 0 ? fc.toLowerCase() : fc.toUpperCase() }${remainingChars}` }, '') }