制作一个将 URL 字符串转换为 JSON 的递归算法

我发现自己必须处理如下字符串:


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

进入:


{

  "foo": "bar",

  "foo1": {

    "foo": "bar",

    "foo2": {

      "foo": "bar"

    }

  }

}


我最好的尝试是:

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 };

  }, {});

}

如果您尝试运行它,您应该调用parse(input)

但是,对于某些输入它确实会失败


如何针对此类问题制定完美的递归算法?


拉风的咖菲猫
浏览 154回答 3
3回答

桃花长相依

这似乎适用于您的简单示例和更复杂的示例(现已更新以处理数字和布尔值):const parse = (query) =>  query .startsWith ('{')    ? JSON .parse (query)  : query .includes ('&') || query .includes ('=')    ? Object .fromEntries (        query .split ('&')           .map (p => p .split ('='))          .map (([k, v]) => [k, parse (decodeURIComponent (v))])      )  : query .includes (',')    ? query .split (',') .filter (Boolean) .map (parse)  : isFinite (query)    ? Number (query)  : query .toLowerCase () == "true" || query .toLowerCase () == "false"    ? query .toLowerCase () == "true"  : // else    queryconst 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 (res => res .text ())  .then (parse)  .then (console .log).as-console-wrapper {max-height: 100% !important; top: 0}有两个部分值得关注。首先,这对逗号做了一个假设:它们表示数组元素之间的分隔。而且,更进一步,它假设空字符串不是有意的,从而将watermark=%2Chttps%3A%2F%2Fs.ytimg.com%2Fyts%2Fimg%2Fwatermark%2Fyoutube_watermark-vflHX6b6E.png%2Chttps%3A%2F%2Fs.ytimg.com%2Fyts%2Fimg%2Fwatermark%2Fyoutube_hd_watermark-vflAzLcD6.png进入这个:watermark: [  "https://s.ytimg.com/yts/img/watermark/youtube_watermark-vflHX6b6E.png",  "https://s.ytimg.com/yts/img/watermark/youtube_hd_watermark-vflAzLcD6.png"]原始内容以编码的逗号 ( %2C) 开头,这会导致初始空字符串,因此我们使用.filter (Boolean)删除它。其次,对表示 JSON 的字符串的测试非常幼稚,仅执行.startsWith ('{'). 您可以将其替换为您需要的任何内容,但这会导致意图问题。我不确定我们是否可以以这种方式完全通用地编写此内容。不过,我认为已经很接近了。而且代码相当干净。然而,我确实想知道为什么。这么多数据将会遇到各种 url 大小限制。此时,将其放入请求正文而不是 url 参数不是更有意义吗?

POPMUISE

您可以通过检查编码符号来采用递归方法=。const getValues = string => string.split('&')    .reduce((r, pair) => {      let [key, value] = pair.split('=');      value = decodeURIComponent(value);      r[key] = value.includes('=')        ? getValues(value)        : value;      return r;    }, {});console.log(getValues('foo=bar&foo1=foo%3Dbar%26foo2%3Dfoo%253Dbar'));

呼啦一阵风

我使用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; }

莫回无

Node.js 附带了一个内置的“querystring”npm 包实用程序,但在这里我使用了一个更好的实用程序,称为“qs”。您可以在数组中指定分隔符,而不是仅对前者使用一个分隔符。如果您想使用内置的“querystring”包,则需要在调用解析时删除分隔符数组,并检查字符串以查看使用的分隔符 - 您提供的示例文件使用了几个不同的分隔符。所以试试这个:const qs = require("qs");let params = `foo=bar&foo1=foo%3Dbar%26foo2%3Dfoo%253Dbar`;const isObject = (param) => {  try {    let testProp = JSON.parse(param);    if (typeof testProp === "object" && testProp !== null) {      return true;    }    return false;  } catch (e) {    return false;  }};const isURL = (value) => {  try {    new URL(value);  } catch (e) {    return false;  }  return true;};const isQueryString = (value) => {  if (/[/&=]/.test(value) && !isURL(value)) {    return true;  } else {    return false;  }};const parseData = (data, parsed = false) => {  if (isQueryString(data) && !parsed) {    return parseData(qs.parse(data, { delimiter: /[;,/&]/ }), true);  } else if (isObject(data) || parsed) {    for (let propertyName in data) {      if (isObject(data[propertyName])) {        data[propertyName] = parseData(JSON.parse(data[propertyName]), true);      } else {        data[propertyName] = parseData(data[propertyName]);      }    }    return data;  } else {    return data;  }};let s = parseData(params);console.log(JSON.stringify(s, null, 2));
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

JavaScript