2P by GN⁺ 23시간전 | ★ favorite | 댓글 1개
  • Sj.h는 약 150줄로 이루어진 초경량 JSON 파서로, C99 기반 환경에서 사용 가능함
  • 이 라이브러리는 제로 메모리 할당, 최소 상태 유지 등의 특징을 가지고, 가벼운 임베디드 환경이나 의존성 최소화에 용이함
  • 숫자 및 문자열 파싱에 대해 직접 처리하는 방식을 채택하여, 사용자가 관련 처리를 자유롭게 구현 가능함
  • 라인:컬럼 기반의 에러 메시지 지원으로 디버깅 시 가독성 및 위치 식별성이 높음
  • 공개 도메인 소프트웨어로 누구나 자유롭게 사용 및 변경, 배포 등이 허용됨

프로젝트 소개

  • Sj.h는 과도한 기능이나 불필요한 메모리 할당 없이 최소한의 코드만으로 JSON 파싱 기능을 제공하는 C99 헤더 파일임
  • 전체 구현은 약 150줄로, 임베디드 시스템, 도구, 또는 외부 라이브러리 의존성을 줄이고 싶은 경우 적합함

주요 특징

  • 제로 메모리 할당(zero-allocations)최소 상태(minimal state) 로 작동해 메모리 부담이 매우 적음
  • 라인:컬럼 정보가 포함된 에러 메시지로, 파싱 도중 발생하는 문제의 위치 파악이 용이함
  • 숫자 파싱은 기본 제공하지 않으며, strtod, atoi 등 사용자가 원하는 방식을 사용할 수 있음
  • 문자열 파싱도 직접 구현하거나, Unicode surrogate pair 처리 등을 필요에 따라 구축 가능함
  • 모든 소스 코드는 공개 도메인(Unlicense)으로 제공되어, 라이선스 등의 제약 없이 사용·변경 가능함

사용 예시

  • JSON 문자열을 Rect 구조체로 파싱하는 간단한 예시를 제공함
char *json_text = "{ \"x\": 10, \"y\": 20, \"w\": 30, \"h\": 40 }";
typedef struct { int x, y, w, h; } Rect;
...
  • sj_Reader, sj_Value, sj_reader, sj_read, sj_iter_object 등 간단 명료한 API를 사용
  • 숫자 값 파싱이나 키-값 비교는 직접 구현(builtin 제공하지 않음)
  • demo 폴더에서 다양한 추가 사용 예시 확인 가능함

라이선스

  • Sj.h는 누구나 제약 없이 사용할 수 있는 공개 도메인 소프트웨어
  • 자세한 내용은 LICENSE 파일 참고

기타

  • Github 상에서 643 stars, 9 forks 기록
  • 코드와 폴더 구조가 간단하여, 별도 구성이나 빌드 절차 없이 바로 사용할 수 있음
  • 독립적이고, 외부 종속성이 없으며, 주로 경량성사용 편의성이 요구되는 환경에 적합함
