web-dev-qa-db-ja.com

C ++の単純なJSON文字列エスケープ?

手動で連結してstd :: coutストリームを介して出力する単純なJSON文字列を出力する非常に単純なプログラムがあります(出力は本当に単純です)が、二重引用符、中括弧、およびJSON文字列を壊す可能性のある他の文字。したがって、JSON標準に従って文字列をエスケープするためのライブラリ(またはより正確な関数)が必要です。可能な限り軽量で、それ以上でもそれ以下でもありません。

オブジェクト全体をJSONにエンコードするために使用されるいくつかのライブラリを見つけましたが、私のプログラムは900行のcppファイルであることを念頭に置いて、単純なものを実現するためだけに、プログラムよりも数倍大きいライブラリに依存したくないこの。

33
ddinchev

警告

どのようなソリューションを採用する場合でも、JSON標準ではエスケープする必要があることに注意してくださいすべての制御文字。これはよくある誤解のようです。多くの開発者はそれを間違っています。

すべての制御文字は、_'\x00'_(_'\x1f'_とも呼ばれる)などの短い表現を持つものだけでなく、_'\x0a'_から_'\n'_までのすべてを意味します。たとえば、_'\x02'_文字を_\u0002_としてエスケープする必要があります

参照: ECMA-404 JSONデータ交換形式 、10ページ

簡単な解決策

入力文字列がUTF-8でエンコードされていることが確実にわかっている場合は、物事を単純に保つことができます。

JSONでは_\uXXXX_を介してすべてをエスケープできるため、_"_と_\_も含めて、簡単な解決策は次のとおりです。

_#include <sstream>
#include <iomanip>

std::string escape_json(const std::string &s) {
    std::ostringstream o;
    for (auto c = s.cbegin(); c != s.cend(); c++) {
        if (*c == '"' || *c == '\\' || ('\x00' <= *c && *c <= '\x1f')) {
            o << "\\u"
              << std::hex << std::setw(4) << std::setfill('0') << (int)*c;
        } else {
            o << *c;
        }
    }
    return o.str();
}
_

最も短い表現

最短の表現には、_\"_の代わりに_\u0022_などのJSONショートカットを使用できます。次の関数は、UTF-8エンコードされた文字列sの最も短いJSON表現を生成します。

_#include <sstream>
#include <iomanip>

std::string escape_json(const std::string &s) {
    std::ostringstream o;
    for (auto c = s.cbegin(); c != s.cend(); c++) {
        switch (*c) {
        case '"': o << "\\\""; break;
        case '\\': o << "\\\\"; break;
        case '\b': o << "\\b"; break;
        case '\f': o << "\\f"; break;
        case '\n': o << "\\n"; break;
        case '\r': o << "\\r"; break;
        case '\t': o << "\\t"; break;
        default:
            if ('\x00' <= *c && *c <= '\x1f') {
                o << "\\u"
                  << std::hex << std::setw(4) << std::setfill('0') << (int)*c;
            } else {
                o << *c;
            }
        }
    }
    return o.str();
}
_

Pure switchステートメント

純粋なswitchステートメント、つまりifと_<iomanip>_なしでやり取りすることもできます。これはかなり面倒ですが、「単純さと純粋さによるセキュリティ」の観点からは望ましいかもしれません。

_#include <sstream>

std::string escape_json(const std::string &s) {
    std::ostringstream o;
    for (auto c = s.cbegin(); c != s.cend(); c++) {
        switch (*c) {
        case '\x00': o << "\\u0000"; break;
        case '\x01': o << "\\u0001"; break;
        ...
        case '\x0a': o << "\\n"; break;
        ...
        case '\x1f': o << "\\u001f"; break;
        case '\x22': o << "\\\""; break;
        case '\x5c': o << "\\\\"; break;
        default: o << *c;
        }
    }
    return o.str();
}
_

ライブラリの使用

https://github.com/nlohmann/json を確認することをお勧めします。これは、十分にテストされているように思われる効率的なヘッダーのみのC++ライブラリ(MITライセンス)です。

escape_string()メソッドを直接呼び出すか、独自の実装の開始点としてescape_string()の実装を使用できます。

https://github.com/nlohmann/json/blob/ec7a1d834773f9fee90d8ae908a0c9933c5646fc/src/json.hpp#L4604-L4697

34
vog

更新:これを使用しないでください! vogは、以下のより完全な(そして同様にコンパクトな)ソリューションを提供します。 https://stackoverflow.com/a/33799784

これは非常に単純なスタートですが、無効なUnicode文字は処理しません。出力にそれらのいずれも期待しない場合は、これを自由に使用してください...

#include <string>
#include <sstream>

std::string escapeJsonString(const std::string& input) {
    std::ostringstream ss;
    for (auto iter = input.cbegin(); iter != input.cend(); iter++) {
    //C++98/03:
    //for (std::string::const_iterator iter = input.begin(); iter != input.end(); iter++) {
        switch (*iter) {
            case '\\': ss << "\\\\"; break;
            case '"': ss << "\\\""; break;
            case '/': ss << "\\/"; break;
            case '\b': ss << "\\b"; break;
            case '\f': ss << "\\f"; break;
            case '\n': ss << "\\n"; break;
            case '\r': ss << "\\r"; break;
            case '\t': ss << "\\t"; break;
            default: ss << *iter; break;
        }
    }
    return ss.str();
}
34
Milan

