직렬화 »

JSON

작성, 마지막 큰 수정

에 해커뉴스에 올렸던 글을 바탕으로 초벌 작성.

에 미작성이었던 표준이 왜 이따위인가?대안 섹션을 마저 작성. 에 “소숫점이 없는 정수에 범위 제한이 있는가?” 항목이 왜 문제가 되는지 구체적으로 부연. 에 여는 예제를 추가함. 에 인명을 한글 발음으로 대체.

JSON자바스크립트의 (거의) 부분집합으로 시작해서 현재는 언어 불문하고 아주 널리 쓰이는 직렬화 포맷이다. 사실상 모든 프로그래밍 언어에 JSON 라이브러리가 있다고 보아도 좋다. JSON보다 30년 정도 이전에 나온 Apple II에서 돌아가는 JSON 라이브러리도 있을 정도이다.

{
  "": null,
  "excluded middle": [true, false],
  "pi": {
    "integer": 3,
    "decimal": 3.141592,
    "von Neumann ordinal": [{}, [{}], [{}, [{}]]]
  },
  "https://tvtropes.org/pmwiki/pmwiki.php/Main/DyingClue":
    "🍖고기 먹고 싶다\r\n\ud83d\uDC1F앗 물고기잖아?!"
}

JSON에서 가능한 모든 종류의 값(NULL, 불린형, 정수, 소수, 문자열, 배열, 오브젝트)을 사용하는 예제.

[주장:] JSON은 쓰레기다. JSON은 대강 만들어진 표준이 얼마나 심각한 문제를 야기할 수 있는지 보여 주는 대표적인 예제이다. 이걸 만든 사람은 더글라스 크록포드(Douglas Crockford)라는 사람인데, 당시 자바스크립트를 빡세게 사용하던 야후에서 근무한 덕분에 자바스크립트의 초기 발달에 상당한 영향을 미쳤다. 허나 그가 스스로 말하듯, 그는 표준화에 관심이 있다기보다는 자바스크립트를 유용한 언어로 만드는 데에 더 관심이 있었고, “이 소프트웨어는 악을 행하는 용도로 쓸 수 없다” 같은 드립이나 치면서 오픈 소스 소프트웨어 라이선싱을 꼬아 버리는 트롤링도 벌인 적이 있다. 돌이켜 보면 이 사람이 관여한 표준이 괜찮을 거라는 믿음은 허황된 것이었다.

효율적이지 않다 #

JSON은 자바스크립트의 부분집합을 표방했기 때문에 근본적으로 인간이 읽을 수 있는 포맷이다. 허나 일반적으로 직렬화 포맷은 성능을 중시하고 인간이 읽을 수 있는 건 따로 설계하게 마련이다. (그렇다고 인간이 쓰기 편한 포맷이냐 하면 그건 또 아니다.) JSON은 상당히 비효율적인 포맷이며 설계에 성능이 전혀 고려되지 않았다.

이런 비효율성에도 불구하고 JSON이 살아남은 것은 거의 순전히 XML이라는 훨씬 더 비효율적인 안티테제가 있었고, 덤으로 자바스크립트에서 바로 쓸 수 있다는 외부적 요인 때문이었다고 봐야 할 것이다.

반론: 요즘 JSON 구현은 효율적이지 않은가? #

과거에는 JSON이 비효율적이었으나 요즘의 RapidJSON이나 simdjson 같은 구현들은 효율적이라는 반론이 있을 수 있다. 이는 일정 부분 사실이나, 공정한 비교가 아니기도 하다.

앞선 구현들의 공통점은 JSON의 구조 분석과 트리 생성이 나뉘어 있다는 것이다. [1, 2, 3]을 예로 들면, 구조 분석이란 이게 3개의 숫자가 오프셋 1, 4, 7에 위치한 배열이라는 것을 파악하는 것이며, 트리 생성은 이로부터 내가 사용하는 언어의 배열 타입을 실제로 얻어 내는 것이다. 그리고 이들 구현이 자랑하는 무지막지한 성능은 구조 분석 성능에 국한된다.

트리 생성 속도는 전통적으로 파싱 속도가 아니라 메모리 할당 속도에 제한을 받는데, 일개 라이브러리가 이 제한을 피하려면 메모리를 덜 할당하고 일부 처리를 최대한 지연시키는 자체 자료 구조를 만들어야 한다. simdjson의 예를 들면 ondemand 인터페이스가 이런 역할을 한다. 배열 안의 숫자 합을 구하는 식으로 한 번 읽고 버리는 용도라면 자체 자료 구조를 써도 큰 문제는 없겠지만, JSON 문서의 일부가 아닌 전체를 읽어 들여야 하거나 읽어 들인 값을 변경해야 하는 상황이라면 자체 자료 구조는 큰 도움이 되지 못한다.

요즘 JSON이 빠르다는 주장은 이 점을 이해하지 못해서 나온 “착시”이다. 구조 분석이 필요한 JSON과는 달리, 대부분의 이진 직렬화 포맷은 구조 분석이라는 것 자체가 필요하지 않다. 하지만 JSON은 너무 널리 쓰여서 트리 생성이 분리된 성능 중시 라이브러리를 찾기가 더 쉬운 것 뿐이다.

