Commit 0dbbe856 authored by Bartek Ryniec's avatar Bartek Ryniec Committed by facebook-github-bot-4

json: Add option to parse numbers as strings

Summary: Adding a flag in `folly::json::serialization_opts` that allows you to request all numbers to be parsed into strings. This saves you from exceptions being thrown by `folly::parseJson` if it encounters numbers that wouldn't fit in int64.

Reviewed By: yfeldblum

Differential Revision: D2713039

fb-gh-sync-id: 4f3d0d20f3012efd6229adf895200a3901bb4493
parent d662c1b7
...@@ -470,8 +470,10 @@ dynamic parseArray(Input& in) { ...@@ -470,8 +470,10 @@ dynamic parseArray(Input& in) {
dynamic parseNumber(Input& in) { dynamic parseNumber(Input& in) {
bool const negative = (*in == '-'); bool const negative = (*in == '-');
if (negative) { if (negative && in.consume("-Infinity")) {
if (in.consume("-Infinity")) { if (in.getOpts().parse_numbers_as_strings) {
return "-Infinity";
} else {
return -std::numeric_limits<double>::infinity(); return -std::numeric_limits<double>::infinity();
} }
} }
...@@ -487,6 +489,11 @@ dynamic parseNumber(Input& in) { ...@@ -487,6 +489,11 @@ dynamic parseNumber(Input& in) {
constexpr const char* minInt = "9223372036854775808"; constexpr const char* minInt = "9223372036854775808";
constexpr auto maxIntLen = __builtin_strlen(maxInt); constexpr auto maxIntLen = __builtin_strlen(maxInt);
if (*in != '.' && !wasE && in.getOpts().parse_numbers_as_strings) {
return integral;
}
if (*in != '.' && !wasE) { if (*in != '.' && !wasE) {
if (LIKELY(!in.getOpts().double_fallback || integral.size() < maxIntLen) || if (LIKELY(!in.getOpts().double_fallback || integral.size() < maxIntLen) ||
(integral.size() == maxIntLen && (integral.size() == maxIntLen &&
...@@ -511,7 +518,9 @@ dynamic parseNumber(Input& in) { ...@@ -511,7 +518,9 @@ dynamic parseNumber(Input& in) {
end = expPart.end(); end = expPart.end();
} }
auto fullNum = range(integral.begin(), end); auto fullNum = range(integral.begin(), end);
if (in.getOpts().parse_numbers_as_strings) {
return fullNum;
}
auto val = to<double>(fullNum); auto val = to<double>(fullNum);
return val; return val;
} }
...@@ -626,8 +635,12 @@ dynamic parseValue(Input& in) { ...@@ -626,8 +635,12 @@ dynamic parseValue(Input& in) {
in.consume("true") ? true : in.consume("true") ? true :
in.consume("false") ? false : in.consume("false") ? false :
in.consume("null") ? nullptr : in.consume("null") ? nullptr :
in.consume("Infinity") ? std::numeric_limits<double>::infinity() : in.consume("Infinity") ?
in.consume("NaN") ? std::numeric_limits<double>::quiet_NaN() : (in.getOpts().parse_numbers_as_strings ? (dynamic)"Infinity" :
(dynamic)std::numeric_limits<double>::infinity()) :
in.consume("NaN") ?
(in.getOpts().parse_numbers_as_strings ? (dynamic)"NaN" :
(dynamic)std::numeric_limits<double>::quiet_NaN()) :
in.error("expected json value"); in.error("expected json value");
} }
......
...@@ -65,6 +65,7 @@ namespace json { ...@@ -65,6 +65,7 @@ namespace json {
, double_mode(double_conversion::DoubleToStringConverter::SHORTEST) , double_mode(double_conversion::DoubleToStringConverter::SHORTEST)
, double_num_digits(0) // ignored when mode is SHORTEST , double_num_digits(0) // ignored when mode is SHORTEST
, double_fallback(false) , double_fallback(false)
, parse_numbers_as_strings(false)
{} {}
// If true, keys in an object can be non-strings. (In strict // If true, keys in an object can be non-strings. (In strict
...@@ -109,6 +110,10 @@ namespace json { ...@@ -109,6 +110,10 @@ namespace json {
// Fallback to double when a value that looks like integer is too big to // Fallback to double when a value that looks like integer is too big to
// fit in an int64_t. Can result in loss a of precision. // fit in an int64_t. Can result in loss a of precision.
bool double_fallback; bool double_fallback;
// Do not parse numbers. Instead, store them as strings and leave the
// conversion up to the user.
bool parse_numbers_as_strings;
}; };
/* /*
......
...@@ -401,6 +401,39 @@ TEST(Json, ParseDoubleFallback) { ...@@ -401,6 +401,39 @@ TEST(Json, ParseDoubleFallback) {
opts).items().begin()->second.asDouble()); opts).items().begin()->second.asDouble());
} }
TEST(Json, ParseNumbersAsStrings) {
folly::json::serialization_opts opts;
opts.parse_numbers_as_strings = true;
auto parse = [&](folly::fbstring number) {
return parseJson(number, opts).asString();
};
EXPECT_EQ("0", parse("0"));
EXPECT_EQ("1234", parse("1234"));
EXPECT_EQ("3.00", parse("3.00"));
EXPECT_EQ("3.14", parse("3.14"));
EXPECT_EQ("0.1234", parse("0.1234"));
EXPECT_EQ("0.0", parse("0.0"));
EXPECT_EQ("46845131213548676854213265486468451312135486768542132",
parse("46845131213548676854213265486468451312135486768542132"));
EXPECT_EQ("-468451312135486768542132654864684513121354867685.5e4",
parse("-468451312135486768542132654864684513121354867685.5e4"));
EXPECT_EQ("6.62607004e-34", parse("6.62607004e-34"));
EXPECT_EQ("6.62607004E+34", parse("6.62607004E+34"));
EXPECT_EQ("Infinity", parse("Infinity"));
EXPECT_EQ("-Infinity", parse("-Infinity"));
EXPECT_EQ("NaN", parse("NaN"));
EXPECT_THROW(parse("ThisIsWrong"), std::runtime_error);
EXPECT_THROW(parse("34-2"), std::runtime_error);
EXPECT_THROW(parse(""), std::runtime_error);
EXPECT_THROW(parse("-"), std::runtime_error);
EXPECT_THROW(parse("34-e2"), std::runtime_error);
EXPECT_THROW(parse("34e2.4"), std::runtime_error);
EXPECT_THROW(parse("infinity"), std::runtime_error);
EXPECT_THROW(parse("nan"), std::runtime_error);
}
TEST(Json, SortKeys) { TEST(Json, SortKeys) {
folly::json::serialization_opts opts_on, opts_off; folly::json::serialization_opts opts_on, opts_off;
opts_on.sort_keys = true; opts_on.sort_keys = true;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment