C ++的简单JSON字符串转义?

时间:2011-10-11 10:16:21

标签: c++ json

我有一个非常简单的程序输出简单的JSON字符串,我手动连接在一起并通过std :: cout流输出(输出真的很简单)但我有可能包含双引号的字符串,卷曲-braces和其他可能破坏JSON字符串的字符。因此,我需要一个库(或更准确的函数)来相应地将字符串转义为JSON标准,尽可能轻量级,仅此而已。

我找到了一些用于将整个对象编码为JSON的库,但考虑到我的程序是900行cpp文件,我宁愿不依赖于比我的程序大几倍的库来实现某些东西就这么简单。

5 个答案:

答案 0 :(得分:34)

<强>买者

无论您采取何种解决方案,请记住JSON标准要求您转义所有控制字符。这似乎是一种常见的误解。许多开发人员都错了。

所有控制字符表示从'\x00''\x1f'的所有内容,而不仅仅是'\x0a'之类的短表示形式(也称为'\n' })。例如,必须将'\x02'字符转义为 \u0002

另见:ECMA-404 The JSON Data Interchange Format,第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();
}

最短陈述

对于最短的表示,您可以使用JSON快捷方式,例如\"而不是\u0022。以下函数生成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();
}

纯粹的开关声明

也可以使用纯的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

答案 1 :(得分:33)

更新:请勿使用此功能! 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();
}

答案 2 :(得分:7)

我编写了一个简单的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;
}

答案 3 :(得分:1)

以 vog 的回答为基础:

为字符 0 到 92 = null 到反斜杠生成一个完整的跳转表

// generate full jump table for c++ json string escape
// license is public domain or CC0-1.0
//var s = require('fs').readFileSync('case-list.txt', 'utf8');
var s = ` // escape hell...
        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;
`;
const charMap = new Map();
s.replace(/case\s+'(.*?)':\s+o\s+<<\s+"(.*?)";\s+break;/g, (...args) => {
  const [, charEsc, replaceEsc ] = args;
  const char = eval(`'${charEsc}'`);
  const replace = eval(`'${replaceEsc}'`);
  //console.dir({ char, replace, });
  charMap.set(char, replace);
});
iMax = Math.max(
  0x1f, // 31. 0 to 31: control characters
  '""'.charCodeAt(0), // 34
  '\\'.charCodeAt(0), // 92
);
const replace_function_name = 'String_showAsJson';
const replace_array_name = replace_function_name + '_replace_array';
// longest replace (\u0000) has 6 chars + 1 null byte = 7 byte
var res = `\
// ${iMax + 1} * 7 = ${(iMax + 1) * 7} byte / 4096 page = ${Math.round((iMax + 1) * 7 / 4096 * 100)}%
char ${replace_array_name}[${iMax + 1}][7] = {`;
res += '\n  ';
let i, lastEven;
for (i = 0; i <= iMax; i++) {
  const char = String.fromCharCode(i);
  const replace = charMap.has(char) ? charMap.get(char) :
    (i <= 0x1f) ? '\\u' + i.toString(16).padStart(4, 0) :
    char // no replace
  ;
  const hex = '0x' + i.toString(16).padStart(2, 0);
  //res += `case ${hex}: o << ${JSON.stringify(replace)}; break; /`+`/ ${i}\n`;
  //if (i > 0) res += ',';
  //res += `\n  ${JSON.stringify(replace)}, // ${i}`;
  if (i > 0 && i % 5 == 0) {
    res += `// ${i - 5} - ${i - 1}\n  `;
    lastEven = i;
  }
  res += `${JSON.stringify(replace)}, `;
}
res += `// ${lastEven} - ${i - 1}`;
res += `\n};

void ${replace_function_name}(std::ostream & o, const std::string & s) {
  for (auto c = s.cbegin(); c != s.cend(); c++) {
    if ((std::uint8_t) *c <= ${iMax})
      o << ${replace_array_name}[(std::uint8_t) *c];
    else
      o << *c;
  }
}
`;

//console.log(res);
document.querySelector('#res').innerHTML = res;
<pre id="res"></pre>

答案 4 :(得分:0)

最初,您并没有确切说出您正在一起串接的字符串是来自的地方,所以这可能没有任何用处。但是,如果它们都恰好都存在于代码中,如this comment中提到的@isnullxbh对另一个问题的答案,则另一种选择是利用一个可爱的C ++ 11功能:Raw string literals

我不会引用cppreference的冗长的,基于标准的解释,您可以在那里自己阅读。不过,基本上,R字符串为C ++带来了与程序员分隔的文字相同的类型,对内容的绝对限制是 no ,这是从shell中的here-docs获得的,以及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开头,并且在引号内,字符串前面带有自由格式的定界符(一个或多个字符),后跟一个开头请原谅。

从那时起,您可以安全地按字面意思写任何内容,除了右括号后跟您选择的定界符。该序列(后跟一个双引号)终止了原始文字,然后您可以放心地相信std::string将不受任何解析或字符串处理的影响。

在随后的操作中,“原始”状态也不会丢失。因此,从Crockford的 JavaScript的工作方式的章节列表中借用,这是完全有效的:

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;

字符串“章节”将从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"}
]