世界を動かす技術を、日本語で。

Sj.h: 約150行のC99で書かれた小さなJSONパースライブラリ

2025年9月22日原文(github.com)

概要

sj.h は、C99で書かれた約150行の 超小型JSONパーサーゼロアロケーション 設計で、状態管理も最小限。 エラー時は 行:列 情報付きのメッセージを出力。 数値や文字列のパースは ライブラリ外部で処理 する方針。 パブリックドメイン として公開され、商用利用も自由。

sj.h 超小型JSONパーサーの特徴

  • C99準拠 かつ約150行のシンプル実装
  • 動的メモリアロケーション非依存 設計
  • 最小限の内部状態管理 による高速動作
  • エラー発生時は行番号・列番号を含むメッセージ を返却
  • 数値パース機能非搭載
    • 利用者が strtodatoi などで独自処理
  • 文字列パース機能非搭載
    • Unicodeサロゲートペア などのハンドリングは利用者側で実装
  • パブリックドメインライセンス
    • 商用・非商用問わず自由に利用可能

基本的な使い方

  • JSON文字列から構造体への変換例
    • JSON例: { "x": 10, "y": 20, "w": 30, "h": 40 }
    • 変換先構造体: Rect(int型のx, y, w, hフィールドを持つ)
char *json_text = "{ \"x\": 10, \"y\": 20, \"w\": 30, \"h\": 40 }";
typedef struct { int x, y, w, h; } Rect;

bool eq(sj_Value val, char *s) {
  size_t len = val.end - val.start;
  return strlen(s) == len && !memcmp(s, val.start, len);
}

int main(void) {
  Rect rect = {0};
  sj_Reader r = sj_reader(json_text, strlen(json_text));
  sj_Value obj = sj_read(&r);
  sj_Value key, val;
  while (sj_iter_object(&r, obj, &key, &val)) {
    if (eq(key, "x")) { rect.x = atoi(val.start); }
    if (eq(key, "y")) { rect.y = atoi(val.start); }
    if (eq(key, "w")) { rect.w = atoi(val.start); }
    if (eq(key, "h")) { rect.h = atoi(val.start); }
  }
  printf("rect: { %d, %d, %d, %d }\n", rect.x, rect.y, rect.w, rect.h);
  return 0;
}
  • オブジェクトイテレーション
    • sj_iter_object でkey-valueペアを繰り返し取得
  • 値の比較
    • eq関数 でJSONのキー名と比較
  • 値の変換
    • atoi など標準関数で数値へ変換

注意点・制約

  • 数値パースは外部関数任せ
    • 浮動小数点や整数のフォーマットエラーは 自己責任
  • 文字列エスケープやUnicode処理は未対応
    • 必要に応じて 独自実装 が必要
  • JSON仕様の一部(例:特殊なUnicodeやエスケープ文字)には非対応
  • エラーメッセージ行:列 情報付きで分かりやすい
  • スレッドセーフ設計ではない ため、必要に応じて保護処理を実装

ライセンス・利用条件

  • パブリックドメイン
    • 商用・非商用問わず制限なし
    • クレジット表記不要
  • 詳細はLICENSEファイル 参照

関連資料

  • demoフォルダ に追加サンプルコード
  • 公式リポジトリ で最新情報・アップデートを確認

sj.h は、 組み込み用途最小限のJSONパースが必要なCプロジェクト に最適な選択肢。 自己責任での拡張カスタマイズ が前提のため、柔軟な運用が可能。

Hackerたちの意見

これは面白いけど、適合テストはどうなの? https://github.com/nst/JSONTestSuite

ほんとに聞きたいんだけど、ネストされたオブジェクトは扱えるの?

実際の特定の実装に基づいた適合テストがあったらめっちゃ役立つと思うんだ。具体的には、特定のターゲットパーサーライブラリのパース動作に完全に一致するサブセット(またはスーパーセット?)があればいいなって。これがなぜ役立つかっていうと、同じJSONが異なるパーサーによって異なって扱われることに依存する脆弱性を避けるためなんだ(例えば、これを利用して認可レイヤーを回避できる)。

検証があまりないみたいで、例えばオブジェクトや配列を終了させるのに ']' や '}' を無差別に使わせるんだよね。それに、'\v' をホワイトスペースとして許可する点では、RFCやjson.orgのJSONよりも寛容だと思う。もっと「正しいJSONからデータを抽出するツール」として扱った方がいいかも。でもそれでも、自分で文字列や数値のパーサーを作るのは面倒になるかもしれない。プロデューサーがJSON構文のサブセットに合意しない限りはね。

こういうのって何に使うの? JSON用の優れたライブラリはたくさんあるし。これは教育用のツールなの?

ゼロアロケーションで最小限の状態

コードが小さいとレビューしやすいから、厳しいセキュリティ要件のあるプロジェクトには向いてるかもね。それに、ライセンスの遵守も簡単(通知不要だし)。

これは特定の用途に合わせて改造するためのミニマルなライブラリを意図してるんじゃないかな。

埋め込みCPUは簡単だね。今や、ベイプの上でAPIサーバーを動かせるかも。

既存のコードベースに統合するのは超簡単だし、サイズのオーバーヘッドも最小限。ヒープの割り当てもないし、標準ライブラリも使ってない(型定義のためにstdbool.hとstddef.hだけ含まれてる)。C++のテンプレートのごちゃごちゃもないし、APIもすごくシンプルでわかりやすい。これらの条件を満たすCライブラリは実際にはかなり珍しいし、C++ライブラリはもっと珍しいよね。

メモリを割り当てない小さな単一ファイルの純粋なC依存関係は、うまく動けば共通の問題に対する普遍的な解決策になり得る。

オーバーヘッドが少なく、割り当てもなしでパースできるのは結構面白いよね。例えば、大量のJSONダンプを処理して特定のプロパティを抽出する時(Wikidataのダンプが思い浮かぶ)。

Hacker Newsで議論の続きを見る