Перспектива создания синтаксического анализатора и построителя запросов может показаться пугающей, как это было для меня, но пусть это вас не пугает, поиск пути решения этой задачи проще, чем вы думаете, это помогает разбить проблему на части и сосредоточиться на ней. отдельные шаги до конечной реализации. Это конкретное задание также является отличной возможностью проверить свои знания в области кодирования и отточить свои навыки решения проблем.

Вот типичная строка запроса: "?foo=hello&bar=world&baz&baz=again"

Обычно строка запроса будет иметь несколько параметров, разделенных амперсандом &, каждый из которых состоит из имени поля и значения, разделенных знаком равенства =.

Знак вопроса в начале строки указывает, где заканчивается путь к файлу и начинается строка запроса.

Поскольку протокол HTTP не имеет состояния, одним из способов поддержания состояния является URL-адрес, в котором мы храним данные с помощью строк запроса.

Разбор строки запроса

Получив входную строку запроса, как мы можем проанализировать ее и встроить в структуру данных, которую мы затем сможем использовать во всей нашей программе?

Используя приведенный выше пример строки запроса, желаемый результат должен быть объектом, где пары ключ-значение создаются с использованием параметров в строке запроса: { foo: "hello", bar: "world", baz: ["true", "again"] }

Имя поля слева от знака равенства будет ключом, а слово справа от знака равенства будет его значением.

В случае, когда нет знака равенства, значение этого конкретного ключа должно быть установлено на значение по умолчанию "true".

Если в строке есть несколько вхождений одного и того же ключа, его значение должно быть массивом всех значений, связанных с этим словом в строке, поэтому в приведенном выше примере с двумя вхождениями слова "baz" результатом должен быть массив с первый элемент имеет значение "true", так как он существует один без значения, а второй элемент — "again", поскольку во втором вхождении в строку он устанавливается равным этому слову.

Для начала давайте определим наш объект, который мы будем создавать по всей программе и в конечном итоге возвращать:

let result = {};

Двигаясь вперед, наша следующая цель — проанализировать строку, давайте начнем с удаления символа вопросительного знака:

let queryString = query.split('?').join('');

Другой способ сделать это:

let queryString = query.substr(1);

С учетом этого мы можем работать с параметрами, с каждой парой, разделенной амперсандом, мы можем создать массив параметров, разделив на них строку:

let parameters = queryString.split('&');

Следующим шагом будет повторение каждого из этих параметров и создание желаемого объекта.

parameters.forEach(element => {})

Работая внутри операторов скобок, на каждой итерации нам нужно найти и установить наши пары ключ-значение, что требует еще одного разбиения с использованием текущего элемента, на этот раз по знаку равенства.

let keyVal = element.split('=');
let key = keyVal[0];

Теперь самое интересное, нам нужно обратить внимание на две вещи:

  1. Имеет ли этот ключ значение? Практически говоря, установлен ли знак равенства для значения справа от него?
  2. Этот ключ появляется в строке более одного раза?

Глядя непосредственно на результат разбиения этих параметров, мы обнаружим, что те, у которых есть знак равенства, имеют два элемента, а те, у которых нет, содержат только один, разница заключается в длине массива. Если длина больше единицы, она должна иметь значение:

let valuePresent = (keyVal.length > 1);

Используя тройку, мы можем проверить, присутствует ли значение, если да, установить его на его значение, если нет, установить его на значение по умолчанию "true"

let val = valuePresent ? keyVal[1] : 'true';

Мы также должны помнить о повторяющихся ключах, для этого мы должны проверить, существует ли уже ключ в объекте, если он уже существует, нам нужно установить его значение в массив всех значений, связанных с ним, но не перед сохранением предыдущего значения, чтобы мы не потеряли его при создании нашего массива.

if (key in result) {
  let oldVal = result[key];
  result[key] = [oldVal, val];
} else {
  result[key] = val;
}

Хм, кажется, это работает, если есть только два вхождения ключа, но если их больше, скажем, мы добавляем к нашей строке string"baz=triple", мы получаем это:

{ foo: 'hello',
  bar: 'world',
  baz: [ [ 'true', 'again' ], 'triple' ] }

Вложенный массив, не совсем то, что нам нужно.

Как мы можем справиться со сценарием, в котором предыдущее значение уже является массивом? Что ж, мы можем проверить, является ли значение этого ключа массивом с Array.isArray(), а затем выполнить конкретную задачу, когда это правда, удобный инструмент, который у нас есть для работы с вложенным массивом, — это .concat()

if (Array.isArray(result[key])) {
  result[key] = oldVal.concat(val);
} else {
  result[key] = [oldVal, val];
}

Здорово! В этот момент объект result вернет:

{ foo: 'hello',
  bar: 'world',
  baz: [ 'true', 'again', 'triple' ] }

Парсер пользовательской строки запроса завершен!

Вот полный код нашего парсера строки запроса:

Построитель строки запроса

Давайте поменяем цель и преобразуем объект в строку запроса.

План состоит в том, чтобы формировать параметры из пар ключ-значение, ключей, равных значениям со знаком равенства, каждая пара делится амперсандом, и, конечно же, не забудем символ вопросительного знака!

Во-первых, давайте объявим массив, в котором мы будем хранить данные:

let queryStringArray = [];

Затем мы перебираем наш объект и сохраняем каждый ключ и значение, разделенные знаком равенства, в виде строки в нашем массиве:

for (key in queryObject) {
  queryStringArray.push(key + '=' + queryObject[key]);
}

Задерживать! Что делать, если значение представляет собой массив? В настоящее время наш массив будет выводить:

[ 'foo=hello', 'bar=world', 'baz=true,again' ]

Обратите внимание, что последний элемент имеет два значения, так как в этой итерации значение было массивом с двумя значениями.

Чтобы добиться желаемого результата, нам нужно погрузиться глубже и добавить условное выражение для обработки случая, когда значение является массивом. Подобно тому, что мы сделали с нашим парсером запросов, мы можем проверить, является ли значение массивом, используя Array.isArray(), и выполнить итерацию по массиву.

if (Array.isArray(queryObject[key])) {
  queryObject[key].forEach(val => {
      queryStringArray.push(key + '=' + val);
  })
} else {
  queryStringArray.push(key + '=' + queryObject[key]);
}

Мы приближаемся! Нам все еще нужно помнить о ключе, значение которого установлено по умолчанию "true".

Время для другого условного:

} else if (queryObject[key] === 'true') {
  queryStringArray.push(key);
}

Это работает для значений, которые находятся за пределами массива, нам нужно рассмотреть случай, когда одно из значений в массиве равно "true".

Ты угадал! Время для последнего условного выражения, на этот раз внутри блока кода, который запускается, когда значение является массивом:

if (Array.isArray(queryObject[key])) {
  queryObject[key].forEach(val => {
    if (val === 'true') {
      queryStringArray.push(key)
    } else {
      queryStringArray.push(key + '=' + val)
    }
  })
}

Мы создали массив со всеми нашими параметрами, теперь мы можем соединить их с помощью амперсанда и вернуть их с нашим очень важным вопросительным знаком:

return '?' + queryStringArray.join('&');

Тада! Вернемся к тому, с чего начали: "?foo=hello&bar=world&baz&baz=again"

Вот полный код нашего построителя строки запроса:

Спасибо за прочтение и Happy Coding!