2

JsonCppを使用してC++でJSONを解析しています。

例えば

Json::Reader r;
std::stringstream ss;
ss << "{\"name\": \"sample\"}";

Json::Value v;
assert(r.parse(ss, v));         // OK
assert(v["name"] == "sample");  // OK

しかし、私の実際の入力はJSONメッセージのストリーム全体であり、任意のサイズのチャンクで到着する可能性があります。私にできることは、JsonCppに入力を文字ごとに解析させ、JSONメッセージを見つけたら完全に使い果たしてしまうことだけです。

Json::Reader r;
std::string input = "{\"name\": \"sample\"}{\"name\": \"aardvark\"}";

for (size_t cursor = 0; cursor < input.size(); cursor++) {  
    std::stringstream ss;
    ss << input.substr(0, cursor);

    Json::Value v;
    if (r.parse(ss, v)) {
        std::cout << v["name"] << " ";
        input.erase(0, cursor);
    }
} // Output: sample aardvark

これはすでに少し厄介ですが、さらに悪化します。また、入力の一部が欠落している場合(何らかの理由で)に再同期できる必要があります。

これでロスレスである必要はありませんが、次のような入力がパーサーを永久に壊す可能性を防ぎたいと思います。

{"name": "samp{"name": "aardvark"}

この入力をJsonCppに渡すことは失敗しますが、バッファーにさらに多くの文字を受け取るので、その問題は解消されません。その秒は、その前の秒nameの直後は単に無効"です。有効なJSONを提示するためにバッファーを完了することはできません。

ただし、2番目の文字の時点でフラグメントが確実に無効になると言えば、その時点までのすべてをバッファにドロップし、次のオブジェクトが新しいオブジェクトの開始を検討するのをn待つのが最善です。 {-努力の再同期。


それで、JSONの不完全なフラグメントが完全な「オブジェクト」が構文的に無効になることをすでに保証しているかどうかをJsonCppに教えてもらう方法はありますか?

あれは:

{"name": "sample"}   Valid        (Json::Reader::parse == true)
{"name": "sam        Incomplete   (Json::Reader::parse == false)
{"name": "sam"LOL    Invalid      (Json::Reader::parse == false)

2つの失敗国家を区別したいと思います。

これを実現するためにJsonCppを使用できますか、それとも、入力文字列の各ステップでどの文字が「有効」であるかを考慮するステートマシンを構築して、独自のJSON「部分バリデーター」を作成する必要がありますか?車輪の再発明はしたくない...

4

3 に答える 3

3

バッファを1文字ずつ繰り返し、手動で次のことを確認します。

  • アルファベット文字の存在
    • 文字列の外側(ただし、"エスケープできるように注意し\てください)
    • の一部ではないnulltrueまたはfalse
    • 指数を含む数値リテラルのように見える、eまたは内部ではありませんE
  • 文字列の外側であるが直後の数字の存在"

...すべてを網羅しているわけではありませんが、メッセージの切り捨ての時点またはその時点にかなり近い時点で解析をかなり確実に中断するのに十分なケースをカバーしていると思います。

それは正しく受け入れます:

{"name": "samL
{"name": "sam0
{"name": "sam", 0
{"name": true

有効なJSONフラグメントとして、ただしキャッチ:

{"name": "sam"L
{"name": "sam"0
{"name": "sam"true

受け入れられないものとして。

したがって、次の入力はすべて、完全な後続オブジェクトが正常に解析される結果になります。

1. {"name": "samp{"name": "aardvark"}
   //            ^ ^
   //            A B    - B is point of failure.
   //                     Stripping leading `{` and scanning for the first
   //                      free `{` gets us to A. (*)
   {"name": "aardvark"}

2. {"name": "samp{"0": "abc"}
   //            ^ ^
   //            A B    - B is point of failure.
   //                     Stripping and scanning gets us to A.
   {"0": "abc"}

3. {"name":{ "samp{"0": "abc"}
   //      ^      ^ ^
   //      A      B C   - C is point of failure.
   //                     Stripping and scanning gets us to A.
   { "samp{"0": "abc"}
   //     ^ ^
   //     B C           - C is still point of failure.
   //                     Stripping and scanning gets us to B.
   {"0": "abc"}

私の実装は、かなり徹底的な単体テストに合格しています。それでも、複雑さを増すことなくアプローチ自体を改善できるのではないかと思います。


*先頭を探す代わりに、"{"実際にはすべてのメッセージの前にセンチネル文字列を追加して、「ストリッピングとスキャン」の部分の信頼性をさらに高めています。

于 2012-02-14T02:53:59.693 に答える
3

確かに、実際にパケット(したがってプロデューサー)を制御するかどうかによって異なります。その場合、最も簡単な方法は、ヘッダーに境界を示すことです。

+---+---+---+---+-----------------------
| 3 | 16|132|243|endofprevious"}{"name":...
+---+---+---+---+-----------------------

ヘッダーは単純です。

  • 3は境界の数を示します
  • 16、132、および243は、新しいオブジェクト(またはリスト)の開き角かっこに対応する各境界の位置を示します。

そして、バッファ自体が来ます。

このようなパケットを受信すると、次のエントリを解析できます。

  • previous + current[0:16]
  • current[16:132]
  • current[132:243]

そしてcurrent[243:]、次のパケットのために保存されます(ただし、完了した場合はいつでも解析を試みることができます)。

このように、パケットは自動同期され、ファジー検出は発生せず、それに伴うすべての障害ケースが発生します。

0パケットに境界がある可能性があることに注意してください。これは、1つのオブジェクトが複数のパケットにまたがるのに十分な大きさであり、今のところ蓄積する必要があることを意味します。

数値表現を「固定」(たとえば、それぞれ4バイト)にし、バイトオーダー(マシンのバイトオーダー)に落ち着かせて、バイナリに簡単に変換したり、バイナリから変換したりすることをお勧めします。オーバーヘッドはかなり最小限であると思います({"name":""}すでに11バイトである場合、エントリごとに4バイト+ 4バイト)。

于 2012-02-14T07:51:13.073 に答える
0

expatまたは他のストリーミングされたxmlパーサーを見てください。jsoncppのロジックは、そうでない場合でも同様である必要があります。(必要に応じて、このライブラリの開発者にストリームの読み取りを改善するよう依頼してください。)

言い換えれば、そして私の観点から:

  1. ネットワーク(JSONではない)パケットの一部が失われた場合は、JSONパーサーの問題ではないため、より信頼性の高いプロトコルを使用するか、独自のプロトコルを考案してください。そして、その上でのみJSONを転送します。

  2. JSONパーサーがエラーを報告し、このエラーが最後に解析されたトークンで発生した場合(ストリームにデータはありませんが、予期されています)-データを蓄積して再試行します(このタスクはライブラリ自体で実行する必要があります)。

    ただし、エラーが報告されない場合もあります。たとえば、123456を転送し、123のみが受信された場合。ただし、プリミティブデータを単一のJSONパケットで転送しないため、これはケースとは一致しません。

  3. ストリームに有効なパケットとそれに続く半受信パケットが含まれている場合は、有効なパケットごとにコールバックを呼び出す必要があります。

  4. JSONパーサーがエラーを報告し、それが本当に無効なJSONである場合は、必要に応じてストリームを閉じて再度開く必要があります。

于 2012-12-27T09:44:05.907 に答える