簡単なJSONエスケープとエスケープされていない関数を記述しました。コードは GitHub で公開されています。ここに興味のある人はコードです:

enum State {ESCAPED, UNESCAPED};

std::string escapeJSON(const std::string& input)
{
    std::string output;
    output.reserve(input.length());

    for (std::string::size_type i = 0; i < input.length(); ++i)
    {
        switch (input[i]) {
            case '"':
                output += "\\\"";
                break;
            case '/':
                output += "\\/";
                break;
            case '\b':
                output += "\\b";
                break;
            case '\f':
                output += "\\f";
                break;
            case '\n':
                output += "\\n";
                break;
            case '\r':
                output += "\\r";
                break;
            case '\t':
                output += "\\t";
                break;
            case '\\':
                output += "\\\\";
                break;
            default:
                output += input[i];
                break;
        }

    }

    return output;
}

std::string unescapeJSON(const std::string& input)
{
    State s = UNESCAPED;
    std::string output;
    output.reserve(input.length());

    for (std::string::size_type i = 0; i < input.length(); ++i)
    {
        switch(s)
        {
            case ESCAPED:
                {
                    switch(input[i])
                    {
                        case '"':
                            output += '\"';
                            break;
                        case '/':
                            output += '/';
                            break;
                        case 'b':
                            output += '\b';
                            break;
                        case 'f':
                            output += '\f';
                            break;
                        case 'n':
                            output += '\n';
                            break;
                        case 'r':
                            output += '\r';
                            break;
                        case 't':
                            output += '\t';
                            break;
                        case '\\':
                            output += '\\';
                            break;
                        default:
                            output += input[i];
                            break;
                    }

                    s = UNESCAPED;
                    break;
                }
            case UNESCAPED:
                {
                    switch(input[i])
                    {
                        case '\\':
                            s = ESCAPED;
                            break;
                        default:
                            output += input[i];
                            break;
                    }
                }
        }
    }
    return output;
}
7
mariolpantunes

あなたが一緒にコブリングしているそれらの文字列がどこからどこから来ているかを正確に言っていなかったので、これは役に立たないかもしれません。しかし、それらすべてがたまたまコード内に存在する場合、@ isnullxbhが このコメント で別の質問の回答に言及したように、別のオプションは素敵なC++ 11機能を活用することです 生の文字列リテラル

Cppreferenceの長くて標準に基づく説明を引用するつもりはありません。自分で読むことができます。基本的に、ただし、R文字列はC++にプログラマーで区切られたリテラルと同じ種類をもたらしますが、コンテンツに絶対的にno制限があり、ここから取得できます-シェルのドキュメント、Perlのようなどの言語が効果的に使用されているか。 (中括弧を使用した接頭辞付きの引用は、Perlの唯一の最大の発明かもしれません:)

my qstring = q{Quoted 'string'!};
my qqstring = qq{Double "quoted" 'string'!};
my replacedstring = q{Regexps that /totally/! get eaten by your parser.};
replacedstring =~ s{/totally/!}{(won't!)}; 
# Heh. I see the syntax highlighter isn't quite up to the challege, though.

C++ 11以降では、生の文字列リテラルの前に二重引用符の前に大文字のRを付け、引用符の内側では、文字列の前に自由形式の区切り文字(1つまたは複数の文字)とそれに続く開き括弧を付けます。

そこから、選択した区切り文字が後に続く閉じかっこ以外は、文字どおりすべてを安全に書き込むことができます。そのシーケンス(最後に二重引用符が続く)は、未処理のリテラルを終了し、解析または文字列処理によって信頼され、信頼できると確信できるstd::stringがあります。

「生」らしさはその後の操作でも失われません。したがって、CrockfordのHow JavaScript Worksの章リストから借りると、これは完全に有効です。

std::string ch0_to_4 = R"json(
[
    {"number": 0, "chapter": "Read Me First!"},
    {"number": 1, "chapter": "How Names Work"},
    {"number": 2, "chapter": "How Numbers Work"},
    {"number": 3, "chapter": "How Big Integers Work"},
    {"number": 4, "chapter": "How Big Floating Point Works"},)json";

std::string ch5_and_6 = R"json(
    {"number": 5, "chapter": "How Big Rationals Work"},
    {"number": 6, "chapter": "How Booleans Work"})json";

std::string chapters = ch0_to_4 + ch5_and_6 + "\n]";
std::cout << chapters;

文字列 'chapters'はstd::coutから完全に完全な形で出現します。

[
    {"number": 0, "chapter": "Read Me First!"},
    {"number": 1, "chapter": "How Names Work"},
    {"number": 2, "chapter": "How Numbers Work"},
    {"number": 3, "chapter": "How Big Integers Work"},
    {"number": 4, "chapter": "How Big Floating Point Works"},
    {"number": 5, "chapter": "How Big Rationals Work"},
    {"number": 6, "chapter": "How Booleans Work"}
]
0
FeRD