쓰기도 귀찮다 #

성능은 뭐 그렇다고 치고, 사람이 읽고 쓸 수 있는 포맷이라는 것 자체에 가치를 두는 사람도 있다. 그러나 JSON은 옛 자바스크립트(ECMAScript 3) 시절의 문법을 고수하고 있는데다, 자바스크립트에는 없던 자체적인 제한도 있어서 별로 인간이 직접 쓸만한 포맷이 되지 못한다.

끔찍한 점은 이에도 불구하고 어쨌든 사람이 읽을 수 있다는 이유로 설정 포맷으로 잘못 쓰이는 경우가 흔하다는 것이다. 그리고 JSON을 설정 포맷으로 쓸 경우 백이면 백 자체적인 확장으로 위 문제를 해결하는 경우가 많다. 표준화? 당연히 엄청나게 많은 시도가 있었지만 어느 하나 통일된 제안이 나오진 못했다.

데이터 모델이 이상하다 #

데이터 모델은 직렬화 포맷을 해석해서 나오는 논리적인 자료형이다. 예를 들어 포맷에 “숫자”가 있다면 그 숫자의 허용 범위 같은 것이 자료형의 속성이 될 것이다. “유니코드 문자열”과 “이진 바이트열”의 구분이 있는지 여부도 데이터 모델의 소관이 되겠다.

옛날 글
This is true, but due to its origin I think we have a weak agreement over the JSON data model: it is a JavaScript value unless ambiguous. We do expect dotted number literals represent IEEE 754 binary64 numbers (importantly, this is pretty much the only case that excess precision do not alter the value), undotted number literals of the range (-231, 231) (exclusive) represent 32-bit integers, string literals with no lone surrogates and no unescaped U+2028/U+2029 represent Unicode strings, objects with no duplicate keys represent a partial mapping from strings to values.
해커뉴스에 쓴 글

JSON의 데이터 모델은 제대로 정의되어 있지 않다. 고작해야 “모호하지 않으면 자바스크립트와 같은 의미론” 정도의 말을 할 수 있을 뿐이다. 이는 RFC 8259 시점에서도 그다지 발전이 없으며, 구현체들이 이런 값에 아마도 이렇게 반응할 것이다, 정도의 추측만을 늘어 놓고 있는 실정이다.

표준이 왜 이따위인가? #

본래 JSON은 크록포드의 웹사이트에 대강 정의했던 것을 2006년에 RFC 4627로 “표준화”한 것이 시초이다. 이 표준은 놀랄 정도로 내용이 없는 걸로 유명한데, 가장 강력한 문제는 JSON 파서가 JSON 텍스트(오브젝트나 배열)를 처리해서 내 놓는 출력에 대한 어떤 지침도 없다는 것이다. 이를테면 이 RFC에 따르면 이론적으로 JSON 파서는 올바른 JSON을 인식하기만 한 뒤 항상 빈 오브젝트를 반환해도 무방하다. 실제로 그런 구현체가 존재하는가? 아마 아닐 것이다. 그러나 지금껏 설명한 JSON의 모호한 구석에 대해 표준이 아무 부연도 하지 않는 건 상식을 벗어난다.

JSON의 인기에 힘입어 2009년에 발표된 ECMAScript 5에는 JSON 오브젝트가 추가되었다. 이 API는 크록포드의 JSON2.js 라이브러리에 기반한 것으로, 원래는 문법 정의를 RFC 4627에 맡기고 있었으나 2008-11-03 초안을 기점으로 ECMAScript 표준에도 JSON 문법이 추가되었다. RFC와 비교하면 이 표준은 상당한 발전이 있었지만, 동시에 JSON에 내재된 문제가 표면화된 첫 표준이기도 하다.

이러한 이유로 한동안 JSON의 정의는 두 개가 있었다. 이후 2013년에 발표된 ECMA-404는 순전히 RFC 4627을 ECMAScript 명세를 반영하도록 살짝 고친 것에 불과했다. 이런 꼬라지다 보니 RFC의 갱신이 필요했는데, 크록포드가 표준화 과정에서 나가 떨어져 나간 뒤에 팀 브레이(Tim Bray)가 그 자리를 이어 받아, 2014년에 갱신된 RFC 7159가 발표되기에 이른다. 2021년 현재의 최신판 RFC 8259은 몇 가지 사소한 수정을 빼고는 7159와 같다.

RFC 7159는 크록포드의 RFC보다는 상태가 많이 낫지만 여전히 이런 저런 문제가 남아 있다.

대안 #

미안한 얘기지만, 현대 프로그래밍에서 JSON을 완전히 피할 방법은 없다. JSON의 문제와는 무관하게 JSON은 널리 쓰이는 포맷이기 때문에 좋든 싫든 써야 할 때가 올 것이다. 뭐 다음 상황을 만족한다면 JSON을 쓴다고 해도 큰 문제는 일어나지 않을 것이다.

물론, 만약 피할 수 있다고 판단되면 JSON을 피하는 것이 적절한 선택일 것이다. 직렬화 포맷 문서를 참고하자.