Floating-point NaN or Infinity values should be allowed as a feature #209

Introduce 'allowSpecialFloats' for readers and 'useSpecialFloats' for writers, use consistent macro snprintf definition for writers and readers, provide new unit tests for #209
This commit is contained in:
drgler 2015-09-03 22:19:22 +02:00
parent 6329975e6d
commit 2084563efb
5 changed files with 167 additions and 44 deletions

View File

@ -321,6 +321,9 @@ public:
the JSON value in the input string.
- `"rejectDupKeys": false or true`
- If true, `parse()` returns false when a key is duplicated within an object.
- `"allowSpecialFloats": false or true`
- If true, special float values (NaNs and infinities) are allowed
and their values are lossfree restorable.
You can examine 'settings_` yourself
to see the defaults. You can also write and read them just like any

View File

@ -99,6 +99,10 @@ public:
Strictly speaking, this is not valid JSON. But when the output is being
fed to a browser's Javascript, it makes for smaller output and the
browser can handle the output just fine.
- "useSpecialFloats": false or true
- If true, outputs non-finite floating point values in the following way:
NaN values as "NaN", positive infinity as "Infinity", and negative infinity
as "-Infinity".
You can examine 'settings_` yourself
to see the defaults. You can also write and read them just like any

View File

@ -17,10 +17,21 @@
#include <sstream>
#include <memory>
#include <set>
#include <limits>
#if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below
#if defined(_MSC_VER)
#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above
#define snprintf sprintf_s
#elif _MSC_VER >= 1900 // VC++ 14.0 and above
#define snprintf std::snprintf
#else
#define snprintf _snprintf
#endif
#elif defined(__ANDROID__)
#define snprintf snprintf
#elif __cplusplus >= 201103L
#define snprintf std::snprintf
#endif
#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0
// Disable warning about strdup being deprecated.
@ -795,15 +806,7 @@ std::string Reader::getLocationLineAndColumn(Location location) const {
int line, column;
getLocationLineAndColumn(location, line, column);
char buffer[18 + 16 + 16 + 1];
#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__)
#if defined(WINCE)
_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column);
#else
sprintf_s(buffer, sizeof(buffer), "Line %d, Column %d", line, column);
#endif
#else
snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column);
#endif
return buffer;
}
@ -894,6 +897,7 @@ public:
bool allowSingleQuotes_;
bool failIfExtra_;
bool rejectDupKeys_;
bool allowSpecialFloats_;
int stackLimit_;
}; // OurFeatures
@ -905,6 +909,7 @@ OurFeatures::OurFeatures()
, allowDroppedNullPlaceholders_(false), allowNumericKeys_(false)
, allowSingleQuotes_(false)
, failIfExtra_(false)
, allowSpecialFloats_(false)
{
}
@ -950,6 +955,9 @@ private:
tokenTrue,
tokenFalse,
tokenNull,
tokenNaN,
tokenPosInf,
tokenNegInf,
tokenArraySeparator,
tokenMemberSeparator,
tokenComment,
@ -980,7 +988,7 @@ private:
bool readCppStyleComment();
bool readString();
bool readStringSingleQuote();
void readNumber();
bool readNumber();
bool readValue();
bool readObject(Token& token);
bool readArray(Token& token);
@ -1134,6 +1142,30 @@ bool OurReader::readValue() {
currentValue().setOffsetLimit(token.end_ - begin_);
}
break;
case tokenNaN:
{
Value v(std::numeric_limits<double>::quiet_NaN());
currentValue().swapPayload(v);
currentValue().setOffsetStart(token.start_ - begin_);
currentValue().setOffsetLimit(token.end_ - begin_);
}
break;
case tokenPosInf:
{
Value v(std::numeric_limits<double>::infinity());
currentValue().swapPayload(v);
currentValue().setOffsetStart(token.start_ - begin_);
currentValue().setOffsetLimit(token.end_ - begin_);
}
break;
case tokenNegInf:
{
Value v(-std::numeric_limits<double>::infinity());
currentValue().swapPayload(v);
currentValue().setOffsetStart(token.start_ - begin_);
currentValue().setOffsetLimit(token.end_ - begin_);
}
break;
case tokenArraySeparator:
case tokenObjectEnd:
case tokenArrayEnd:
@ -1215,8 +1247,12 @@ bool OurReader::readToken(Token& token) {
case '8':
case '9':
case '-':
token.type_ = tokenNumber;
readNumber();
if (readNumber()) {
token.type_ = tokenNumber;
} else {
token.type_ = tokenNegInf;
ok = features_.allowSpecialFloats_ && match("nfinity", 7);
}
break;
case 't':
token.type_ = tokenTrue;
@ -1230,6 +1266,22 @@ bool OurReader::readToken(Token& token) {
token.type_ = tokenNull;
ok = match("ull", 3);
break;
case 'N':
if (features_.allowSpecialFloats_) {
token.type_ = tokenNaN;
ok = match("aN", 2);
} else {
ok = false;
}
break;
case 'I':
if (features_.allowSpecialFloats_) {
token.type_ = tokenPosInf;
ok = match("nfinity", 7);
} else {
ok = false;
}
break;
case ',':
token.type_ = tokenArraySeparator;
break;
@ -1330,8 +1382,12 @@ bool OurReader::readCppStyleComment() {
return true;
}
void OurReader::readNumber() {
bool OurReader::readNumber() {
const char *p = current_;
if (p != end_ && *p == 'I') {
current_ = ++p;
return false;
}
char c = '0'; // stopgap for already consumed character
// integral part
while (c >= '0' && c <= '9')
@ -1350,6 +1406,7 @@ void OurReader::readNumber() {
while (c >= '0' && c <= '9')
c = (current_ = p) < end_ ? *p++ : 0;
}
return true;
}
bool OurReader::readString() {
Char c = 0;
@ -1758,15 +1815,7 @@ std::string OurReader::getLocationLineAndColumn(Location location) const {
int line, column;
getLocationLineAndColumn(location, line, column);
char buffer[18 + 16 + 16 + 1];
#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__)
#if defined(WINCE)
_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column);
#else
sprintf_s(buffer, sizeof(buffer), "Line %d, Column %d", line, column);
#endif
#else
snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column);
#endif
return buffer;
}
@ -1880,6 +1929,7 @@ CharReader* CharReaderBuilder::newCharReader() const
features.stackLimit_ = settings_["stackLimit"].asInt();
features.failIfExtra_ = settings_["failIfExtra"].asBool();
features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool();
features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool();
return new OurCharReader(collectComments, features);
}
static void getValidReaderKeys(std::set<std::string>* valid_keys)
@ -1894,6 +1944,7 @@ static void getValidReaderKeys(std::set<std::string>* valid_keys)
valid_keys->insert("stackLimit");
valid_keys->insert("failIfExtra");
valid_keys->insert("rejectDupKeys");
valid_keys->insert("allowSpecialFloats");
}
bool CharReaderBuilder::validate(Json::Value* invalid) const
{
@ -1927,6 +1978,7 @@ void CharReaderBuilder::strictMode(Json::Value* settings)
(*settings)["allowSingleQuotes"] = false;
(*settings)["failIfExtra"] = true;
(*settings)["rejectDupKeys"] = true;
(*settings)["allowSpecialFloats"] = false;
//! [CharReaderBuilderStrictMode]
}
// static
@ -1942,6 +1994,7 @@ void CharReaderBuilder::setDefaults(Json::Value* settings)
(*settings)["stackLimit"] = 1000;
(*settings)["failIfExtra"] = false;
(*settings)["rejectDupKeys"] = false;
(*settings)["allowSpecialFloats"] = false;
//! [CharReaderBuilderDefaults]
}