Hacker News 의견
  • 내가 이 저자의 작품을 좋아하는 이유는 대부분 ANSI C나 Lua로 작성된 단일 파일 라이브러리임, 범위에 집중하고 사용하기 쉬운 인터페이스와 훌륭한 문서를 제공함, 무료 소프트웨어 라이선스도 마음에 듦, 이 프로젝트 외에도 내가 좋아하는 작품 몇 가지가 있음: log.c(심플한 C99 로깅 라이브러리), microui(아주 작은 즉시형 UI 라이브러리), fe(임베드할 수 있는 작은 언어), microtar(경량의 tar 라이브러리), cembed(파일을 C 헤더에 임베드하는 도구), ini(.ini 설정 파일용 경량 라이브러리), json.lua(Lua용 경량 JSON 라이브러리), lite(Lua로 만든 경량 텍스트 에디터), cmixer(게임용 이식성 오디오 믹서), uuid4(C로 구현한 작은 uuid4 라이브러리)임
    • 나는 C 프로젝트에 log.c를 매번 가져와서 씀, 저자가 이렇게 여러 라이브러리를 만든 줄 몰랐음, log.c는 진짜 다루기 쉬워서 추천할 수 있음
    • LOVE2D로 게임 만들 때 Lume 라이브러리를 썼었음, 실제로 IRC 채팅방에서 저자를 몇 번 만나봤고, 한 번은 저자의 아이디어에 나쁘다고 말한 적 있음, 근데 다시 보니까 좋은 아이디어였음, https://github.com/rxi/lume 참고할 만함
    • 오픈 소스기는 한데, 진정한 free software는 아님
  • 이 라이브러리는 아래 라인들에서 signed integer overflow 체크를 수행하지 않음: L89, L149, L150 특정 입력값에 따라 undefined behavior가 일어날 수 있음
    • 일부 개발자들은 단순한 단일 헤더 C 라이브러리 문화를 즐김, Tsoding 같은 스트리머가 대표적인 예임, 이런 라이브러리들은 보안이나 기능성에 집중하지 않는다는 점을 인지함, 모든 프로젝트가 수많은 고객에게 노출되는 비즈니스 급 소프트웨어는 아니기 때문에 이런 방식도 괜찮음
    • edge case를 모두 처리하는 라이브러리의 비대함에 대해 다룬 좋은 이 있고, 관련 토론도 있음, 모든 에러를 처리하려고 하면 복잡성이 급격하게 증가함, 때로는 라이브러리의 책임이 아닐 때도 있음
    • 레벨 깊이가 20억 이상이거나, 두 번째 케이스에선 라인 수가 20억이 넘으면 UB가 발생할 수 있음, JS 입력을 1GB로 제한하면 그 이상 들어올 경우 스택의 다른 부분에서도 더 큰 문제가 생김, 만약 2GB 초과 JSON을 처리해야 한다면 소스 코드의 int를 64비트로 모두 바꾸겠음, 그래도 입력이 2^64 초과면 결국 죽음, int 오버플로우를 코드에서 일일이 체크하지는 않을 생각임
    • int가 32비트이므로 각 라인에 대해 20억 깊이의 중첩값, 20억 줄 이상의 파일, 혹은 20억 글자 이상의 한 줄이 필요한 경우에야 문제가 됨
    • 이 라이브러리는 프로덕션에서는 못 쓸 것 같음
  • 이 라이브러리는 상당히 관대한 편임, 이런 방식이 나쁘다고는 못하겠지만, 코드를 보지 않고 사용할 사람들에게는 주의가 필요함, 이것이 코드가 이렇게 작은 주된 이유임, README에 나온 데모 예제로 보면<br><code>{"x",10eee"y"22:5,{[:::,,}]"w"7"h"33<br>rect: { 10, 22, 7, 33 }</code> 처럼 동작함
    • 파서는 일반적으로 입력값이 유효하다고 가정함, 검증 작업은 이 라이브러리가 커버하지 않는 전혀 별도의 문제임, 결국 이 라이브러리는 데이터를 추출해주는 역할임
    • 그래서 잘못된 거임?
  • JSON 파서 라이브러리는 전반적으로 고통의 블랙홀이라고 생각함, 각각 적용하려는 곳이 다르고, 복잡한 추상화로 가득 차 있음, 둘 다 해당하는 경우도 많음, 사실 특정 목적에 딱 맞게 필요한 부분만 구현하면 그리 어려운 문제도 아님
    • 모던 JSON 라이브러리가 얼마나 복잡해지는지 놀라움, 예전에 정말 심플했던 nlohmann의 C++ 단일 헤더 JSON 라이브러리는<br>* 13년이나 됐고<br>* 지금도 pull request가 활발하게 머지되고<br>* 1억 2,200만 개의 단위 테스트가 있음<br>이런데도 본인들도 가장 빠르진 않다고 인정함, 진짜 속도가 필요하면 simdjson을 추천함, 직접 JSON 파서 라이브러리 만들 생각은 하지 않는 게 좋음, 90%까지는 45분이면 짤 수 있는데 나머지 10%를 위해서 만 명의 인력이 수천 시간 들여야 함
    • Parsing JSON is a Minefield (2016)이라는 유명한 자료가 있음 https://seriot.ch/projects/parsing_json.html
    • 이 라이브러리만큼 중립적인 설계도 드문 것 같음, 그냥 키와 배열 아이템을 반복해서 반환하고 string-slice로 타입 구분해서 돌려줌
    • 이 프로젝트는 zero-allocation, 최소 상태 저장을 자랑함, 하지만 strings처럼 가장 흔히 쓰는 타입엔 결국 할당이 필요함, 본인 문제와는 굉장히 다른 대안임
    • S-Expressions(셉식스)는 아직도 사랑받길 기대함
  • 흥미롭긴 한데, 이 라이브러리가 conformance test를 얼마나 통과하는지 궁금함 https://github.com/nst/JSONTestSuite
    • 검증 측면에서 이 라이브러리는 그리 엄격하지 않은 듯함, 예를 들어 ']'나 '}'로 객체나 배열을 닫는 걸 다 받아줌, '\v'도 whitespace로 인정해서 표준보다 관대함, "올바른 JSON을 위한 데이터 추출기" 정도로 보는 게 맞음, 하지만 직접 string이나 number 파서를 짜는 게 귀찮을 수 있음, 특히 생산자와 JSON 문법 서브셋에 대해 동의가 필요한 경우 번거로움
    • 실제 구현체 기반으로 conformance test를 만드는 게 정말 유용할 것 같음, 특정 파싱 라이브러리의 동작과 정확히 일치하는 서브셋/슈퍼셋으로 test-set을 만드는 것임, 그래야 두 개의 파서가 같은 JSON을 다르게 해석해서 생기는 취약점(예: 인증 우회)에 대한 위험을 피할 수 있음
    • 실제 질문임, 중첩 오브젝트는 지원하는지 궁금함
  • 약간 반쪽짜리 파서처럼 보임, 숫자를 float이나 int로 변환하지 않아서 직접 그런 부분을 추가로 만들어야 할 것 같음, 그래도 인상적이라서 쓸 의향 있음, 그냥 그런 점을 짚어본 것임
  • C99가 이 구조체를 기본적으로 0으로 초기화한다고 명시하는지, 아니면 = { 0 }이 빠진 건지 궁금함, 관련 코드를 보면 r->depth가 초기화 안 된 채 sj__discard_until 루프에서 depth와 우연히 같아질 수도 있겠음
    • r->depth는 여기서 이미 0으로 초기화되어 있음, 관련 코드 참고
  • 이런 라이브러리의 사용 목적이 뭔지 궁금함, 이미 훌륭한 JSON 라이브러리가 많아 보이는데, 교육용 도구임?
    • 기존 코드베이스에 통합하기 쉬움, 크기 부담이 적고 힙 할당을 안 쓰고, stdlib도 안 씀(타입 정의를 위한 stdbool.h와 stddef.h만 포함), C++ 템플릿 장난도 없고 API가 직관적임, 이런 요건을 모두 만족하는 C 라이브러리는 진짜 드묾, C++는 더 드묾
    • 오버헤드와 할당 없이 파싱하는 게 매력적임, 대용량 json dump(예: Wikidata dumps)에서 특정 속성만 추출하고 싶을 때 유용함
    • 임베디드 cpu에서 바로 쓸 수 있음, 심지어 이제 vape 같은 데서 api 서버도 돌릴 수 있을 듯함
    • 코드가 작으니 보안이 엄격한 프로젝트에도 검토가 쉬움, 라이선스 컴플라이언스도 매우 간단함(알림 문구 불필요)
    • 초보자 참고용이거나 간단한 파싱을 하려는 사람에겐 좋은 코드베이스가 될 수 있음, 프로세서가 제한된 소규모 취미 프로젝트에서 작은 코드 풋프린트가 필요할 때 쓸 만함, 그래도 그런 경우라면 아마 TOML 같은 파일 포맷을 선호하겠음
  • 멋짐! 다음에 C로 JSON 파서가 필요하면 살펴볼 생각임, 나는 수년간 cJSON(https://github.com/DaveGamble/cJSON)을 잘 써왔음, 그 전에 wjelement(https://github.com/netmail-open/wjelement)를 썼었지만, 몇 가지 문제로 결국 바꿈(구체적인 이유는 오래돼서 기억이 안 남)
  • 이걸 파서(parser)라고 부르기엔 좀 억지로 느껴짐, 일반적으로 lexer에 가까워 보임