Simple JSON string escape for C++?
I have written a simple JSON escape and unescaped functions. The code is public available in GitHub. For anyone interested here is the code:
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;
}
Caveat
Whatever solution you take, keep in mind that the JSON standard requires that you escape all control characters. This seems to be a common misconception. Many developers get that wrong.
All control characters means everything from '\x00'
to '\x1f'
, not just those with a short representation such as '\x0a'
(also known as '\n'
). For example, you must escape the '\x02'
character as \u0002
.
See also: ECMA-404 - The JSON data interchange syntax, 2nd edition, December 2017, Page 4
Simple solution
If you know for sure that your input string is UTF-8 encoded, you can keep things simple.
Since JSON allows you to escape everything via \uXXXX
, even "
and \
, a simple solution is:
#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') << static_cast<int>(*c);
} else {
o << *c;
}
}
return o.str();
}
Shortest representation
For the shortest representation you may use JSON shortcuts, such as \"
instead of \u0022
. The following function produces the shortest JSON representation of a UTF-8 encoded string s
:
#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') << static_cast<int>(*c);
} else {
o << *c;
}
}
}
return o.str();
}
Pure switch statement
It is also possible to get along with a pure switch statement, that is, without if
and <iomanip>
. While this is quite cumbersome, it may be preferable from a "security by simplicity and purity" point of view:
#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();
}
Using a library
You might want to have a look at https://github.com/nlohmann/json, which is an efficient header-only C++ library (MIT License) that seems to be very well-tested.
You can either call their escape_string()
method directly (Note that this is a bit tricky, see comment below by Lukas Salich), or you can take their implementation of escape_string()
as a starting point for your own implementation:
https://github.com/nlohmann/json/blob/ec7a1d834773f9fee90d8ae908a0c9933c5646fc/src/json.hpp#L4604-L4697