정보실

웹학교

정보실

javascript JavaScript가 포함 된 JSON 파서

본문

이번 주 Cassidoo 주간 뉴스 레터의 인터뷰 질문은


https://lihautan.com/json-parser-with-javascript/ 


유효한 JSON 문자열을 가져 와서 객체 (또는 선택한 언어가 사용하는 모든 것, dict, map 등)로 변환하는 함수를 작성하십시오. 

입력 예 :


fakeParseJSON('{ "data": { "fish": "cake", "array": [1,2,3], "children": [ { "something": "else" }, { "candy": "cane" }, { "sponge": "bob" } ] } } ')


어느 시점에서, 나는 단지 다음과 같이 쓰고 싶은 유혹을 받았습니다


const fakeParseJSON = JSON.parse;


하지만 AST에 관한 기사를 많이 작성했습니다.

컴파일러 파이프 라인의 개요와 AST 조작 방법에 대해 다루지만 파서를 구현하는 방법에 대해서는 다루지 않았습니다.


기사에서 JavaScript 컴파일러를 구현하는 것은 너무 어려운 작업이기 때문입니다.


글쎄요. JSON도 언어입니다. 자체 문법이 있으며 사양에서 참조 할 수 있습니다. 

JSON 파서를 작성하는 데 필요한 지식과 기술은 JS 파서를 작성하는 데 양도 할 수 있습니다.


이제 JSON 파서를 작성해 봅시다!


문법 이해 


사양 페이지를 보면 2 개의 다이어그램이 있습니다.

https://www.json.org/img/object.png 

이미지 출처 : https://www.json.org/img/object.png


json
  element

value
  object
  array
  string
  number
  "true"
  "false"
  "null"

object
  '{' ws '}'
  '{' members '}'


두 다이어그램 모두 동일합니다.


하나는 시각적이고 다른 하나는 텍스트 기반입니다. 텍스트 기반 문법 구문 인 Backus-Naur Form은 일반적으로 이 문법을 구문 분석하고 구문 분석기를 생성하는 다른 구문 분석기에 제공됩니다. 파서 인식이라고 하면! 🤯


이 기사에서는 철도 다이어그램에 중점을 둘 것입니다. 시각적이며 더 친숙해 보였기 때문입니다.


첫 번째 철도 다이어그램을 살펴 보겠습니다.


https://www.json.org/img/object.png 

이미지 출처 : https://www.json.org/img/object.png


이것이 JSON의 “object”문법 입니다.


화살표를 따라 왼쪽에서 시작한 다음 오른쪽에서 끝납니다.


{, ,, :,}와 같은 원은 문자이며, 공백, 문자열 및 값과 같은 상자는 다른 문법의 자리 표시자입니다. 

따라서“whitepsace”을 파싱 하려면 “whitepsace”에 대한 문법을 ​​살펴 봐야 합니다.


따라서 왼쪽에서 시작하여 객체의 경우 첫 번째 문자는 열린 중괄호 {입니다. 여기에는 두 가지 옵션이 있습니다.

  • whitespace → } → end, or
  • whitespace → string → whitespace → : → value → } → end

물론 "value"에 도달하면 다음으로 이동하도록 선택할 수 있습니다.

  • → } → end, or
  • → , → whitespace → … → value

다음으로 가기로 결정할 때까지 계속 반복 할 수 있습니다.

  • → } → end.

이제 우리는 철도 다이어그램에 대해 잘 알고 있다고 생각합니다. 다음 섹션으로 넘어 갑시다.


파서 구현 


다음과 같은 구조로 시작하겠습니다.


function fakeParseJSON(str) {
  let i = 0;
  // TODO
}


우리는 현재 캐릭터의 인덱스로 i를 초기화합니다. str의 끝에 도달하자마자 끝납니다.


“object”에 대한 문법을 ​​구현해 봅시다 :


function fakeParseJSON(str) {
  let i = 0;
  function parseObject() {
    if (str[i] === '{') {
      i++;
      skipWhitespace();

      // if it is not '}',
      // we take the path of string -> whitespace -> ':' -> value -> ...
      while (str[i] !== '}') {
        const key = parseString();
        skipWhitespace();
        eatColon();
        const value = parseValue();
      }
    }
  }
}


parseObject에서 "string"및 "whitespace"와 같은 다른 문법의 구문 분석을 호출하면 모든 구문이 작동합니다.


추가하는 것을 잊어 버린 한 가지는 쉼표입니다. 는 공백 → 문자열 → 공백 → : →…의 두 번째 루프를 시작하기 전에 만 나타납니다.


이를 바탕으로 다음 행을 추가합니다.


function fakeParseJSON(str) {
  let i = 0;
  function parseObject() {
    if (str[i] === '{') {
      i++;
      skipWhitespace();

      let initial = true;      // if it is not '}',
      // we take the path of string -> whitespace -> ':' -> value -> ...
      while (str[i] !== '}') {
        if (!initial) {          eatComma();          skipWhitespace();        }        const key = parseString();
        skipWhitespace();
        eatColon();
        const value = parseValue();
        initial = false;      }
      // move to the next character of '}'
      i++;
    }
  }
}


