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. the JSON value in the input string.
- `"rejectDupKeys": false or true` - `"rejectDupKeys": false or true`
- If true, `parse()` returns false when a key is duplicated within an object. - 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 You can examine 'settings_` yourself
to see the defaults. You can also write and read them just like any 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 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 fed to a browser's Javascript, it makes for smaller output and the
browser can handle the output just fine. 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 You can examine 'settings_` yourself
to see the defaults. You can also write and read them just like any to see the defaults. You can also write and read them just like any

View File

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

View File

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

View File

@ -7,6 +7,7 @@
#include <json/config.h> #include <json/config.h>
#include <json/json.h> #include <json/json.h>
#include <cstring> #include <cstring>
#include <limits>
// Make numeric limits more convenient to talk about. // Make numeric limits more convenient to talk about.
// Assumes int type in 32 bits. // 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 {}; struct WriterTest : JsonTest::TestCase {};
JSONTEST_FIXTURE(WriterTest, dropNullPlaceholders) { JSONTEST_FIXTURE(WriterTest, dropNullPlaceholders) {
@ -1924,7 +1945,7 @@ JSONTEST_FIXTURE(CharReaderStrictModeTest, dupKeys) {
struct CharReaderFailIfExtraTest : JsonTest::TestCase {}; struct CharReaderFailIfExtraTest : JsonTest::TestCase {};
JSONTEST_FIXTURE(CharReaderFailIfExtraTest, issue164) { 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::CharReaderBuilder b;
Json::Value root; Json::Value root;
char const doc[] = char const doc[] =
@ -2259,6 +2280,40 @@ JSONTEST_FIXTURE(CharReaderAllowZeroesTest, issue176) {
delete reader; 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 {}; struct BuilderTest : JsonTest::TestCase {};
JSONTEST_FIXTURE(BuilderTest, settings) { 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, nulls);
JSONTEST_REGISTER_FIXTURE(runner, ValueTest, zeroes); JSONTEST_REGISTER_FIXTURE(runner, ValueTest, zeroes);
JSONTEST_REGISTER_FIXTURE(runner, ValueTest, zeroesInKeys); JSONTEST_REGISTER_FIXTURE(runner, ValueTest, zeroesInKeys);
JSONTEST_REGISTER_FIXTURE(runner, ValueTest, specialFloats);
JSONTEST_REGISTER_FIXTURE(runner, WriterTest, dropNullPlaceholders); JSONTEST_REGISTER_FIXTURE(runner, WriterTest, dropNullPlaceholders);
JSONTEST_REGISTER_FIXTURE(runner, StreamWriterTest, 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, CharReaderAllowZeroesTest, issue176);
JSONTEST_REGISTER_FIXTURE(runner, CharReaderAllowSpecialFloatsTest, issue209);
JSONTEST_REGISTER_FIXTURE(runner, BuilderTest, settings); JSONTEST_REGISTER_FIXTURE(runner, BuilderTest, settings);
JSONTEST_REGISTER_FIXTURE(runner, IteratorTest, distance); JSONTEST_REGISTER_FIXTURE(runner, IteratorTest, distance);