Search code examples
javascriptjsonrecursionurldecode

Making a recursive algorithm for converting URL string into JSON


I found myself having to process a string like:

foo=bar&foo1=foo%3Dbar%26foo2%3Dfoo%253Dbar

Into:

{
  "foo": "bar",
  "foo1": {
    "foo": "bar",
    "foo2": {
      "foo": "bar"
    }
  }
}

A real input example.

My best attempt is:

function parse(input) {
  try {
    const parsed = JSON.parse(input);
    return parseJSON(parsed);
  } catch (err) {
    const decodedInput = decodeURIComponent(input);
    if (input.includes("&") && input.includes("=")) {
      return input.split("&").reduce((json, part) => {
        const [key, value] = part.split("=");
        const decodedValue = decodeURIComponent(value);
        return { ...json, [key]: parsePrimitive(decodedValue) };
      }, {});
    }
    return decodedInput;
  }
}

function parsePrimitive(input) {
  if (!isNaN(input)) {
    return Number(input);
  }
  if (input === "true" || input === "false") {
    return input === "true";
  }
  return parse(input);
}

function parseJSON(input) {
  return Object.entries(input).reduce((json, [key, value]) => {
    let object = {};
    if (typeof value === "object") {
      if (Array.isArray(value)) {
        object[key] = value;
      } else {
        object[key] = parseJSON(value);
      }
    } else {
      const decodedValue = decodeURIComponent(value);
      if (decodedValue.includes("&") && decodedValue.includes("=")) {
        object[key] = parse(decodedValue);
      } else {
        object[key] = parsePrimitive(decodedValue);
      }
    }
    return { ...json, ...object };
  }, {});
}

If you try to run it, you're supposed to call parse(input)
However, it does fail for certain inputs

How can I make the perfect recursive algorithm for this kind of problem?
Thanks!


Solution

  • I reworked the algorithm using Object.fromEntries(new URLSearchParams()).

    function parse(query) {
      try {
        return JSON.parse(query);
      } catch {
        if (!isNaN(query)) {
          return Number(query);
        }
    
        if (typeof query !== "string") {
          const obj = {};
          for (const queryKey in query) {
            if (query.hasOwnProperty(queryKey)) {
              obj[queryKey] = parse(query[queryKey]);
            }
          }
    
          return obj;
        }
        if (!query) {
          return "";
        }
    
        if (query.toLowerCase().match(/^(true|false)$/)) {
          return query.toLowerCase() === "true";
        }
    
        const object = Object.fromEntries(new URLSearchParams(query));
        const values = Object.values(object);
        if (values.length === 1 && values[0] === "") {
          return query;
        }
        return parse(object);
      }
    }
    
    
    
    const q = 'foo=bar&foo1=foo%3Dbar%26foo2%3Dfoo%253Dbar';
    console.log(parse(q));
    
    console.log('fetching larger example...');
    
    fetch('https://gist.githubusercontent.com/avi12/cd1d6728445608d64475809a8ddccc9c/raw/030974baed3eaadb26d9378979b83b1d30a265a3/url-input-example.txt')
      .then(response => response.text())
      .then(parse)
      .then(console.log);
    .as-console-wrapper { max-height: 100% !important; top: 0; }