Introduce 'allowSpecialFloats' for readers and 'useSpecialFloats' for writers, use consistent macro snprintf definition for writers and readers, provide new unit tests for #209
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
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
#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.
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;
}
bool allowSingleQuotes_;
bool failIfExtra_;
bool rejectDupKeys_;
+ bool allowSpecialFloats_;
int stackLimit_;
}; // OurFeatures
, allowDroppedNullPlaceholders_(false), allowNumericKeys_(false)
, allowSingleQuotes_(false)
, failIfExtra_(false)
+ , allowSpecialFloats_(false)
{
}
tokenTrue,
tokenFalse,
tokenNull,
+ tokenNaN,
+ tokenPosInf,
+ tokenNegInf,
tokenArraySeparator,
tokenMemberSeparator,
tokenComment,
bool readCppStyleComment();
bool readString();
bool readStringSingleQuote();
- void readNumber();
+ bool readNumber();
bool readValue();
bool readObject(Token& token);
bool readArray(Token& token);
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:
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;
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;
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')
while (c >= '0' && c <= '9')
c = (current_ = p) < end_ ? *p++ : 0;
}
+ return true;
}
bool OurReader::readString() {
Char c = 0;
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;
}
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)
valid_keys->insert("stackLimit");
valid_keys->insert("failIfExtra");
valid_keys->insert("rejectDupKeys");
+ valid_keys->insert("allowSpecialFloats");
}
bool CharReaderBuilder::validate(Json::Value* invalid) const
{
(*settings)["allowSingleQuotes"] = false;
(*settings)["failIfExtra"] = true;
(*settings)["rejectDupKeys"] = true;
+ (*settings)["allowSpecialFloats"] = false;
//! [CharReaderBuilderStrictMode]
}
// static
(*settings)["stackLimit"] = 1000;
(*settings)["failIfExtra"] = false;
(*settings)["rejectDupKeys"] = false;
+ (*settings)["allowSpecialFloats"] = false;
//! [CharReaderBuilderDefaults]
}
#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
#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) {
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);
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)
, endingLineFeedSymbol_(endingLineFeedSymbol)
, addChildValues_(false)
, indented_(false)
+ , useSpecialFloats_(useSpecialFloats)
{
}
int BuiltStyledStreamWriter::write(Value const& root, std::ostream* sout)
pushValue(valueToString(value.asLargestUInt()));
break;
case realValue:
- pushValue(valueToString(value.asDouble()));
+ pushValue(valueToString(value.asDouble(), useSpecialFloats_));
break;
case stringValue:
{
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;
std::string endingLineFeedSymbol = "";
return new BuiltStyledStreamWriter(
indentation, cs,
- colonSymbol, nullSymbol, endingLineFeedSymbol);
+ colonSymbol, nullSymbol, endingLineFeedSymbol, usf);
}
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
{
(*settings)["indentation"] = "\t";
(*settings)["enableYAMLCompatibility"] = false;
(*settings)["dropNullPlaceholders"] = false;
+ (*settings)["useSpecialFloats"] = false;
//! [StreamWriterBuilderDefaults]
}
#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.
}
}
+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) {
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[] =
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) {
//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);
JSONTEST_REGISTER_FIXTURE(runner, CharReaderAllowZeroesTest, issue176);
+ JSONTEST_REGISTER_FIXTURE(runner, CharReaderAllowSpecialFloatsTest, issue209);
+
JSONTEST_REGISTER_FIXTURE(runner, BuilderTest, settings);
JSONTEST_REGISTER_FIXTURE(runner, IteratorTest, distance);