Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ environment:

before_build:
- git submodule update --init --recursive
- cmake -H. -BBuild/VS -G "Visual Studio %VS_VERSION%" -DCMAKE_GENERATOR_PLATFORM=%VS_PLATFORM% -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_SHARED_LIBS=true -DRAPIDJSON_BUILD_CXX11=%CXX11% -DRAPIDJSON_BUILD_CXX17=%CXX17% -DRAPIDJSON_BUILD_CXX20=%CXX20% -DRAPIDJSON_USE_MEMBERSMAP=%MEMBERSMAP% -Wno-dev
- cmake -H. -BBuild/VS -G "Visual Studio %VS_VERSION%" -DCMAKE_GENERATOR_PLATFORM=%VS_PLATFORM% -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_SHARED_LIBS=true -DRAPIDJSON_BUILD_CXX11=%CXX11% -DRAPIDJSON_BUILD_CXX17=%CXX17% -DRAPIDJSON_BUILD_CXX20=%CXX20% -DRAPIDJSON_USE_MEMBERSMAP=%MEMBERSMAP% -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -Wno-dev

build:
project: Build\VS\RapidJSON.sln
Expand Down
4 changes: 4 additions & 0 deletions include/rapidjson/document.h
Original file line number Diff line number Diff line change
Expand Up @@ -1972,6 +1972,8 @@ class GenericValue {
return handler.EndArray(data_.a.size);

case kStringType:
if (data_.f.flags & kRawNumberFlag)
return handler.RawNumber(GetString(), GetStringLength(), (data_.f.flags & kCopyFlag) != 0);
return handler.String(GetString(), GetStringLength(), (data_.f.flags & kCopyFlag) != 0);

default:
Expand Down Expand Up @@ -1999,6 +2001,7 @@ class GenericValue {
kStringFlag = 0x0400,
kCopyFlag = 0x0800,
kInlineStrFlag = 0x1000,
kRawNumberFlag = 0x2000,

// Initial flags of different types.
kNullFlag = kNullType,
Expand Down Expand Up @@ -2831,6 +2834,7 @@ class GenericDocument : public GenericValue<Encoding, Allocator> {
new (stack_.template Push<ValueType>()) ValueType(str, length, GetAllocator());
else
new (stack_.template Push<ValueType>()) ValueType(str, length);
stack_.template Top<ValueType>()->data_.f.flags |= ValueType::kRawNumberFlag;
return true;
}

Expand Down
2 changes: 1 addition & 1 deletion include/rapidjson/prettywriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class PrettyWriter : public Writer<OutputStream, SourceEncoding, TargetEncoding,
RAPIDJSON_ASSERT(str != 0);
(void)copy;
PrettyPrefix(kNumberType);
return Base::EndValue(Base::WriteString(str, length));
return Base::EndValue(Base::WriteRawValue(str, length));
}

bool String(const Ch* str, SizeType length, bool copy = false) {
Expand Down
2 changes: 1 addition & 1 deletion include/rapidjson/writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ class Writer {
RAPIDJSON_ASSERT(str != 0);
(void)copy;
Prefix(kNumberType);
return EndValue(WriteString(str, length));
return EndValue(WriteRawValue(str, length));
}

bool String(const Ch* str, SizeType length, bool copy = false) {
Expand Down
163 changes: 163 additions & 0 deletions test/unittest/documenttest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "unittest.h"
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/prettywriter.h"
#include "rapidjson/filereadstream.h"
#include "rapidjson/encodedstream.h"
#include "rapidjson/stringbuffer.h"
Expand Down Expand Up @@ -669,6 +670,168 @@ TYPED_TEST(DocumentMove, MoveAssignmentStack) {
// d1 = d2;
//}

TEST(Document, RawNumberRoundtrip) {
// Parse with kParseNumbersAsStringsFlag, then serialize back
// Numbers should survive the DOM roundtrip without gaining quotes
const char* json = "{\"age\":27,\"pi\":3.14159,\"name\":\"test\"}";

Document d;
d.Parse<kParseNumbersAsStringsFlag>(json);
EXPECT_FALSE(d.HasParseError());

// Verify values are stored as strings internally
EXPECT_TRUE(d["age"].IsString());
EXPECT_STREQ("27", d["age"].GetString());
EXPECT_TRUE(d["pi"].IsString());
EXPECT_STREQ("3.14159", d["pi"].GetString());
EXPECT_TRUE(d["name"].IsString());
EXPECT_STREQ("test", d["name"].GetString());

// Serialize back via Writer
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
d.Accept(writer);

// Numbers should NOT be quoted, but actual strings should be
EXPECT_STREQ("{\"age\":27,\"pi\":3.14159,\"name\":\"test\"}", buffer.GetString());
}

TEST(Document, RawNumberRoundtrip_Array) {
const char* json = "[1, 2.5, \"hello\", 100]";

Document d;
d.Parse<kParseNumbersAsStringsFlag>(json);
EXPECT_FALSE(d.HasParseError());

StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
d.Accept(writer);

EXPECT_STREQ("[1,2.5,\"hello\",100]", buffer.GetString());
}

TEST(Document, RawNumberRoundtrip_PrettyWriter) {
const char* json = "{\"value\":42}";

Document d;
d.Parse<kParseNumbersAsStringsFlag>(json);
EXPECT_FALSE(d.HasParseError());

StringBuffer buffer;
PrettyWriter<StringBuffer> writer(buffer);
d.Accept(writer);

// Should contain unquoted 42
const char* s = buffer.GetString();
EXPECT_TRUE(strstr(s, ": 42") != NULL);
// Should NOT contain "42"
EXPECT_TRUE(strstr(s, ": \"42\"") == NULL);
}

TEST(Document, RawNumberRoundtrip_Precision) {
// Numbers that would lose precision if converted to double
const char* json = "{\"big\":12345678901234567890,\"pi\":3.141592653589793238,\"tiny\":1e-14,\"tiny2\":0.00000000000001}";

Document d;
d.Parse<kParseNumbersAsStringsFlag>(json);
EXPECT_FALSE(d.HasParseError());

// Verify stored as strings internally
EXPECT_TRUE(d["big"].IsString());
EXPECT_STREQ("12345678901234567890", d["big"].GetString());
EXPECT_STREQ("3.141592653589793238", d["pi"].GetString());
EXPECT_STREQ("1e-14", d["tiny"].GetString());
EXPECT_STREQ("0.00000000000001", d["tiny2"].GetString());

// Roundtrip must preserve exact text
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
d.Accept(writer);

EXPECT_STREQ("{\"big\":12345678901234567890,\"pi\":3.141592653589793238,\"tiny\":1e-14,\"tiny2\":0.00000000000001}", buffer.GetString());
}

TEST(Document, RawNumberRoundtrip_NumberFormats) {
// Test all valid JSON number notations survive roundtrip exactly
const char* json =
"{"
"\"int\":42,"
"\"neg\":-17,"
"\"zero\":0,"
"\"frac\":3.14,"
"\"neg_frac\":-0.5,"
"\"exp_lower\":1e10,"
"\"exp_upper\":1E10,"
"\"exp_plus\":1e+10,"
"\"exp_neg\":1e-10,"
"\"exp_frac\":1.5e3,"
"\"exp_neg_frac\":2.99792458e+8,"
"\"huge_int\":99999999999999999999,"
"\"huge_neg\":-99999999999999999999,"
"\"huge_exp\":1e308,"
"\"tiny_exp\":1e-308,"
"\"tiny_frac\":0.000000000000000001,"
"\"max_digits\":1.7976931348623157e+308,"
"\"leading_zero_frac\":0.123"
"}";

Document d;
d.Parse<kParseNumbersAsStringsFlag>(json);
EXPECT_FALSE(d.HasParseError());

// Verify each value stored as exact text
EXPECT_STREQ("42", d["int"].GetString());
EXPECT_STREQ("-17", d["neg"].GetString());
EXPECT_STREQ("0", d["zero"].GetString());
EXPECT_STREQ("3.14", d["frac"].GetString());
EXPECT_STREQ("-0.5", d["neg_frac"].GetString());
EXPECT_STREQ("1e10", d["exp_lower"].GetString());
EXPECT_STREQ("1E10", d["exp_upper"].GetString());
EXPECT_STREQ("1e+10", d["exp_plus"].GetString());
EXPECT_STREQ("1e-10", d["exp_neg"].GetString());
EXPECT_STREQ("1.5e3", d["exp_frac"].GetString());
EXPECT_STREQ("2.99792458e+8", d["exp_neg_frac"].GetString());
EXPECT_STREQ("99999999999999999999", d["huge_int"].GetString());
EXPECT_STREQ("-99999999999999999999", d["huge_neg"].GetString());
EXPECT_STREQ("1e308", d["huge_exp"].GetString());
EXPECT_STREQ("1e-308", d["tiny_exp"].GetString());
EXPECT_STREQ("0.000000000000000001", d["tiny_frac"].GetString());
EXPECT_STREQ("1.7976931348623157e+308", d["max_digits"].GetString());
EXPECT_STREQ("0.123", d["leading_zero_frac"].GetString());

// Full roundtrip
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
d.Accept(writer);

// All numbers must appear unquoted and with exact original text
const char* result = buffer.GetString();
EXPECT_TRUE(strstr(result, ":42,") != NULL);
EXPECT_TRUE(strstr(result, ":-17,") != NULL);
EXPECT_TRUE(strstr(result, ":0,") != NULL);
EXPECT_TRUE(strstr(result, ":3.14,") != NULL);
EXPECT_TRUE(strstr(result, ":-0.5,") != NULL);
EXPECT_TRUE(strstr(result, ":1e10,") != NULL);
EXPECT_TRUE(strstr(result, ":1E10,") != NULL);
EXPECT_TRUE(strstr(result, ":1e+10,") != NULL);
EXPECT_TRUE(strstr(result, ":1e-10,") != NULL);
EXPECT_TRUE(strstr(result, ":1.5e3,") != NULL);
EXPECT_TRUE(strstr(result, ":2.99792458e+8,") != NULL);
EXPECT_TRUE(strstr(result, ":99999999999999999999,") != NULL);
EXPECT_TRUE(strstr(result, ":-99999999999999999999,") != NULL);
EXPECT_TRUE(strstr(result, ":1e308,") != NULL);
EXPECT_TRUE(strstr(result, ":1e-308,") != NULL);
EXPECT_TRUE(strstr(result, ":0.000000000000000001,") != NULL);
EXPECT_TRUE(strstr(result, ":1.7976931348623157e+308,") != NULL);
EXPECT_TRUE(strstr(result, ":0.123}") != NULL);

// None should be quoted
EXPECT_TRUE(strstr(result, ":\"42\"") == NULL);
EXPECT_TRUE(strstr(result, ":\"-17\"") == NULL);
EXPECT_TRUE(strstr(result, ":\"1e10\"") == NULL);
EXPECT_TRUE(strstr(result, ":\"99999999999999999999\"") == NULL);
}

#ifdef __clang__
RAPIDJSON_DIAG_POP
#endif
19 changes: 19 additions & 0 deletions test/unittest/prettywritertest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,25 @@ TEST(PrettyWriter, Issue_1336) {
EXPECT_TRUE(writer.IsComplete());
}

TEST(PrettyWriter, RawNumber_NoQuotes) {
StringBuffer buffer;
PrettyWriter<StringBuffer> writer(buffer);
writer.StartObject();
writer.Key("pi");
writer.RawNumber("3.14159", 7);
writer.Key("answer");
writer.RawNumber("42", 2);
writer.Key("label");
writer.String("test");
writer.EndObject();
EXPECT_TRUE(writer.IsComplete());
// Verify numbers are not quoted
const char* s = buffer.GetString();
EXPECT_TRUE(strstr(s, ": 3.14159") != NULL);
EXPECT_TRUE(strstr(s, ": 42") != NULL);
EXPECT_TRUE(strstr(s, ": \"test\"") != NULL);
}

#ifdef __clang__
RAPIDJSON_DIAG_POP
#endif
25 changes: 25 additions & 0 deletions test/unittest/writertest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,31 @@ TEST(Writer, RawValue) {
EXPECT_STREQ("{\"a\":1,\"raw\":[\"Hello\\nWorld\", 123.456]}", buffer.GetString());
}

TEST(Writer, RawNumber_NoQuotes) {
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
writer.StartArray();
const char number[] = "3.14159";
writer.RawNumber(number, 4);
writer.RawNumber(number, static_cast<SizeType>(strlen(number)));
writer.EndArray();
EXPECT_TRUE(writer.IsComplete());
EXPECT_STREQ("[3.14,3.14159]", buffer.GetString());
}

TEST(Writer, RawNumber_InObject) {
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
writer.StartObject();
writer.Key("value");
writer.RawNumber("42", 2);
writer.Key("name");
writer.String("test");
writer.EndObject();
EXPECT_TRUE(writer.IsComplete());
EXPECT_STREQ("{\"value\":42,\"name\":\"test\"}", buffer.GetString());
}

TEST(Write, RawValue_Issue1152) {
{
GenericStringBuffer<UTF32<> > sb;
Expand Down