如何将查询字符串转换为多级对象

How to convert query string to multi level object

我目前正在尝试将 URL 查询字符串转换为 JavaScript 对象,但进展并不顺利。我查看了 Stack Overflow 以寻找可能的解决方案,但还没有完全找到我要找的东西。这是查询字符串:

"class%5Blocation_id%5D=122&student%5Bgender%5D=&student%5Bpicture%5D=&class%5Bquestions%5D%5B2775%5D%5Banswers%5D%5B%5D=Black+canary&ids%5B%5D=32&class%5Bquestions%5D%5B2775%5D%5Banswer%5D=&class%5Bquestions%5D%5B2776%5D%5Banswers%5D%5B%5D=Blue+Whistle&class%5Bquestions%5D%5B2776%5D%5Banswer%5D=&class%5Bdescription%5D="

我正在寻找这样的东西:

{
   class: {
     description: '',
     location_id: '122',
     questions: {
       2275: {
         answer: '',
         answers: ['Black canary']
       },
       2276: {
         answer: '',
         answers: ['Blue Whistle']
      }
     }
   },
   ids: ['32']
   student: {
     gender: '',
     picture: ''
   }
}

我尝试使用名为 query-string 的库,但这是我得到的:

{
  'class[description]': '',
  'class[location_id]': '122',
  'class[questions][2775][answer]': '',
  'class[questions][2775][answers][]': 'Black canary',
  'class[questions][2776][answer]': '',
  'class[questions][2776][answers][]': 'Blue Whistle',
  'ids[]': '32',
  'student[gender]': '',
  'student[picture]': '' 
}

我尝试使用我发现的两个实现 here:

  function form2Json(str) {
    "use strict";
    var obj, i, pt, keys, j, ev;
    if (typeof form2Json.br !== 'function') {
      form2Json.br = function (repl) {
        if (repl.indexOf(']') !== -1) {
          return repl.replace(/\](.+?)(,|$)/g, function (, , ) {
            return form2Json.br( + '}' + );
          });
        }
        return repl;
      };
    }
    str = '{"' + (str.indexOf('%') !== -1 ? decodeURI(str) : str) + '"}';
    obj = str.replace(/\=/g, '":"').replace(/&/g, '","').replace(/\[/g, '":{"');
    obj = JSON.parse(obj.replace(/\](.+?)(,|$)/g, function (, , ) { return form2Json.br( + '}' + ); }));
    pt = ('&' + str).replace(/(\[|\]|\=)/g, '""').replace(/\]"+/g, ']').replace(/&([^\[\=]+?)(\[|\=)/g, '"&["]');
    pt = (pt + '"').replace(/^"&/, '').split('&');
    for (i = 0; i < pt.length; i++) {
      ev = obj;
      keys = pt[i].match(/(?!:(\["))([^"]+?)(?=("\]))/g);
      for (j = 0; j < keys.length; j++) {
        if (!ev.hasOwnProperty(keys[j])) {
          if (keys.length > (j + 1)) {
            ev[keys[j]] = {};
          }
          else {
            ev[keys[j]] = pt[i].split('=')[1].replace(/"/g, '');
            break;
          }
        }
        ev = ev[keys[j]];
      }
    }
    return obj;
  }

但结果是这样的:

  {
    class: {
      description: "",
      location_id: "122"
    },
    questions:{
         2775: {answers: "Black+canary", answer: ""}
         2776: {answers: "Blue+Whistle", answer: ""}
    },
    ids: {
         "": "32"
    },
    student: {
         gender: ""
         picture: ""
    }
   }

ids 成为对象而不是数组,answers 成为字符串。 我能得到的最接近它的是使用这个:

   function form2Json(str) {
    "use strict";
    var pairs = str.split('&'),
      result = {};

    for (var i = 0; i < pairs.length; i++) {
      var pair = pairs[i].split('='),
        key = decodeURIComponent(pair[0]),
        value = decodeURIComponent(pair[1]),
        isArray = /\[\]$/.test(key),
        dictMatch = key.match(/^(.+)\[([^\]]+)\]$/);

      if (dictMatch) {
        key = dictMatch[1];
        var subkey = dictMatch[2];

        result[key] = result[key] || {};
        result[key][subkey] = value;
      } else if (isArray) {
        key = key.substring(0, key.length - 2);
        result[key] = result[key] || [];
        result[key].push(value);
      } else {
        result[key] = value;
      }
    }

    return result;
  }

结果如下:

{
   class: {location_id: "122", description: ""},
   class[questions][2775]: {answer: ""},
   class[questions][2775][answers]: ["Black+canary"],
   class[questions][2776]: {answer: ""},
   class[questions][2776][answers]: ["Blue+Whistle"],
   ids: ["32"],
   student: {gender: "", picture: ""}
}

...只有一级关联,ids 的数组被正确分配为数组。我怎样才能使这项工作?

您可以使用 URLSearchParams:

function form2Object(str) {
    let result = {};
    for (let [path, value] of new URLSearchParams(str).entries()) {
         let keys = path.match(/[^[\]]+/g);
         let prop = keys.pop();
         let acc = result;
         for (let key of keys) acc = (acc[key] = acc[key] || {});
         acc[prop] = path.endsWith("[]") 
                   ? (acc[prop] || []).concat(value)
                   : value;
    }
    return result;
}
// Demo
let str = "class%5Blocation_id%5D=122&student%5Bgender%5D=&student%5Bpicture%5D=&class%5Bquestions%5D%5B2775%5D%5Banswers%5D%5B%5D=Black+canary&ids%5B%5D=32&class%5Bquestions%5D%5B2775%5D%5Banswer%5D=&class%5Bquestions%5D%5B2776%5D%5Banswers%5D%5B%5D=Blue+Whistle&class%5Bquestions%5D%5B2776%5D%5Banswer%5D=&class%5Bdescription%5D=";
console.log(form2Object(str));

对于旧版浏览器

这是 EcmaScript 2015 兼容的:

function form2Object(str) {
    var result = {};
    str.split("&").map(function(eq) {
        return eq.split("=").map(decodeURIComponent);
    }).forEach(function (entry) {
        var path = entry[0];
        var value = entry[1];
        var keys = path.match(/[^[\]]+/g);
        var prop = keys.pop();
        var acc = keys.reduce(function (acc, key) {
            return (acc[key] = acc[key] || {});
        }, result);
        acc[prop] = path.slice(-2) === "[]"
                  ? (acc[prop] || []).concat(value)
                  : value;
    });
    return result;
}
// Demo
let str = "class%5Blocation_id%5D=122&student%5Bgender%5D=&student%5Bpicture%5D=&class%5Bquestions%5D%5B2775%5D%5Banswers%5D%5B%5D=Black+canary&ids%5B%5D=32&class%5Bquestions%5D%5B2775%5D%5Banswer%5D=&class%5Bquestions%5D%5B2776%5D%5Banswers%5D%5B%5D=Blue+Whistle&class%5Bquestions%5D%5B2776%5D%5Banswer%5D=&class%5Bdescription%5D=";

console.log(form2Object(str));