View File

@ -27,8 +27,14 @@
#define isfinite std::isfinite
#endif
#if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below
#if defined(_MSC_VER)
#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above
#define snprintf sprintf_s
#elif _MSC_VER >= 1900 // VC++ 14.0 and above
#define snprintf std::snprintf
#else
#define snprintf _snprintf
#endif
#elif defined(__ANDROID__)
#define snprintf snprintf
#elif __cplusplus >= 201103L
@ -108,43 +114,35 @@ std::string valueToString(UInt value) {
#endif // # if defined(JSON_HAS_INT64)
std::string valueToString(double value) {
std::string valueToString(double value, bool useSpecialFloats) {
// Allocate a buffer that is more than large enough to store the 16 digits of
// precision requested below.
char buffer[32];
int len = -1;
// Print into the buffer. We need not request the alternative representation
// that always has a decimal point because JSON doesn't distingish the
// concepts of reals and integers.
#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with
// visual studio 2005 to
// avoid warning.
#if defined(WINCE)
len = _snprintf(buffer, sizeof(buffer), "%.17g", value);
#else
len = sprintf_s(buffer, sizeof(buffer), "%.17g", value);
#endif
#else
// Print into the buffer. We need not request the alternative representation
// that always has a decimal point because JSON doesn't distingish the
// concepts of reals and integers.
if (isfinite(value)) {
len = snprintf(buffer, sizeof(buffer), "%.17g", value);
} else {
// IEEE standard states that NaN values will not compare to themselves
if (value != value) {
len = snprintf(buffer, sizeof(buffer), "null");
len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "NaN" : "null");
} else if (value < 0) {
len = snprintf(buffer, sizeof(buffer), "-1e+9999");
len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "-Infinity" : "-1e+9999");
} else {
len = snprintf(buffer, sizeof(buffer), "1e+9999");
len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "Infinity" : "1e+9999");
}
// For those, we do not need to call fixNumLoc, but it is fast.
}
#endif
assert(len >= 0);
fixNumericLocale(buffer, buffer + len);
return buffer;
}
std::string valueToString(double value) { return valueToString(value, false); }
std::string valueToString(bool value) { return value ? "true" : "false"; }
std::string valueToQuotedString(const char* value) {
@ -816,7 +814,8 @@ struct BuiltStyledStreamWriter : public StreamWriter
CommentStyle::Enum cs,
std::string const& colonSymbol,
std::string const& nullSymbol,
std::string const& endingLineFeedSymbol);
std::string const& endingLineFeedSymbol,
bool useSpecialFloats);
virtual int write(Value const& root, std::ostream* sout);
private:
void writeValue(Value const& value);
@ -843,13 +842,15 @@ private:
std::string endingLineFeedSymbol_;
bool addChildValues_ : 1;
bool indented_ : 1;
bool useSpecialFloats_ : 1;
};
BuiltStyledStreamWriter::BuiltStyledStreamWriter(
std::string const& indentation,
CommentStyle::Enum cs,
std::string const& colonSymbol,
std::string const& nullSymbol,
std::string const& endingLineFeedSymbol)
std::string const& endingLineFeedSymbol,
bool useSpecialFloats)
: rightMargin_(74)
, indentation_(indentation)
, cs_(cs)
@ -858,6 +859,7 @@ BuiltStyledStreamWriter::BuiltStyledStreamWriter(
, endingLineFeedSymbol_(endingLineFeedSymbol)
, addChildValues_(false)
, indented_(false)
, useSpecialFloats_(useSpecialFloats)
{
}
int BuiltStyledStreamWriter::write(Value const& root, std::ostream* sout)
@ -887,7 +889,7 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) {
pushValue(valueToString(value.asLargestUInt()));
break;
case realValue:
pushValue(valueToString(value.asDouble()));
pushValue(valueToString(value.asDouble(), useSpecialFloats_));
break;
case stringValue:
{
@ -1102,6 +1104,7 @@ StreamWriter* StreamWriterBuilder::newStreamWriter() const
std::string cs_str = settings_["commentStyle"].asString();
bool eyc = settings_["enableYAMLCompatibility"].asBool();
bool dnp = settings_["dropNullPlaceholders"].asBool();
bool usf = settings_["useSpecialFloats"].asBool();
CommentStyle::Enum cs = CommentStyle::All;
if (cs_str == "All") {
cs = CommentStyle::All;
@ -1123,7 +1126,7 @@ StreamWriter* StreamWriterBuilder::newStreamWriter() const
std::string endingLineFeedSymbol = "";
return new BuiltStyledStreamWriter(
indentation, cs,
colonSymbol, nullSymbol, endingLineFeedSymbol);
colonSymbol, nullSymbol, endingLineFeedSymbol, usf);
}
static void getValidWriterKeys(std::set<std::string>* valid_keys)
{
@ -1132,6 +1135,7 @@ static void getValidWriterKeys(std::set<std::string>* valid_keys)
valid_keys->insert("commentStyle");
valid_keys->insert("enableYAMLCompatibility");
valid_keys->insert("dropNullPlaceholders");
valid_keys->insert("useSpecialFloats");
}
bool StreamWriterBuilder::validate(Json::Value* invalid) const
{
@ -1162,6 +1166,7 @@ void StreamWriterBuilder::setDefaults(Json::Value* settings)
(*settings)["indentation"] = "\t";
(*settings)["enableYAMLCompatibility"] = false;
(*settings)["dropNullPlaceholders"] = false;
(*settings)["useSpecialFloats"] = false;
//! [StreamWriterBuilderDefaults]
}

View File

@ -7,6 +7,7 @@
#include <json/config.h>
#include <json/json.h>
#include <cstring>
#include <limits>
// Make numeric limits more convenient to talk about.
// Assumes int type in 32 bits.
@ -1651,6 +1652,26 @@ JSONTEST_FIXTURE(ValueTest, zeroesInKeys) {
}
}
JSONTEST_FIXTURE(ValueTest, specialFloats) {
Json::StreamWriterBuilder b;
b.settings_["useSpecialFloats"] = true;
Json::Value v = std::numeric_limits<double>::quiet_NaN();
std::string expected = "NaN";
std::string result = Json::writeString(b, v);
JSONTEST_ASSERT_STRING_EQUAL(expected, result);
v = std::numeric_limits<double>::infinity();
expected = "Infinity";
result = Json::writeString(b, v);
JSONTEST_ASSERT_STRING_EQUAL(expected, result);
v = -std::numeric_limits<double>::infinity();
expected = "-Infinity";
result = Json::writeString(b, v);
JSONTEST_ASSERT_STRING_EQUAL(expected, result);
}
struct WriterTest : JsonTest::TestCase {};
JSONTEST_FIXTURE(WriterTest, dropNullPlaceholders) {
@ -1924,7 +1945,7 @@ JSONTEST_FIXTURE(CharReaderStrictModeTest, dupKeys) {
struct CharReaderFailIfExtraTest : JsonTest::TestCase {};
JSONTEST_FIXTURE(CharReaderFailIfExtraTest, issue164) {
// This is interpretted as a string value followed by a colon.
// This is interpreted as a string value followed by a colon.
Json::CharReaderBuilder b;
Json::Value root;
char const doc[] =
@ -2259,6 +2280,40 @@ JSONTEST_FIXTURE(CharReaderAllowZeroesTest, issue176) {
delete reader;
}
struct CharReaderAllowSpecialFloatsTest : JsonTest::TestCase {};
JSONTEST_FIXTURE(CharReaderAllowSpecialFloatsTest, issue209) {
Json::CharReaderBuilder b;
b.settings_["allowSpecialFloats"] = true;
Json::Value root;
std::string errs;
Json::CharReader* reader(b.newCharReader());
{
char const doc[] = "{\"a\":NaN,\"b\":Infinity,\"c\":-Infinity}";
bool ok = reader->parse(
doc, doc + std::strlen(doc),
&root, &errs);
JSONTEST_ASSERT(ok);
JSONTEST_ASSERT_STRING_EQUAL("", errs);
JSONTEST_ASSERT_EQUAL(3u, root.size());
double n = root["a"].asDouble();
JSONTEST_ASSERT(n != n);
JSONTEST_ASSERT_EQUAL(std::numeric_limits<double>::infinity(), root.get("b", 0.0));
JSONTEST_ASSERT_EQUAL(-std::numeric_limits<double>::infinity(), root.get("c", 0.0));
}
{
char const doc[] = "{\"posInf\": Infinity, \"NegInf\": -Infinity}";
bool ok = reader->parse(
doc, doc + std::strlen(doc),
&root, &errs);
JSONTEST_ASSERT(ok);
JSONTEST_ASSERT_STRING_EQUAL("", errs);
JSONTEST_ASSERT_EQUAL(2u, root.size());
JSONTEST_ASSERT_EQUAL(std::numeric_limits<double>::infinity(), root["posInf"].asDouble());
JSONTEST_ASSERT_EQUAL(-std::numeric_limits<double>::infinity(), root["NegInf"].asDouble());
}
}
struct BuilderTest : JsonTest::TestCase {};
JSONTEST_FIXTURE(BuilderTest, settings) {
@ -2362,6 +2417,7 @@ int main(int argc, const char* argv[]) {
//JSONTEST_REGISTER_FIXTURE(runner, ValueTest, nulls);
JSONTEST_REGISTER_FIXTURE(runner, ValueTest, zeroes);
JSONTEST_REGISTER_FIXTURE(runner, ValueTest, zeroesInKeys);
JSONTEST_REGISTER_FIXTURE(runner, ValueTest, specialFloats);
JSONTEST_REGISTER_FIXTURE(runner, WriterTest, dropNullPlaceholders);
JSONTEST_REGISTER_FIXTURE(runner, StreamWriterTest, dropNullPlaceholders);
@ -2396,6 +2452,8 @@ int main(int argc, const char* argv[]) {
JSONTEST_REGISTER_FIXTURE(runner, CharReaderAllowZeroesTest, issue176);
JSONTEST_REGISTER_FIXTURE(runner, CharReaderAllowSpecialFloatsTest, issue209);
JSONTEST_REGISTER_FIXTURE(runner, BuilderTest, settings);
JSONTEST_REGISTER_FIXTURE(runner, IteratorTest, distance);