Sj.h C99でたった150行!驚きの超軽量JSONパースライブラリ
引用元:https://news.ycombinator.com/item?id=45324349
この著者の作品はANSI CかLuaの単一ファイルライブラリで、スコープが絞られてて使いやすい。ドキュメントも充実してて、フリーソフトウェアライセンスなんだって。log.c、microui、fe、microtar、cembed、ini、json.lua、lite、cmixer、uuid4とか色々あるみたいだよ。
それってオープンソースで、フリーソフトウェアじゃないよ。
ライセンスは違うこと言ってるよ。パブリックドメインより自由なものってないしね。
一般的な企業のセキュリティ部門ってパブリックドメインのソフトウェアについてどう考えてるんだろう?そういうライセンス(またはライセンスなし)のソフトウェアを受け入れるのかな?
誰が気にするの?真面目な話、君の作品を使いたい企業がライセンスを受け入れるかどうかは、リヒテンシュタインの首相がアメリカの家の外壁の色を気に入るかどうかと一緒だよ。つまり、全然気にすることじゃないってこと。
「フリーソフトウェア」より「オープンソース」の方が情報量が多い言葉だよ。全てのフリーソフトウェアがオープンソースってわけじゃないけど、全てのオープンソースソフトウェアは無料なんだ。追記:FSFの定義は知らなかったんだ。有料じゃないソフトウェアをフリーソフトウェアとして使ってたよ。
‘フリーソフトウェア’と‘オープンソースソフトウェア’は(それぞれFSFとOSIの定義によって、実際にはそう使われるんだけど)定義が重なってるよ。このプロジェクトはUnlicenseを通してパブリックドメインでリリースされてて、これはフリーソフトウェアの’ライセンス’として認められるんだ。他の多くのプロジェクトはMIT/Expatライセンスを使ってて、これもフリーソフトウェアライセンスだよ。
https://www.gnu.org/philosophy/free-sw.html
https://opensource.org/osd
それって、みんなが使えるようにオープンソースソフトウェアを公開してるかどうかによるよね。もしそうじゃないなら、なんで公開してるの?使ってほしくないならGPLが良いし、使ってほしいならMITかBSDの方がずっといいよ。
君が探してるのは”Source Available”と”Open Source”(OSI承認ライセンス付き)っていう言葉だよ。”自由な(Free as in speech)のか、無料の(Free as in beer)なのか?”が君のスローガンだね。
GPLライセンスのソフトウェアだと「エボラみたいな自由」って感じだよね。空気や太陽の光みたいな自由はどこ行っちゃったの?
C言語プロジェクトでlog.cをよく使うよ!作者がこんなに多作だとは思わなかったな。log.cは本当にオススメ。必要な機能を簡単に追加できるからね。
君は間違ってると思うよ。オープンソースとフリーソフトウェアは、どっちかがどっちかの部分集合じゃないんだ。OSIはオープンソースって認めても、FSFはフリーって認めないライセンスとか、逆もあるしね[1]。 massiveな重複はあるけど、根本的に別の定義って言うのが適切じゃないかな。
[1] https://spdx.org/licenses/
で、それがどうやってオープンソースライセンスの資格がないって言うの?俺が見る限り、定義を満たしてるように見えるんだけど。
SQLiteがパブリックドメインをライセンスに選んだことで、結構問題があったって聞いたよ。後悔してるらしい。国際的に理解が広まってない概念で、ソフトウェアの文脈での法的判例も少ないから、法務部門の懸念で採用が難しいチームもあったんだってさ。
GPLはみんなに使ってほしい時に使うもの。MITは、大企業がそれをenshittifiedなプロプライエタリソフトウェアに変えて、あなたに何も還元せずに利益を上げてもいい時に使うものだよ。
俺もUnlicenseを使ってるよ。文字通り、持てる中で一番許諾的なライセンスだからね 笑
「全てのフリーソフトウェアがオープンソースってわけじゃない」ってコメントだけど、それはどの”フリーソフトウェア”の定義を言ってるかによるね。FSFの定義では、フリーソフトウェアはオープンソースであることが必須なんだよ。
それは、守るものが何もなかったからenshittifiedになっちゃったんだよ。
例えが悪いね。もし彼らが本当に君の家の色を気にするなら、いくらでも手はあるだろう。だって、かなりの数のアメリカの大企業の税制や企業構造は、リヒテンシュタイン政府のルールに大きく依存してるんだからさ。
HOAや地方議会みたいに、良くも悪くも影響力を持つ人達がいるよね。でもリヒテンシュタイン政府にはそういうのがないって話。
反対意見として、僕はMITライセンスよりGPLやAGPLのコードを使いたいな。コピーレフトの哲学が好きだから。GPLはフリーソフトウェアであり続けたいって意思表示だし、MITは将来的にプロプライエタリ化されるリスクがある。だからGPLの方が信頼できるし、みんなのためになると思うよ。
Linux、Git、GNUシステムは反例だよね。FreeBSDは毎日衰退してるし。一般の人々と企業の法務部は違うんだよ。
Unlicenseは単なるパブリックドメインじゃないよ。PDが認められなくても「コピー、変更、公開、使用、コンパイル、販売、配布」を許可するフォールバック条項があるんだ。SQLiteは著作権を放棄するだけだから、最初の文が駄目だとあまり役立たないかもね。
「みんなに使われたくないならGPLがいい」って、それ笑っちゃうね。
オープンソースじゃないとは言ってないよ。問題はフリーライセンスかどうかだったんだ。「フリーソフトウェアじゃない」って言ってたけど、実際はそうだよ。F3nd0が言ったように、両方なんだよね。
Unlicenseの注意点だけど、一部の法域では機能しないことがあるんだ。その場合、著作権者以外は誰も使えない「文字通りライセンスなし」と見なされるかも。現実には訴えられることはないと思うけど、頭に入れておいて損はないよ。だから多くの団体はCC0やMITを推奨してるんだ。
アメリカの視点だと、セキュリティに関してMITライセンスとの実質的な違いはないよ。企業はパッケージがちゃんとメンテされててバグがなく、脆弱性データベースに既知の攻撃がないかを重視するからね。あくまで僕の経験だけど、他の会社はもっと厳しい条件があるかもね。
Stallmanの信奉者たちを召喚する呪文を唱えるのに成功したね。
追加の提案なんだけど、無料でただな「無料」を指すのには”gratis”も使えるよ。ラテン語由来で、スペイン語圏の国々では「無料」を意味するのに普通に使われるけど、自由の方の「無料」とは意味が違うんだ。
追記:FSFの定義を知らなかったんだ。僕は、お金を払わずに使えるソフトウェアをフリーソフトウェアだと思ってたよ。
それは「フリーウェア」って呼ばれるものだね。それに、オープンソースソフトウェアは有料でも良いんだ(ただし、誰かがそれを買ったら、無料で再配布することを許可しないといけないけどね)。
このライブラリ、符号付き整数オーバーフローをチェックしてない箇所があるよ:
https://github.com/rxi/sj.h/blob/eb725e0858877e86932128836c1…
https://github.com/rxi/sj.h/blob/eb725e0858877e86932128836c1…
https://github.com/rxi/sj.h/blob/eb725e0858877e86932128836c1…
特定の入力で未定義動作(UB)が起きる可能性があるね。
もっとコメントを表示(1)
肥大化したエッジケースライブラリについての良い記事があったね([0]: https://43081j.com/2025/09/bloat-of-edge-case-libraries, [1]: https://news.ycombinator.com/item?id=45319399)。
全ての可能性のあるエラーを処理しようとすると、すぐに複雑になっちゃうから、それがライブラリの責任じゃないこともあるんだよ。
あなたは、一部の開発者が好む、シンプルなシングルヘッダCライブラリ文化を知らないんだね。Tsoding(ストリーマー)なんかがその代表例だよ。彼らは、こういうものが”セキュリティ”や”機能”に焦点を当ててないことを分かってて、それで良いんだって思ってる。全てのものが、何千もの有料顧客にさらされる超真剣なビジネスプロジェクトである必要はないからね。
これには強く反対だよ。JSONは信頼できないソースから来る可能性があるし、これはセキュリティ上の影響がある問題だ。肥大化に関する記事が議論してるのは、インターフェースの契約が悪いっていう問題で、それとは種類が違うよ。
intは最近のプラットフォームなら32ビットだから、オーバーフローが起きるには、それぞれの行で、
・20億を超える深さにネストされたJSONファイル
・20億行以上のファイル
・20億文字以上の行
が必要ってことだね。
システム全体を制御していれば、JSONが必ずしも信頼できないソースから来るわけじゃないよ。システムを制御している限り、全てが絶対100%セキュアである必要はないんだ。公開システムならセキュリティに努めるべきだけど、パブリックな入力を処理しないプロジェクトでは必ずしも必要じゃない。
例えば、僕がアセンブリ言語で書いた簡易JSONパーサーは、シリアルポート経由で組み込みCPUに送られる制御メッセージを解析するものだったんだけど、カメラを回して写真を撮る小さなモーターを制御するやつでね。システムに”信頼できない”JSONが入る方法は全くなかったよ。完璧に機能したし、モーターを制御する組み込みデバイスにすごくシンプルなJSONパーサーがあっても、何も危うくなることはなかったんだ。
趣味のプロジェクトって、いつの間にか本番コードで使われ始めて、後でCVEになっちゃう傾向があるんだ。もし意図的に安全性を無視するなら、そのことについてREADMEに目立つ警告を載せるべきだよ。
じゃあ、もしそれが少人数のために設計された趣味のプロジェクトだとしたら、手抜きで彼らを危険に晒すのは急に大丈夫になるってこと?
実際のシステムだと20億文字ってのは普通にありそうだよな。
C言語でやるなら、ありとあらゆる未定義動作(UB)を徹底的にチェックするか、別の言語に乗り換えるしかないよ(後者がおすすめ)。
1つのJSONファイルで2GBは流石に異例でしょ。このヘッダを使う時の簡単な注意書きで十分だよ。『入力は2GB未満にしてください』とかね。
これは別に使う義務もないし、お金を払ったわけでもないオープンソースプロジェクトだよ。誰が危険に晒されるって言うんだ?ライセンスにも作者は損害賠償の責任を負わないって明記されてるし。
>趣味プロジェクトが実用的なら製品コードに使われる傾向があるって言うけど、それが作者の問題なわけ?ライセンスには損害賠償の責任はないってハッキリ書いてあるよ。もしそんな真剣なプロジェクトを開発してるなら、適切な審査プロセスや依存関係のサポート契約が必要だろ。
たぶん、相手側の制御はしてなかったんでしょ?そうでなければ、JSONよりもっとまともなものを使ってたんじゃない?
チェック付きインクリメントがデフォルトのプログラミング言語はほとんどないよ。RustやJavaのプログラマーだって同じ間違いをするだろうね。他の言語みたいにチェック付き加算をする関数を書くのも、そんなに難しくないし。
俺は両端を制御してたよ。JSONに変なところなんてないね。多くの用途で広く使われてる。JSONを送るシステムはNode.jsベースだったから、JSONを使うのは自然だった。それに、俺がそうしたかったからJSONを使ったんだ。どうせなら他のプロトコルを発明しなきゃいけなかっただろうし、アセンブリ言語で基本的なJSONパーサーを書くのが簡単だって思った時に、車輪の再発明をする気はなかったね(組み込みシステムで40年間アセンブリをコーディングしてきたから)。
作者の問題だとは言ってないよ。コードの問題だね。
今回の問題は、呼び出し側が制限を知らされていないから、サポート外の入力を渡すのを防ぐことも期待できないし、オーバーフローケースを後から処理する方法もないってことだね。
簡単なものならJSONよりカスタムバイナリプロトコルかASN.1を選ぶな。HLLからの生成もLLLでのパースも楽だろ(俺も数十年Asm書いてるし)。
なんでこんな言葉遊びしてんだ? 作者の問題だって言いたいのか? お金を払いたくないプロダクション\ビジネスユーザーのために、READMEに警告を追記しろとまで言うのかよ。
…で、オープンソースのソフトウェアライセンスで、作者が損害賠償の責任を負うものなんて、この世にどんなのあるんだ?
プロジェクトにライブラリを追加するとき、レビューしないのか? forgeのissueを見るか、コードを読めよ。コードが究極の仕様だ。ドキュメントと違う挙動や説明不足は信用しない。再帰やループの処理は特にチェックする。オーバーフロー処理がないなら、コードをFork\Vendorしてアサーションを追加すればいい。
「システムを制御している限り、何もかも100%安全である必要はない」ってのは、「家に入れるのが自分だけなら、家のセキュリティを心配する必要はない」って言ってるようなもんじゃないのか?
「安全性を無視するなら、READMEに警告が必要だ」って言うけど、LICENSEのこの条項はどう思う?「ソフトウェアは“現状有姿”で提供され、いかなる保証もなく、作者は損害賠償の責任を負わない」って明記されてるじゃん。
こんなライブラリにセキュリティは期待しないな。メモリセーフにしたければ、Fil-Cでコンパイルすればいいだろ。
おそらく、ライセンスに明示的な責任に関する記述が不足しているってことだろ。
同感だね。JSONが変わらないと分かってたから、たった10行のパーサーを書いたよ。JSONパーサーとは言えないけど、俺が必要とするものはちゃんとパースしてくれるんだ。
いや、違うね。俺はこれ毎日扱ってるんだ。もしライブラリに入力サイズの制限があるなら、それを明記すべきだろ。
レベル深度が20億超えるか、行数が20億超えるとUBになるぜ。JSON入力は1GBまでにしろよ。Web経由で2GBのJSONファイルを受け取ったら、スタックの他の部分でもっと問題が起きるだろうしな。もし2GB超えるファイルで動かしたいなら、ソースのintを全部64bitに変えるぜ。でも入力が2^64超えたら結局クラッシュだろ。コードでintオーバーフローのチェックなんて絶対しねぇよ。
これ、メモリ安全性とは全然関係ねぇよ。
あんたの言う『古くない』ってどういう意味だよ?今でも32ビット整数がない組み込みシステムが作られてんだぜ。
もっとコメントを表示(2)
これ、かなり寛容だよな。悪いことじゃないけど(コード見ずに使う奴のために注意書きはいるかもな)、でもだからこそこんなに小さくできるんだぜ。readmeのデモだと、{”x”,10eee”y”22:5,{[:::,,}]”w”7”h”33
って入力がrect: { 10, 22, 7, 33 }
になるんだぜ。
パーサーってのは入力が正しいと仮定してるもんだろ。バリデーションは別の問題で、このライブラリの管轄外だ。ただデータを抽出するだけのライブラリを他にどう呼べってんだよ。
個人的には、JSONパーサーライブラリ全般は苦しみのブラックホールだよ。だいたい別のユースケースを想定して書かれてるか、抽象化の複雑なごちゃまぜかのどっちか、いや両方の場合が多いな。自分の特定のユースケースに必要なものだけ書けば、そんなに難しい問題じゃないんだがな。
現代のJSONライブラリがいかに複雑かってのは驚きだぜ。かつて“超シンプル”だったnlohmannのC++単一ヘッダーJSONライブラリですら、今や13歳、5時間前にもPRがマージされてるし、1.22億ものユニットテストがあるんだ。これでもC++で最速じゃない。最速ならsimdjsonを見てみろ。だからお前ら、自分のJSONパーサーライブラリなんて始めるな。やめとけ。45分で90%は作れても、残りの10%に1万時間かかるぞ。
でもこのライブラリより『意見がない』ものって、そうそうないだろ。キーと配列要素をイテレートして、値の型を識別して、文字列スライスを返すだけなんだから。
俺にはなんか、半分しか仕事してないように感じるな。SAX“パーサー”がレキサーと大して変わらなかったのを思い出すぜ。
JSONファイルをイテレートするときに、他になにをすればいいっての?数値解析やUNICODE処理をユーザーに任せるのは、機能と見なせるよ(だって、どれだけコストをかけるか、どれだけ堅牢にするか、自分で決められるんだからね)。
データをオブジェクトに抽出することだよ。SerdeやPydanticみたいなライブラリがそれをやってくれるんだ。ていうか、オリジナルのeval() JSONロードメソッドだってそうだったじゃん。
そうしたら、ストリーミングする能力を失っちゃうじゃん。
確かに、でも普通はデータがメモリに収まらないほど大きい場合にだけ必要で、そんな場合はそもそもJSONを使うべきじゃないんだ。(一度、JSONファイルがギガバイト単位になって、SQLiteに切り替えたらすごくうまくいったって経験があるよ。)
実際、DOMスタイルのJSONパーサーは、データが利用メモリの半分より大きくなると限界が来るよ。JSONから独自のモデルオブジェクトを構築するなら、両方メモリにないとダメだからね(不要なDOM部分を段階的に破棄できるなら別だけど)。俺的には、ちゃんとしたJSONライブラリはSAXスタイルとDOMスタイルの両方を、レイヤー化して提供すべきだと思うね。
>JSONから独自のモデルオブジェクトを構築したいだろうから、ある時点で両方がメモリに存在しないといけない。そうでもないよ、JSONライブラリ自体が入力ストリームできるからね。例えばserde_json::from_reader()
を使えば、ファイル全体をメモリにロードせずにオブジェクトにパースできるんだ: https://docs.rs/serde_json/latest/serde_json/fn.from_reader。でも、それはちょっと学術的な話で、メモリの半分と全部って、同じくらいのレベルだよね。
JSONのパースは地雷原だぜ(2016)
https://seriot.ch/projects/parsing_json.html
これってJSONっていうよりC++の話なんじゃね?
うん、俺これ使ってるし、友達のほとんども使ってると思うよ :)
>一般的にJSONパーサーライブラリは苦しみのブラックホールだよ、って俺は思う。Sexprsはここで、誰かに愛されることを願って座ってるぜ。
「そんなに難しい問題じゃない」って言うやつは、実際にその問題を解決したことがないんだよ。
それはモデルオブジェクトがserdeのstructになってる場合だけだね。モデルを特定のディスクフォーマットに縛り付けたくないから、これは望ましくないんだ。
ユニットテストの統計には本当に驚いたよ。JSONパースで1億2200万ものバリエーションをカバーする必要があるなんて、どんなとんでもないエッジケースがあるんだろうね?
Common Lispのライブラリはそこが好きだよ。ほとんどアルゴリズムに特化してて、データ構造はユーザー任せ。だから、関数を呼ぶ前にデータ構造が合ってるか確認するんだ。
俺も書いたことあるけど、クラッシュレポーターで使うから、クラッシュ時に書きかけのファイルを回復できる必要があったし、エンコーダーもasync-safeじゃなきゃダメだったんだ。
https://github.com/kstenerud/KSCrash/blob/master/Sources/KSC…
うん、JSONコーデック書くのは本当に最悪だよ。
だから今、同じ機能を持つasync-safeでクラッシュ耐性もあって、35倍速くてコードも少ないBONJSONコーデックに置き換えてるところ。
https://github.com/kstenerud/ksbonjson/blob/main/library/src…
https://github.com/kstenerud/ksbonjson/blob/main/library/src…
ほとんどの人は残りの10%の機能なんて必要ないけど、小さくて保守しやすいコードベースを重視するんだよ(nlohmannは絶対そうじゃないけどね)。
パフォーマンスがそこまで重要じゃないなら、問題の多くは消えるよ。そうすれば、もっと良くて安全でシンプルな言語で、正しいパーサーを書く選択肢が広がるからね。C/C++でもいける。
速度最適化をすればするほど、新しいエッジケースがどんどん厄介になるんだ。
1億2200万個のユニットテスト?何それ?