Created
June 7, 2022 16:45
-
-
Save robertvanhoesel/059ab8792d30b328418a5743cc43443b to your computer and use it in GitHub Desktop.
Format and parse objects in a single url query param
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type JsonParamPrimitiveValue = string | number | boolean | null; | |
export type JsonParamObject = Record<string, JsonParamPrimitiveValue | JsonParamPrimitiveValue[]>; | |
export type JsonParamValue = JsonParamPrimitiveValue | JsonParamValue[] | JsonParamObject; | |
export type JsonParam = Record<string, JsonParamValue>; | |
function formatValue(value: JsonParamValue): string { | |
if (typeof value == 'number') return `${value}`; | |
else if (value === null) return 'null'; | |
else if (typeof value == 'boolean' && value) return undefined; | |
else if (typeof value == 'boolean') return 'false'; | |
else if (value === 'true' || value === 'false' || !isNaN(+value)) return `'${formatString(value as string)}'`; | |
else if (Array.isArray(value)) return wrap(formatArray(value)); | |
else if (typeof value == 'object') return wrap(formatObject(value)); | |
else return formatString(value); | |
} | |
function parseValue(value: string | undefined, depth: number): JsonParamValue | JsonParam { | |
if (value == undefined) return true; | |
else if (value.startsWith('(') && value.endsWith(')')) | |
return (value.includes(':') ? parseObject : parseArray)(value.slice(1, -1), depth); | |
else if (value == 'null') return null; | |
else if (value === 'true') return true; | |
else if (value === 'false') return false; | |
else if (value.match(/^-?\d+\.?\d+$/)) return +value; | |
else return parseString(value); | |
} | |
const formatString = (str: string) => encodeURIComponent(str).replace(/\(/g, '%28').replace(/\)/g, '%29').replace(`'`, '%27'); | |
const parseString = (raw: string): string => decodeURIComponent(raw.startsWith(`'`) && raw.endsWith(`'`) ? raw.slice(1, -1) : raw); | |
const formatArray = (array: JsonParamValue[]) => array.map(formatValue).join(','); | |
const parseArray = (raw: string) => raw.split(',').map(parseValue) as JsonParamValue[]; | |
const formatObject = (object: JsonParam) => | |
Object.entries(object) | |
.map(([key, value]) => { | |
const formatted = formatValue(value); | |
if (formatted === undefined) return key; | |
else return `${key}:${formatted}`; | |
}) | |
.join(','); | |
const parseObject = (raw: string, depth = 0) => { | |
const initialDepth = depth; | |
const object: Record<string, JsonParamValue> = {}; | |
let substringbegin = 0; | |
for (const [i, ch] of [...raw].entries()) { | |
const end = i === raw.length - 1; | |
if (ch === '(') depth++; | |
if (ch === ')') depth--; | |
if ((ch === ',' || end) && depth == initialDepth) { | |
const substr = raw.slice(substringbegin, end ? undefined : i); | |
const [key, value] = parseKeyValue(substr, initialDepth); | |
object[key] = value; | |
substringbegin = i + 1; | |
} | |
if (depth < 0) throw new Error(`Unexpected closing parantheses at pos ${i} in ${raw}`); | |
if (end && depth > 0) throw new Error(`Unexpected end of string at pos ${i} in ${raw}, expected closing parantheses`); | |
} | |
return object; | |
}; | |
function parseKeyValue(raw: string, depth: number): [string, any] { | |
const separator = raw.indexOf(':'); | |
if (separator == -1) return [raw, parseValue(undefined, depth)]; | |
const key = raw.slice(0, separator); | |
const value = raw.slice(separator + 1); | |
return [key, parseValue(value, depth)]; | |
} | |
const wrap = (value: string) => `(${value})`; | |
export const jsonParam = { | |
format: formatObject, | |
parse: parseObject, | |
formatValue, | |
parseValue, | |
formatObject, | |
parseObject, | |
}; | |
export const formatJsonParam = formatObject; | |
export const parseJsonParam = parseObject; | |
export default jsonParam; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment