我有一个非常简单的程序输出简单的JSON字符串,我手动连接在一起并通过std :: cout流输出(输出真的很简单)但我有可能包含双引号的字符串,卷曲-braces和其他可能破坏JSON字符串的字符。因此,我需要一个库(或更准确的函数)来相应地将字符串转义为JSON标准,尽可能轻量级,仅此而已。
我找到了一些用于将整个对象编码为JSON的库,但考虑到我的程序是900行cpp文件,我宁愿不依赖于比我的程序大几倍的库来实现某些东西就这么简单。
答案 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()
的实施作为自己实施的起点:
答案 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"}
]