자바스크립에서 쿼리스트링 포맷/파싱하기

vraimentres·1일 전
2

쿼리스트링은 key-value 쌍으로 되어있는 일종의 토큰이다. 원리는 단순하지만 이걸 마음껏 조작하기는 불편하다.
때문에, 객체를 쿼리스트링으로, 다시 쿼리스트링을 객체로 바꿔주는 라이브러리들도 존재한다.

이 쿼리스트링 조작함수를 아주 원시적인 형태 수준으로 직접 구현해보자

파싱

http://website.com/aaa/bbb?ccc=ddd&eee=fff&ggg=hhh 이렇게 생긴 url 이 있다 치자

물음표 기호 기준으로 오른쪽만 꺼내자. 그냥 split 메서드를 쓰면 된다.

const parseQueryString = (url) => {
  const [_, query] = url.split('?')
}

url 에서 ? 기호는 유일무이 하다. 다시말해 딱 한번만 존재한다. 어떻게 확신하냐고? 그냥 규칙이 그렇다.

이렇게 만든 query 변수는 문자열이거나 undefined 이다. 물음표 기호가 없다면 배열에 요소가 하나밖에 안 만들어지고, _ 에 담긴다. 물음표 기호가 있을때만 query 변수에 값이 생기는거다.

쿼리가 없다면 빈 객체를 리턴하도록 하자

const parseQueryString = (url) => {
  const [_, query] = url.split('?')
  if (!query) return {}
}

이제 query 변수엔 이런 형태의 문자열이 담긴다.

ccc=ddd&eee=fff&ggg=hhh

각각의 쿼리 쌍은 & 기호로 연결되어있다. 저걸 분리하자. 마찬가지로 split 을 쓰면 된다.

const parseQueryString = (url) => {
  const [_, query] = url.split('?')
  if (!query) return {}
  const queries = query.split('&')
}

queries 엔 다음과 같은 배열이 담긴다.
[ 'ccc=ddd', 'eee=fff', 'ggg=hhh' ]

이제 = 기호 기준으로 각각 왼쪽은 키고 오른쪽은 값이 되도록 하면 되겠다. 저 배열을 map 으로 반복시키면서 다시 split 하자

const parseQueryString = (url) => {
  const [_, query] = url.split('?')
  if (!query) return {}
  const queries = query.split('&')
  const queries2 = queries.map((e) => {
    const [key, value] = e.split('=')
    return { key, value }
  })
}

우리는 각 요소를 = 기준으로 split 했을때 무조건 요소가 두개씩 나올거라는걸 알고있다.
= 기준으로 왼쪽은 키고 오른쪽은 값이니깐.

그러니 비구조화할당 해서 각각 key, value 라는 변수명으로 만들고 객체로 리턴하자.

결과는 다음처럼 나온다.

[
  { key: 'ccc', value: 'ddd' },
  { key: 'eee', value: 'fff' },
  { key: 'ggg', value: 'hhh' }
]

이제 조금 어려운게 등장했다.
우리는 이미 우리가 어떤 형태의 객체를 원하는지 알고있다.

{ ccc: 'ddd', eee: 'fff', ggg: 'hhh' }

이렇게 나와야 하는것이다.

가장 원시적인 방법으로 이 객체를 만드는 방법은, 빈 객체를 만든 뒤 for 문으로 배열을 반복시키면서 만들어둔 배열에 속성을 할당하는 것이다.

const queries2 = [
  { key: 'ccc', value: 'ddd' },
  { key: 'eee', value: 'fff' },
  { key: 'ggg', value: 'hhh' }
]

const queries3 = {}

for (let i = 0; i < queries2.length; i++) {
  const current = queries2[i]
  queries3[current.key] = current.value
}

하지만 ES6의 computed property, object spread property 문법과 배열의 map 메서드를 활용하면 더 간단하게 만들 수 있다.

const queries3 = queries2.reduce((acc, curr) => {
  const {key, value} = curr
  return { ...acc, [key]: value }
}, {})

우리가 만들고 있던 함수에 넣으면 이렇게 된다.

const parseQueryString = (url) => {
  const [_, query] = url.split('?')
  if (!query) return {}
  const queries = query.split('&')
  const queries2 = queries.map((e) => {
    const [key, value] = e.split('=')
    return { key, value }
  })
  const queries3 = queries2.reduce((acc, curr) => {
    const { key, value } = curr
    return { ...acc, [key]: value }
  }, {})
  return queries3
}

끝난건가...?

하지만 우리는 이 함수를 좀 더 줄일 수 있다.

map과 reduce 는 배열의 메서드이다. 그리고 split과 map과 reduce는 배열을 리턴한다.

이 두개의 사실을 이용해 우리는 메서드 체이닝을 할 수 있다

const parseQueryString = (url) => {
  const [_, query] = url.split('?')
  if (!query) return {}
  return query.split('&')
    .map((e) => {
      const [key, value] = e.split('=')
      return { key, value }
    })
    .reduce((acc, curr) => {
      const { key, value } = curr
      return { ...acc, [key]: value }
    }, {})
}

배열을 map을 돌리고 또 다시 그걸 reduce 하지 않고 그냥 reduce 안에 로직을 다 때려넣을수 있다.

const parseQueryString = (url) => {
  const [_, query] = url.split('?')
  if (!query) return {}
  return query.split('&').reduce((acc, e) => {
    const [key, value] = e.split('=')
    return { ...acc, [key]: value }
  }, {})
}

완성이다.

사실 실사용하기위해선 좀더 여러가지 기능들이 들어가야 하지만, 시간이 없으니 그만 알아보기로 하자.

0개의 댓글