Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript: recursively encode and decode querystring. (object to querystring, querystring to object)

I want to encode a complex json/javascript object into the standard querystring encoding. And i want to decode this querystring back to an json/javascript object. It should be recursively, with arrays, objects, strings, booleans and numbers.

I thought this should be easy, but was proven wrong. Does anyone have an idea, how to solve this problem? Either in Javascript or preferably in Typescript.

like image 547
Scriptomaniac Avatar asked Sep 01 '26 22:09

Scriptomaniac


1 Answers

I think, what you want to do, is encode and decode nested objects.

There is no single standard, but very often, QS (Query String) syntax is used:

{
    "attribute": "value",
    "array": ["apples", "bananas"],
    "object": { "number": 55 },
}

will become:

?attribute=value&array[0]=apples&array[1]=bananas&object[number]=55

Example code:

function decode(querystring: string): object {
    function parseValue(value: string): any {
        if (value === 'TRUE') return true;
        if (value === 'FALSE') return false;
        return isNaN(Number(value)) ? value : Number(value);
    }

    function dec(list: any[], isArray = false): object {
        let obj: any = isArray ? [] : {};

        let recs: any[] = list.filter((item) => {
            if (item.keys.length > 1) return true;
            obj[item.keys[0]] = parseValue(item.value);
        });

        let attrs = {};
        recs.map((item) => {
            item.key = item.keys.shift();
            attrs[item.key] = [];
            return item;
        }).forEach((item) => attrs[item.key].push(item));

        Object.keys(attrs).forEach((attr) => {
            let nextKey = attrs[attr][0].keys[0];
            obj[attr] = dec(attrs[attr], typeof nextKey === 'number');
        });

        return obj;
    }

    return dec(
        querystring
            .split('&')
            .map((item) => item.split('=').map((x) => decodeURIComponent(x)))
            .map((item) => {
                return {
                    keys: item[0]
                        .split(/[\[\]]/g)
                        .filter((n) => n)
                        .map((key) => (isNaN(Number(key)) ? key : Number(key))),
                    value: item[1],
                };
            })
    );
}

export function encode(object: object): string {
    function reducer(obj, parentPrefix = null) {
        return function (prev, key) {
            const val = obj[key];
            key = encodeURIComponent(key);
            const prefix = parentPrefix ? `${parentPrefix}[${key}]` : key;

            if (val == null || typeof val === 'function') {
                prev.push(`${prefix}=`);
                return prev;
            }

            if (typeof val === 'boolean') {
                prev.push(`${prefix}=${val.toString().toUpperCase()}`);
                return prev;
            }

            if (['number', 'string'].includes(typeof val)) {
                prev.push(`${prefix}=${encodeURIComponent(val)}`);
                return prev;
            }

            prev.push(
                Object.keys(val).reduce(reducer(val, prefix), []).join('&')
            );
            return prev;
        };
    }

    return Object.keys(object).reduce(reducer(object), []).join('&');
}
like image 128
kopilot.schneider Avatar answered Sep 03 '26 13:09

kopilot.schneider