diff --git a/include/json/reader.h b/include/json/reader.h index c8ff747..c07ce19 100644 --- a/include/json/reader.h +++ b/include/json/reader.h @@ -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 diff --git a/include/json/writer.h b/include/json/writer.h index f5f0a38..39bfa9b 100644 --- a/include/json/writer.h +++ b/include/json/writer.h @@ -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 diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 8d09273..a8b1fe2 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -17,10 +17,21 @@ #include #include #include +#include -#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::quiet_NaN()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenPosInf: + { + Value v(std::numeric_limits::infinity()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } + break; + case tokenNegInf: + { + Value v(-std::numeric_limits::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* valid_keys) @@ -1894,6 +1944,7 @@ static void getValidReaderKeys(std::set* 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] } diff --git a/src/lib_json/json_writer.cpp b/src/lib_json/json_writer.cpp index 89cd651..a38a5f2 100644 --- a/src/lib_json/json_writer.cpp +++ b/src/lib_json/json_writer.cpp @@ -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* valid_keys) { @@ -1132,6 +1135,7 @@ static void getValidWriterKeys(std::set* 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] } diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index a532f1f..08cf104 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -7,6 +7,7 @@ #include #include #include +#include // 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::quiet_NaN(); + std::string expected = "NaN"; + std::string result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + + v = std::numeric_limits::infinity(); + expected = "Infinity"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + + v = -std::numeric_limits::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::infinity(), root.get("b", 0.0)); + JSONTEST_ASSERT_EQUAL(-std::numeric_limits::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::infinity(), root["posInf"].asDouble()); + JSONTEST_ASSERT_EQUAL(-std::numeric_limits::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);