8 ポイント 投稿者 GN⁺ 2025-09-22 | 1件のコメント | WhatsAppで共有
  • 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 ファイルを参照

その他

  • コードとフォルダ構成がシンプルで、別途設定やビルド手順なしにすぐ使える
  • 独立しており、外部依存がなく、主に 軽量性使いやすさ が求められる環境に適している

1件のコメント

 
GN⁺ 2025-09-22
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 億を超えるか、2 番目のケースでは行数が 20 億を超えると UB が発生する可能性があります。JS 入力を 1GB に制限するなら、それ以上になった場合はスタックの他の部分でももっと大きな問題が起きるでしょう。もし 2GB 超の JSON を処理する必要があるなら、ソースコード中の int をすべて 64 ビットに置き換えます。それでも入力が 2^64 を超えれば結局は落ちます。int オーバーフローをコード内で一つひとつチェックするつもりはありません
    • int は 32 ビットなので、各行について 20 億階層のネスト値、20 億行を超えるファイル、あるいは 20 億文字を超える 1 行が必要な場合にしか問題になりません
    • このライブラリは本番環境では使えなさそうです
  • このライブラリはかなり寛容です。こういうやり方が悪いとまでは言いませんが、コードを見ずに使う人は注意が必要です。これがコードがここまで小さい主な理由でもあります。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% のために 1 万人分の労力で何千時間も必要になります
    • Parsing JSON is a Minefield (2016) という有名な資料があります https://seriot.ch/projects/parsing_json.html
    • このライブラリほど中立的な設計も珍しい気がします。キーと配列アイテムをただ反復して返し、string-slice で型を区別して渡してくるだけです
    • このプロジェクトは zero-allocation と最小限の状態保持を売りにしていますが、strings のような最もよく使われる型では結局アロケーションが必要になります。自分の問題とはかなり異なる代替案です
    • S-Expressions(S式)が今でも愛され続けてほしいと思っています
  • 興味深いですが、このライブラリが conformance test をどれくらい通るのか気になります https://github.com/nst/JSONTestSuite
    • 検証という観点では、このライブラリはそれほど厳密ではないようです。たとえば ]} のどちらでオブジェクトや配列を閉じても受け入れますし、\v も whitespace として認識するので標準より寛容です。「正しい JSON のためのデータ抽出器」くらいに考えるのが適切でしょう。ただし string や number のパーサを自前で書くのが面倒なことはあります。特に、生成側と JSON 文法のサブセットについて合意が必要な場合はなおさらです
    • 実装ベースで conformance test を作るのは本当に有用そうです。特定のパースライブラリの挙動と正確に一致するサブセット/スーパーセットとして test-set を作るのです。そうすれば、2 つのパーサが同じ JSON を異なる解釈をして生じる脆弱性(たとえば認証バイパス)のリスクを避けられます
    • 純粋な質問ですが、ネストしたオブジェクトはサポートしていますか?
  • 少し中途半端なパーサのように見えます。数値を float や int に変換しないので、そのあたりは自分で追加実装する必要がありそうです。それでも印象的なので使ってみたい気はします。ただ、そういう点を指摘したかっただけです
  • C99 がこの構造体をデフォルトで 0 初期化すると明記しているのか、それとも = { 0 } が抜けているのか気になります。該当コードを見ると、r->depth が初期化されないまま sj__discard_until ループの中で depth と偶然一致する可能性がありそうです
    • r->depth はここですでに 0 に初期化されています。該当コード を参照してください
  • こうしたライブラリの用途が気になります。すでに優れた JSON ライブラリがたくさんあるように見えますが、教育用ツールなのでしょうか?
    • 既存コードベースに統合しやすく、サイズの負担が小さく、ヒープアロケーションを使わず、stdlib も使いません(型定義のために stdbool.hstddef.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 に近く見えます