一般
RapidJSONとは何ですか。
RapidJSONは、JSONを解析・生成するC++ライブラリーです。すべての機能を確認できます。
RapidJSONという名前の由来は何ですか。
高速なXML DOMパーサーであるRapidXMLに着想を得ています。
RapidJSONはRapidXMLに似ていますか。
RapidJSONは、in situ解析やヘッダーだけのライブラリーなど、RapidXMLの設計の一部を取り入れています。ただし、両者のAPIはまったく異なります。また、RapidJSONには、RapidXMLにない多くの機能があります。
RapidJSONは無料ですか。
はい。MITライセンスの下で無料で利用でき、商用アプリケーションにも使えます。詳しくはlicense.txtを確認してください。
RapidJSONは小さいですか。何に依存しますか。
はい。JSONを解析し、統計を表示する単純な実行ファイルは、Windowsでは30KB未満です。
RapidJSONは、C++標準ライブラリーだけに依存します。
RapidJSONをインストールするにはどうしますか。
インストールの節を確認してください。
自分のプラットフォームでRapidJSONは動きますか。
コミュニティーは、OS・コンパイラー・CPUアーキテクチャーの多くの組み合わせでRapidJSONをテストしています。ただし、個々のプラットフォームで動くとは保証できません。単体テストスイートをビルドして実行すれば、確認できます。
RapidJSONはC++03やC++11に対応しますか。
RapidJSONは、最初にC++03向けに実装されました。その後、一部のC++11機能(ムーブコンストラクターやnoexceptなど)を任意で使えるようにしました。C++03またはC++11に準拠するコンパイラーと互換性があるはずです。
RapidJSONは実際のアプリケーションで使われていますか。
はい。クライアントとサーバーの実際のアプリケーションに導入されています。コミュニティーメンバーからは、自分たちのシステムで毎日5000万件のJSONを解析しているとの報告があります。
RapidJSONはどのようにテストされていますか。
RapidJSONには、自動テスト用の単体テストスイートがあります。すべての変更について、Travis(Linux)とAppVeyor(Windows)がコンパイルとテストの実行を行います。また、テストではLinux上のValgrindでメモリーリークを検出します。
RapidJSONの文書は充実していますか。
RapidJSONは、利用ガイドとAPI文書を提供しています。
代わりのライブラリーはありますか。
はい。多くの選択肢があります。たとえば、nativejson-benchmarkには、オープンソースのC/C++ JSONライブラリーの一覧があります。json.orgにも一覧があります。
JSON
JSONとは何ですか。
JSON(JavaScript Object Notation)は、人が読めるテキストを使う、軽量なデータ交換形式です。詳しくは、RFC7159とECMA-404を参照してください。
JSONは何に使われますか。
JSONは、Webアプリケーションで構造化データを転送するためによく使われます。データを永続化するファイル形式としても使われます。
RapidJSONはJSON標準に準拠しますか。
はい。RFC7159とECMA-404に完全に準拠します。JSON文字列内のヌル文字やサロゲートペアなど、特殊な場合も扱えます。
RapidJSONは緩やかな構文に対応しますか。
現時点では対応しません。厳密な標準形式だけをサポートします。関連する構文への対応は、この課題で議論されています。
DOMとSAX
DOM形式のAPIとは何ですか。
Document Object Model(DOM)は、問い合わせや操作を行うためにJSONをメモリー内に表現したものです。
SAX形式のAPIとは何ですか。
SAXは、解析と生成のためのイベント駆動APIです。
DOMとSAXのどちらを選べばよいですか。
DOMでは、問い合わせや操作を簡単に行えます。SAXは非常に高速でメモリーを節約できますが、利用するのが難しい場合が多くあります。
in situ解析とは何ですか。
in situ解析は、入力JSONの内部で直接JSON文字列をデコードします。メモリー使用量を減らし、性能を改善する最適化ですが、入力JSONを変更します。詳しくは、in-situ解析を確認してください。
解析時にエラーが発生するのはどのような場合ですか。
入力JSONの構文が不正な場合、値を表現できない場合(数値が大きすぎる場合)、またはパーサーのハンドラーが解析を終了した場合に、エラーが発生します。詳しくは、解析エラーを確認してください。
どのようなエラー情報が得られますか。
エラーはParseResultに保存され、エラーコードとオフセット(JSONの先頭からの文字数)を含みます。エラーコードは、人が読めるエラーメッセージへ変換できます。
JSONの数値をdoubleだけで表さないのはなぜですか。
アプリケーションによっては、64ビットの符号なし・符号付き整数を使います。これらは、精度を失わずにdoubleへ変換することができません。そのため、パーサーはJSONの数値を、各種整数型やdoubleへ変換できるか検出します。
ドキュメントや値を消去し、メモリー使用量を最小化するにはどうしますか。
SetXXX()メソッドのいずれかを呼びます。これらは、DOMデータを解放するデストラクターを呼びます。
Document d;...d.SetObject(); // clear and minimizeGenericValue & SetObject()この値を空のオブジェクトにします。Definition document.h:1013あるいは、C++の一時値とのswapによるイディオムと同じ方法を使えます。
Value(kObjectType).Swap(d);GenericValue & Swap(GenericValue &other) RAPIDJSON_NOEXCEPTこの値の内容を、もう一方の値の内容と交換します。Definition document.h:817同じ処理を、少し長い書き方で表すこともできます。
d.Swap(Value(kObjectType).Move());あるドキュメントのノードを別のドキュメントへ挿入するにはどうしますか。
JSONドキュメントで表した、次の2つのDOMツリーを考えます。
Document person;person.Parse("{\"person\":{\"name\":{\"first\":\"Adam\",\"last\":\"Thomas\"}}}");Document address;address.Parse("{\"address\":{\"city\":\"Moscow\",\"street\":\"Quiet\"}}");GenericDocument & Parse(const typename SourceEncoding::Ch *str)読み取り専用の文字列からJSONテキストを解析します(エンコーディング変換あり)。Definition document.h:2221addressドキュメント全体がpersonのノードになるように、両者を結合したいとします。
{ "person": {"name": { "first": "Adam", "last": "Thomas" },"address": { "city": "Moscow", "street": "Quiet" }}}最も重要なのは、ドキュメントと値の寿命を管理し、値を移す際に正しいアロケーターを使って、メモリー管理の一貫性を保つことです。
単純で最も効率的な方法は、上のaddressの定義を変更してpersonドキュメントのアロケーターで初期化し、値のルートメンバーを追加する方法です。
Documnet address(person.GetAllocator());...person["person"].AddMember("address", address["address"], person.GetAllocator());addressのルート値を名前で明示的に参照したくない場合は、イテレーターで参照することもできます。
auto addressRoot = address.MemberBegin();person["person"].AddMember(addressRoot->name, addressRoot->value, person.GetAllocator());2番目の方法は、addressドキュメントから値をディープコピーする方法です。
Value addressValue = Value(address["address"], person.GetAllocator());person["person"].AddMember("address", addressValue, person.GetAllocator());
Document/Value(DOM)
ムーブセマンティクスとは何ですか。なぜ使うのですか。
Valueは、コピーセマンティクスの代わりに、ムーブセマンティクスを使います。つまり、代入元の値を代入先へ代入すると、代入元の値の所有権が代入先へ移ります。
ムーブはコピーより高速なので、この設計により、利用者がコピーのオーバーヘッドを意識するようになります。
値をコピーするにはどうしますか。
アロケーター付きのコンストラクターとCopyFrom()という2つのAPIがあります。例は、値のディープコピーを参照してください。
文字列の長さを渡す必要があるのはなぜですか。
C文字列はヌル終端されるため、長さの計算にはstrlen()が必要で、実行時間の計算量は線形です。利用者がすでに文字列長を知っている場合には、多くの操作で不要なオーバーヘッドが生じます。
また、RapidJSONは、文字列内の\u0000(ヌル文字)を扱えます。ヌル文字を含む文字列では、strlen()は本当の長さを返せません。その場合は、利用者が長さを明示的に渡さなければなりません。
多くのDOM操作APIでアロケーター引数を渡す必要があるのはなぜですか。
これらはValueのメンバー関数なので、すべてのValueにアロケーターへのポインターを保存したくないためです。
数値型の間で変換しますか。
GetInt()やGetUint()などを使うと、変換が起こる場合があります。整数から整数への変換は、安全な場合だけ行います(それ以外はアサーションに失敗します)。64ビットの符号付き・符号なし整数からdoubleへは変換しますが、精度を失う可能性に注意してください。小数部のある数値や64ビットを超える整数は、GetDouble()でしか取得できません。
Reader/Writer(SAX)
JSONをprintfで出力するだけではいけないのですか。なぜWriterが必要ですか。
最も重要なのは、Writerが整形式のJSON出力を保証することです。SAXイベントの呼び出し方が不正なら(StartObject()にEndArray()を対応させるなど)、アサーションに失敗します。また、Writerは文字列をエスケープします(\nなど)。さらに、特に桁の区切りを使うロケールでは、printf()による数値出力が有効なJSON数値にならない場合があります。Writerの数値から文字列への変換には非常に高速なアルゴリズムが使われ、printf()やiostreamより高性能です。
解析を一時停止し、後で再開できますか。
現在の版では、性能を考慮して直接は対応していません。ただし、実行環境がマルチスレッドに対応する場合は、別のスレッドでJSONを解析し、入力ストリームをブロックして一時停止できます。
Unicode
UTF-8やUTF-16などの形式に対応しますか。
はい。UTF-8、UTF-16(LE/BE)、UTF-32(LE/BE)、ASCIIに完全に対応しています。
エンコーディングを検証できますか。
はい。Parse()へkParseValidateEncodingFlagを渡すだけです。ストリーム内に不正なエンコーディングがあれば、kParseErrorStringInvalidEncodingエラーが発生します。
サロゲートペアとは何ですか。RapidJSONは対応しますか。
JSONはUnicode文字のエスケープにUTF-16を使います。たとえば、\u5927は中国語の「大」を表します。基本多言語面(BMP)以外の文字は、UTF-16では2つの16ビット値で表し、これをUTF-16サロゲートペアと呼びます。たとえば、絵文字U+1F602は、JSON内で\uD83D\uDE02と表せます。
RapidJSONは、UTF-16サロゲートの解析と生成に完全に対応しています。
JSON文字列内の\u0000(ヌル文字)を扱えますか。
はい。JSON文字列内のヌル文字に完全に対応しています。ただし、利用者はそれを意識し、GetStringLength()などのAPIで、本当の文字列長を取得する必要があります。
ASCII以外のすべての文字を\uxxxxで出力できますか。
はい。Writerの出力エンコーディングのテンプレート引数にASCII<>を使うと、これらの文字を必ずエスケープできます。
ストリーム
大きなJSONファイルがあります。全体をメモリーへ読み込む必要がありますか。
FileReadStreamで、ファイルをチャンクごとに読み込めます。ただし、in situ解析では、ファイル全体を読み込む必要があります。
ネットワークから届くJSONをストリームとして解析できますか。
はい。このための独自のストリームを実装できます。FileReadStreamの実装を参照してください。
JSONのエンコーディングが分かりません。どう扱いますか。
入力ストリームのエンコーディングを自動検出するAutoUTFInputStreamを使えます。ただし、性能上のオーバーヘッドが生じます。
BOMとは何ですか。RapidJSONはどう扱いますか。
バイト順マーク(BOM)は、ファイルやストリームの先頭に置かれ、そのUTFエンコーディングの種類を示す場合があります。
RapidJSONのEncodedInputStreamは、BOMを検出して読み取れます。EncodedOutputStreamは、必要に応じてBOMを書き込めます。例は、エンコードされたストリームを参照してください。
リトルエンディアンとビッグエンディアンは、なぜ関係しますか。
ストリームのリトルエンディアン・ビッグエンディアンは、UTF-16とUTF-32では問題になりますが、UTF-8では問題になりません。
性能
RapidJSONは本当に高速ですか。
はい。オープンソースのJSONライブラリーで最速かもしれません。C/C++ JSONライブラリーの性能を評価するベンチマークがあります。
なぜ高速ですか。
RapidJSONの多くの設計判断は、時間と空間の性能を目指しています。そのため、APIの使いやすさが下がる場合があります。また、低水準の最適化(組み込み関数、SIMD)や、特殊なアルゴリズム(独自のdoubleと文字列の相互変換)も使っています。
SIMDとは何ですか。RapidJSONではどう使われますか。
SIMD命令は、現代のCPUで並列計算を行えます。RapidJSONはIntelのSSE2/SSE4.2をサポートし、空白の読み飛ばしを高速化します。これにより、インデント付きJSONの解析性能が改善します。RAPIDJSON_SSE2かRAPIDJSON_SSE42マクロを定義すると有効になります。ただし、該当する命令セットをサポートしないマシンで実行ファイルを動かすと、クラッシュします。
多くのメモリーを使いますか。
RapidJSONは、メモリー使用量を減らすことを目指して設計されています。
SAX APIでは、Readerのメモリー使用量は、JSONツリーの最大深さに比例する量と、JSON文字列の最大長を合わせた量になります。
DOM APIでは、各Valueは、32ビット・64ビットアーキテクチャーでそれぞれ正確に16・24バイトを使います。また、特殊なアロケーターで、メモリー確保のオーバーヘッドを最小化します。
高性能を目指す目的は何ですか。
非常に大きなJSONファイルを処理するアプリケーションや、大量のJSONを処理するサーバー側アプリケーションがあります。高性能であれば、遅延とスループットの両方を改善できます。広い意味では、エネルギーの節約にもつながります。
雑談
RapidJSONの開発者は誰ですか。
RapidJSONの原作者は、Milo Yip(miloyip)です。世界中の多くの貢献者が改善してきました。Philipp A. Hartmann(pah)は、多くの改善を実装し、自動テストを整備し、コミュニティーの多くの議論にも参加しています。Don Ding(thebusytypist)は反復型パーサーを実装しました。Andrii Senkovych(jollyroger)はCMakeへの移行を完成させました。Kosta(Kosta-Github)は、優れた短い文字列の最適化を提供しました。他のすべての貢献者とコミュニティーメンバーにも感謝します。
なぜRapidJSONを開発したのですか。
2011年に始めた、趣味のプロジェクトでした。Milo Yipはゲームプログラマーで、当時JSONを知ったばかりで、将来のプロジェクトで使いたいと思っていました。JSONがとても単純に見えたため、ヘッダーだけの高速なライブラリーを書こうと考えました。
開発に長い空白期間があるのはなぜですか。
主に、家族が増えるなどの個人的な事情によるものです。また、Milo Yipは、多くの余暇を、Jason Gregoryの「Game Engine Architecture」の中国語翻訳に費やしてきました。
リポジトリをGoogle CodeからGitHubへ移したのはなぜですか。
それが時代の流れであり、GitHubの方がはるかに強力で便利だからです。