Skip to content

Commit 0052c4f

Browse files
authored
Fix flgBaseWriter to use kParseNumbersAsStringsFlag instead of kParseFullPrecisionFlag (#241)
Fixes #39 and Closes #39 Fixes #119 and Closes #119 Fixes #196 and Closes #196
1 parent 057a028 commit 0052c4f

File tree

4 files changed

+114
-63
lines changed

4 files changed

+114
-63
lines changed

src/NppJsonViewer/JsonHandler.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,14 @@ struct Result
2626
using LE = rj::LineEndingOption;
2727
using LF = rj::PrettyFormatOptions;
2828

29+
// Parse flags for all JSON operations. Historically flgBaseWriter used kParseFullPrecisionFlag
30+
// instead of kParseNumbersAsStringsFlag because upstream RapidJSON's RawNumber() would quote
31+
// numbers as strings. Our fork fixes that (RawNumber now writes via WriteRawValue without quotes),
32+
// so both flag sets are now identical. They are kept separate to allow independent tuning of the
33+
// DOM-based path (flgBaseReader: sort-by-key) vs the SAX streaming path (flgBaseWriter: format/compress)
34+
// if needed in the future.
2935
constexpr auto flgBaseReader = rj::kParseEscapedApostropheFlag | rj::kParseNanAndInfFlag | rj::kParseNumbersAsStringsFlag;
30-
constexpr auto flgBaseWriter = rj::kParseEscapedApostropheFlag | rj::kParseNanAndInfFlag | rj::kParseFullPrecisionFlag;
36+
constexpr auto flgBaseWriter = rj::kParseEscapedApostropheFlag | rj::kParseNanAndInfFlag | rj::kParseNumbersAsStringsFlag;
3137

3238
class JsonHandler
3339
{

tests/UnitTest/JsonCompressTest.cpp

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ namespace JsonCompress
140140
std::unordered_map<std::string, std::string> testData {
141141
{"{\r\n \"NaN\": NaN\r\n}", R"({"NaN":NaN})"},
142142
{"{\r\n \"Mixed\": [\r\n null,\r\n null,\r\n \"power\"\r\n ]\r\n}", R"({"Mixed":[null,null,"power"]})"},
143-
{"{\n \"Inf\": [\n -Infinity,\n Infinity,\n -Inf,\n Inf\n ]\n}", R"({"Inf":[-Infinity,Infinity,-Infinity,Infinity]})"},
143+
{"{\n \"Inf\": [\n -Infinity,\n Infinity,\n -Inf,\n Inf\n ]\n}", R"({"Inf":[-Infinity,Infinity,-Inf,Inf]})"},
144144
};
145145

146146
for (const auto& [input, output] : testData)
@@ -176,17 +176,17 @@ namespace JsonCompress
176176
TEST_F(JsonCompressTest, CompressJson_numbers)
177177
{
178178
std::unordered_map<std::string, std::string> testData {
179-
{R"({"num": 12.148681171238422})", "12.148681171238422"}, // All good
180-
{R"({"num": 42.835353759876654})", "42.835353759876654"}, // All good
181-
{R"({"num": 5.107091491635510056019771245})", "5.10709149163551"}, // Fine: Rounded up
182-
{R"({"num": 100000000302052988.0})", "100000000302052990.0"}, // Fine: Rounded up
183-
{R"({"num": 12.148681171238427111})", "12.148681171238428"}, // Fine: Rounded down
184-
{R"({"num": 42.8353537598766541666})", "42.835353759876654"}, // Fine: Rounded up
185-
{R"({"num": -1722.1864265316147})", "-1722.1864265316146"}, // This is interesting. Why last digit changed.
186-
{R"({"num": -1722.1864265316148})", "-1722.1864265316149"}, // This is interesting. Why last digit changed.
187-
{R"({"num": -172345.18642653167979})", "-172345.18642653167"}, // This is interesting. Why not rounded up.
188-
{R"({"num": 1.234e5})", "123400.0"}, // Don't know how to fix.
189-
{R"({"num": 0.0000001000})", "1e-7"}, // Don't know how to fix.
179+
{R"({"num": 12.148681171238422})", "12.148681171238422"},
180+
{R"({"num": 42.835353759876654})", "42.835353759876654"},
181+
{R"({"num": 5.107091491635510056019771245})", "5.107091491635510056019771245"},
182+
{R"({"num": 100000000302052988.0})", "100000000302052988.0"},
183+
{R"({"num": 12.148681171238427111})", "12.148681171238427111"},
184+
{R"({"num": 42.8353537598766541666})", "42.8353537598766541666"},
185+
{R"({"num": -1722.1864265316147})", "-1722.1864265316147"},
186+
{R"({"num": -1722.1864265316148})", "-1722.1864265316148"},
187+
{R"({"num": -172345.18642653167979})", "-172345.18642653167979"},
188+
{R"({"num": 1.234e5})", "1.234e5"},
189+
{R"({"num": 0.0000001000})", "0.0000001000"},
190190
};
191191

192192
for (const auto& [input, output] : testData)

tests/UnitTest/JsonFormatTest.cpp

Lines changed: 45 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ namespace JsonFormat
170170
{R"({"NaN":NaN})", "{\r\n \"NaN\": NaN\r\n}"},
171171
{R"({"Mixed":[null,null,"power"]})", "{\r\n \"Mixed\": [\r\n null,\r\n null,\r\n \"power\"\r\n ]\r\n}"},
172172
{R"({"Inf":[-Infinity, Infinity, -Inf, Inf]})",
173-
"{\r\n \"Inf\": [\r\n -Infinity,\r\n Infinity,\r\n -Infinity,\r\n Infinity\r\n ]\r\n}"},
173+
"{\r\n \"Inf\": [\r\n -Infinity,\r\n Infinity,\r\n -Inf,\r\n Inf\r\n ]\r\n}"},
174174
};
175175

176176
LE lineEndingOption = rj::kCrLf; // Windows line ending
@@ -196,7 +196,7 @@ namespace JsonFormat
196196
std::unordered_map<std::string, std::string> testData {
197197
{R"({"NaN":NaN})", "{\r\n \"NaN\": NaN\r\n}"},
198198
{R"({"Mixed":[null,null,"power"]})", "{\r\n \"Mixed\": [null, null, \"power\"]\r\n}"},
199-
{R"({"Inf":[-Infinity, Infinity, -Inf, Inf]})", "{\r\n \"Inf\": [-Infinity, Infinity, -Infinity, Infinity]\r\n}"},
199+
{R"({"Inf":[-Infinity, Infinity, -Inf, Inf]})", "{\r\n \"Inf\": [-Infinity, Infinity, -Inf, Inf]\r\n}"},
200200
};
201201

202202
LE lineEndingOption = rj::kCrLf; // Windows line ending
@@ -242,50 +242,51 @@ namespace JsonFormat
242242
char indentChar = ' '; // space indented
243243
unsigned indentLen = 4; // No indentation
244244

245+
// With kParseNumbersAsStringsFlag, numbers are preserved as exact original text
245246
const std::unordered_map<std::string, std::string> testData {
246-
{R"({"num": 12.148681171238422})", "12.148681171238422"}, // All good
247-
{R"({"num": 42.835353759876654})", "42.835353759876654"}, // All good
248-
{R"({"num": 5.107091491635510056019771245})", "5.10709149163551"}, // Fine: Rounded up
249-
{R"({"num": 100000000302052988.0})", "100000000302052990.0"}, // Fine: Rounded up
250-
{R"({"num": 42.8353537598766541666})", "42.835353759876654"}, // Fine: Rounded up
251-
{R"({"num": 0.184467440737095516159})", "0.1844674407370955"}, // Fine: Rounded down
252-
{R"({"num": 12.148681171238427111})", "12.148681171238428"}, // Rounded up. But why?
253-
{R"({"num": -1722.1864265316147})", "-1722.1864265316146"}, // This is interesting. Why last digit changed.
254-
{R"({"num": -1722.1864265316148})", "-1722.1864265316149"}, // This is interesting. Why last digit changed.
255-
{R"({"num": -172345.18642653167979})", "-172345.18642653167"}, // This is interesting. Why not rounded up.
256-
{R"({"num": 1.234e5})", "123400.0"}, // -------------------------------------
257-
{R"({"num": 0.0000001000})", "1e-7"}, // Don't know how to fix all the below.
258-
{R"({"num":-5.0975200963490517E-33})", "-5.097520096349052e-33"}, // -------------------------------------
259-
{R"({"num":-6.3809366960906694E-48})", "-6.38093669609067e-48"},
260-
{R"({"num":-7.4034655373995265E-78})", "-7.403465537399527e-78"},
261-
{R"({"num":-7.7377839245507245E-78})", "-7.737783924550725e-78"},
262-
{R"({"num":1.2859736125485259E-22, })", "1.285973612548526e-22"},
263-
{R"({"num":-3.5403485244736897E-88})", "-3.54034852447369e-88"},
247+
{R"({"num": 12.148681171238422})", "12.148681171238422"},
248+
{R"({"num": 42.835353759876654})", "42.835353759876654"},
249+
{R"({"num": 5.107091491635510056019771245})", "5.107091491635510056019771245"},
250+
{R"({"num": 100000000302052988.0})", "100000000302052988.0"},
251+
{R"({"num": 42.8353537598766541666})", "42.8353537598766541666"},
252+
{R"({"num": 0.184467440737095516159})", "0.184467440737095516159"},
253+
{R"({"num": 12.148681171238427111})", "12.148681171238427111"},
254+
{R"({"num": -1722.1864265316147})", "-1722.1864265316147"},
255+
{R"({"num": -1722.1864265316148})", "-1722.1864265316148"},
256+
{R"({"num": -172345.18642653167979})", "-172345.18642653167979"},
257+
{R"({"num": 1.234e5})", "1.234e5"},
258+
{R"({"num": 0.0000001000})", "0.0000001000"},
259+
{R"({"num":-5.0975200963490517E-33})", "-5.0975200963490517E-33"},
260+
{R"({"num":-6.3809366960906694E-48})", "-6.3809366960906694E-48"},
261+
{R"({"num":-7.4034655373995265E-78})", "-7.4034655373995265E-78"},
262+
{R"({"num":-7.7377839245507245E-78})", "-7.7377839245507245E-78"},
263+
{R"({"num":1.2859736125485259E-22, })", "1.2859736125485259E-22"},
264+
{R"({"num":-3.5403485244736897E-88})", "-3.5403485244736897E-88"},
264265
{R"({"num":-6553693.3617752995})", "-6553693.3617752995"},
265-
{R"({"num":-6.8141086401061905E-44})", "-6.814108640106191e-44"},
266-
{R"({"num":5.5843284390697506E-71})", "5.584328439069751e-71"},
267-
{R"({"num":-4.3180528943019356E-77})", "-4.318052894301936e-77"},
268-
{R"({"num":-5.8231387142089446E-34})", "-5.823138714208945e-34"},
269-
{R"({"num":4.9686769279508796E-34})", "4.96867692795088e-34"},
270-
{R"({"num":-1.7271559814272259E-77})", "-1.727155981427226e-77"},
271-
{R"({"num":-4.2596088775464085E-81})", "-4.259608877546409e-81"},
272-
{R"({"num":3.7049941506206046E-71})", "3.704994150620605e-71"},
273-
{R"({"num":3.9742364030067576E-86})", "3.974236403006758e-86"},
274-
{R"({"num":3.1628542535929037E-89})", "3.162854253592904e-89"},
275-
{R"({"num":-268.52143589416397})", "-268.521435894164"},
276-
{R"({"num":-2.2795405986944148E-93})", "-2.279540598694415e-93"},
277-
{R"({"num":-7.3921473885461993E-95})", "-7.3921473885462e-95"},
278-
{R"({"num":-5.1970028999668327E-45})", "-5.197002899966833e-45"},
279-
{R"({"num":3.5937267475433058E-09})", "3.593726747543306e-9"},
280-
{R"({"num":7.2781313854297585E-75})", "7.278131385429759e-75"},
281-
{R"({"num":2.1823857766614118E-78})", "2.182385776661412e-78"},
282-
{R"({"num":8.9080568887277594E-11})", "8.90805688872776e-11"},
283-
{R"({"num":-898453.63759556494})", "-898453.637595565"},
284-
{R"({"num":-6.5029793100887976E-55})", "-6.502979310088798e-55"},
285-
{R"({"num":15650583.101212589})", "15650583.10121259"},
286-
{R"({"num":-7.7306921203660293E-55})", "-7.73069212036603e-55"},
287-
{R"({"num":4.3436060211811726E-74})", "4.343606021181173e-74"},
288-
{R"({"num":-7.1399486851522386E-90})", "-7.139948685152239e-9"},
266+
{R"({"num":-6.8141086401061905E-44})", "-6.8141086401061905E-44"},
267+
{R"({"num":5.5843284390697506E-71})", "5.5843284390697506E-71"},
268+
{R"({"num":-4.3180528943019356E-77})", "-4.3180528943019356E-77"},
269+
{R"({"num":-5.8231387142089446E-34})", "-5.8231387142089446E-34"},
270+
{R"({"num":4.9686769279508796E-34})", "4.9686769279508796E-34"},
271+
{R"({"num":-1.7271559814272259E-77})", "-1.7271559814272259E-77"},
272+
{R"({"num":-4.2596088775464085E-81})", "-4.2596088775464085E-81"},
273+
{R"({"num":3.7049941506206046E-71})", "3.7049941506206046E-71"},
274+
{R"({"num":3.9742364030067576E-86})", "3.9742364030067576E-86"},
275+
{R"({"num":3.1628542535929037E-89})", "3.1628542535929037E-89"},
276+
{R"({"num":-268.52143589416397})", "-268.52143589416397"},
277+
{R"({"num":-2.2795405986944148E-93})", "-2.2795405986944148E-93"},
278+
{R"({"num":-7.3921473885461993E-95})", "-7.3921473885461993E-95"},
279+
{R"({"num":-5.1970028999668327E-45})", "-5.1970028999668327E-45"},
280+
{R"({"num":3.5937267475433058E-09})", "3.5937267475433058E-09"},
281+
{R"({"num":7.2781313854297585E-75})", "7.2781313854297585E-75"},
282+
{R"({"num":2.1823857766614118E-78})", "2.1823857766614118E-78"},
283+
{R"({"num":8.9080568887277594E-11})", "8.9080568887277594E-11"},
284+
{R"({"num":-898453.63759556494})", "-898453.63759556494"},
285+
{R"({"num":-6.5029793100887976E-55})", "-6.5029793100887976E-55"},
286+
{R"({"num":15650583.101212589})", "15650583.101212589"},
287+
{R"({"num":-7.7306921203660293E-55})", "-7.7306921203660293E-55"},
288+
{R"({"num":4.3436060211811726E-74})", "4.3436060211811726E-74"},
289+
{R"({"num":-7.1399486851522386E-90})", "-7.1399486851522386E-90"},
289290
};
290291

291292
for (const auto& [input, output] : testData)

tests/UnitTest/JsonSortByKeyTest.cpp

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,58 @@ namespace JsonSortByKey
136136

137137
TEST_F(JsonSortByKeyTest, TestSortJsonByKey_NumberPrecision_Success)
138138
{
139-
// Verify numbers survive the sort-by-key roundtrip without gaining quotes.
140-
// Note: SortJsonByKey re-parses via FormatJson (which uses kParseFullPrecisionFlag),
141-
// so very high precision may be normalized (e.g. 0.00000000000001 -> 1e-14).
142-
// But they must NOT become strings (e.g. "3.14" or "12345678901234567890").
143-
std::string inputJson = R"({"z": "last", "pi": 3.141592653589793, "tiny": 1e-14, "tiny2": 0.00000000000001, "big": 12345678901234567890})";
139+
// Verify numbers survive the sort-by-key roundtrip without gaining quotes
140+
// and with full precision preserved (synced with RapidJSON's Document.RawNumberRoundtrip_Precision test).
141+
std::string inputJson = R"({"z": "last", "pi": 3.141592653589793238, "tiny": 1e-14, "tiny2": 0.00000000000001, "big": 12345678901234567890})";
144142
auto result = m_jsonHandler.SortJsonByKey(inputJson, {}, {}, ' ', 2);
145143

146144
ASSERT_TRUE(result.success);
147-
ASSERT_EQ(result.response, "{\n \"big\": 12345678901234567890,\n \"pi\": 3.141592653589793,\n \"tiny\": 1e-14,\n \"tiny2\": 1e-14,\n \"z\": \"last\"\n}");
145+
ASSERT_EQ(result.response, "{\n \"big\": 12345678901234567890,\n \"pi\": 3.141592653589793238,\n \"tiny\": 1e-14,\n \"tiny2\": 0.00000000000001,\n \"z\": \"last\"\n}");
146+
}
147+
148+
TEST_F(JsonSortByKeyTest, TestSortJsonByKey_NumberFormats_Success)
149+
{
150+
// Verify all valid JSON number notations survive sort-by-key roundtrip exactly
151+
// (synced with RapidJSON's Document.RawNumberRoundtrip_NumberFormats test).
152+
std::string inputJson =
153+
R"({"neg_frac": -0.5,)"
154+
R"( "int": 42,)"
155+
R"( "neg": -17,)"
156+
R"( "zero": 0,)"
157+
R"( "frac": 3.14,)"
158+
R"( "exp_lower": 1e10,)"
159+
R"( "exp_upper": 1E10,)"
160+
R"( "exp_plus": 1e+10,)"
161+
R"( "exp_neg": 1e-10,)"
162+
R"( "exp_frac": 1.5e3,)"
163+
R"( "huge_int": 99999999999999999999,)"
164+
R"( "huge_neg": -99999999999999999999,)"
165+
R"( "tiny_exp": 1e-308,)"
166+
R"( "tiny_frac": 0.000000000000000001,)"
167+
R"( "leading_zero_frac": 0.123})";
168+
auto result = m_jsonHandler.SortJsonByKey(inputJson, {}, {}, ' ', 2);
169+
170+
ASSERT_TRUE(result.success);
171+
172+
// Verify exact output with alphabetically sorted keys and preserved number text
173+
std::string expected =
174+
"{\n"
175+
" \"exp_frac\": 1.5e3,\n"
176+
" \"exp_lower\": 1e10,\n"
177+
" \"exp_neg\": 1e-10,\n"
178+
" \"exp_plus\": 1e+10,\n"
179+
" \"exp_upper\": 1E10,\n"
180+
" \"frac\": 3.14,\n"
181+
" \"huge_int\": 99999999999999999999,\n"
182+
" \"huge_neg\": -99999999999999999999,\n"
183+
" \"int\": 42,\n"
184+
" \"leading_zero_frac\": 0.123,\n"
185+
" \"neg\": -17,\n"
186+
" \"neg_frac\": -0.5,\n"
187+
" \"tiny_exp\": 1e-308,\n"
188+
" \"tiny_frac\": 0.000000000000000001,\n"
189+
" \"zero\": 0\n"
190+
"}";
191+
ASSERT_EQ(result.response, expected);
148192
}
149193
} // namespace JsonSortByKey

0 commit comments

Comments
 (0)