몇 가지 명명 규칙 :

  • 문법을 기반으로 코드를 구문 분석하고 반환 값을 사용할 때 parseSomething을 호출합니다.
  • 문자가 있을 것으로 예상 할 때 eatSomething을 호출하지만 문자를 사용하지 않습니다.
  • 문자가 없으면 skipSomething이라고 합니다.

eatComma와 eatColon을 구현해 봅시다 :


function fakeParseJSON(str) {
  // ...
  function eatComma() {
    if (str[i] !== ',') {
      throw new Error('Expected ",".');
    }
    i++;
  }

  function eatColon() {
    if (str[i] !== ':') {
      throw new Error('Expected ":".');
    }
    i++;
  }
}


parseObject 문법 구현을 마쳤지만 이 구문 분석 함수의 반환 값은 무엇입니까?


자, 우리는 JavaScript 객체를 반환해야 합니다 :


function fakeParseJSON(str) {
  let i = 0;
  function parseObject() {
    if (str[i] === '{') {
      i++;
      skipWhitespace();

      const result = {};
      let initial = true;
      // if it is not '}',
      // we take the path of string -> whitespace -> ':' -> value -> ...
      while (str[i] !== '}') {
        if (!initial) {
          eatComma();
          skipWhitespace();
        }
        const key = parseString();
        skipWhitespace();
        eatColon();
        const value = parseValue();
        result[key] = value;        initial = false;
      }
      // move to the next character of '}'
      i++;

      return result;    }
  }
}


"개체"문법을 구현하는 것을 보았으므로 이제 "배열"문법을 시험해볼 차례입니다.


https://www.json.org/img/array.png 

이미지 출처 : https://www.json.org/img/array.png


function fakeParseJSON(str) {
  // ...
  function parseArray() {
    if (str[i] === '[') {
      i++;
      skipWhitespace();

      const result = [];
      let initial = true;
      while (str[i] !== ']') {
        if (!initial) {
          eatComma();
        }
        const value = parseValue();
        result.push(value);
        initial = false;
      }
      // move to the next character of ']'
      i++;
      return result;
    }
  }
}


이제 더 흥미로운 문법 인 “value”로 넘어가십시오.


https://www.json.org/img/value.png 


이미지 출처 : https://www.json.org/img/value.png


값은 "공백"으로 시작한 다음 "문자열", "번호", "객체", "배열", "참", "거짓"또는 "널"중 하나로 시작하고 "공백"으로 끝납니다. :


function fakeParseJSON(str) {
  // ...
  function parseValue() {
    skipWhitespace();
    const value =
      parseString() ??
      parseNumber() ??
      parseObject() ??
      parseArray() ??
      parseKeyword('true', true) ??
      parseKeyword('false', false) ??
      parseKeyword('null', null);
    skipWhitespace();
    return value;
  }
}


?? Nullish 통합 연산자라고 합니다. || 우리는 값을 기본 값으로 사용하는 데 사용했습니다 foo || 그 외에는 || foo가 거짓 인 경우 기본 값을 반환하는 반면 nullish 통합 연산자는 foo가 null이거나 정의되지 않은 경우에만 기본 값을 반환합니다.


parseKeyword는 현재 str.slice(i)가 키워드 문자열과 일치하는지 확인하여 키워드 값을 반환합니다.


function fakeParseJSON(str) {
  // ...
  function parseKeyword(name, value) {
    if (str.slice(i, i + name.length) === name) {
      i += name.length;
      return value;
    }
  }
}

그게 parseValue입니다!


아직 문법이 3 개 더 있지만 이 기사의 길이를 저장하고 다음 CodeSandbox에서 구현할 것입니다.


https://codesandbox.io/s/json-parser-k4c3w?from-embed


모든 문법 구현을 마쳤으면 이제 json의 값을 반환합니다.이 값은 parseValue에 의해 반환 됩니다.


function fakeParseJSON(str) {
  let i = 0;
  return parseValue();

  // ...
}


글쎄, 내 친구는 그렇게 빨리가 아니라, 우리는 행복한 길을 막 끝냈습니다.


예상치 못한 입력 처리 


훌륭한 개발자는 불행한 길을 우아하게 처리해야 합니다. 파서의 경우 개발자에게 적절한 오류 메시지를 외치며 소리 지 릅니다.


가장 일반적인 두 가지 오류 사례를 처리하겠습니다.

  • Unexpected token
  • Unexpected end of string

Unexpected token 


Unexpected end of string 


모든 while 루프에서, 예를 들어 parseObject의 while 루프 :


function fakeParseJSON(str) {
  // ...
  function parseObject() {
    // ...
    while(str[i] !== '}') {


문자열 길이 이상으로 문자에 액세스하지 않도록 해야 합니다. 이 예에서 문자열 ""} "를 기다리는 동안 문자열이 예기치 않게 종료 된 경우에 발생합니다.


function fakeParseJSON(str) {
  // ...
  function parseObject() {
    // ...
    while (i < str.length && str[i] !== '}') {      // ...
    }
    checkUnexpectedEndOfInput();
    // move to the next character of '}'
    i++;

    return result;
  }
}


페이지 정보

조회 31회 ]  작성일20-01-26 20:43

웹학교