From 0dbbe8568985ac7966c4354dd89fe0a1771f1dc4 Mon Sep 17 00:00:00 2001 From: Bartek Ryniec Date: Fri, 4 Dec 2015 13:07:15 -0800 Subject: [PATCH] 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 --- folly/json.cpp | 23 ++++++++++++++++++----- folly/json.h | 5 +++++ folly/test/JsonTest.cpp | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/folly/json.cpp b/folly/json.cpp index 4c856598..63fa2ba0 100644 --- a/folly/json.cpp +++ b/folly/json.cpp @@ -470,8 +470,10 @@ dynamic parseArray(Input& in) { dynamic parseNumber(Input& in) { bool const negative = (*in == '-'); - if (negative) { - if (in.consume("-Infinity")) { + if (negative && in.consume("-Infinity")) { + if (in.getOpts().parse_numbers_as_strings) { + return "-Infinity"; + } else { return -std::numeric_limits::infinity(); } } @@ -487,6 +489,11 @@ dynamic parseNumber(Input& in) { constexpr const char* minInt = "9223372036854775808"; constexpr auto maxIntLen = __builtin_strlen(maxInt); + + if (*in != '.' && !wasE && in.getOpts().parse_numbers_as_strings) { + return integral; + } + if (*in != '.' && !wasE) { if (LIKELY(!in.getOpts().double_fallback || integral.size() < maxIntLen) || (integral.size() == maxIntLen && @@ -511,7 +518,9 @@ dynamic parseNumber(Input& in) { end = expPart.end(); } auto fullNum = range(integral.begin(), end); - + if (in.getOpts().parse_numbers_as_strings) { + return fullNum; + } auto val = to(fullNum); return val; } @@ -626,8 +635,12 @@ dynamic parseValue(Input& in) { in.consume("true") ? true : in.consume("false") ? false : in.consume("null") ? nullptr : - in.consume("Infinity") ? std::numeric_limits::infinity() : - in.consume("NaN") ? std::numeric_limits::quiet_NaN() : + in.consume("Infinity") ? + (in.getOpts().parse_numbers_as_strings ? (dynamic)"Infinity" : + (dynamic)std::numeric_limits::infinity()) : + in.consume("NaN") ? + (in.getOpts().parse_numbers_as_strings ? (dynamic)"NaN" : + (dynamic)std::numeric_limits::quiet_NaN()) : in.error("expected json value"); } diff --git a/folly/json.h b/folly/json.h index 8352abbf..ead3afd0 100644 --- a/folly/json.h +++ b/folly/json.h @@ -65,6 +65,7 @@ namespace json { , double_mode(double_conversion::DoubleToStringConverter::SHORTEST) , double_num_digits(0) // ignored when mode is SHORTEST , double_fallback(false) + , parse_numbers_as_strings(false) {} // If true, keys in an object can be non-strings. (In strict @@ -109,6 +110,10 @@ namespace json { // 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. 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; }; /* diff --git a/folly/test/JsonTest.cpp b/folly/test/JsonTest.cpp index 02976cf3..d514e8f3 100644 --- a/folly/test/JsonTest.cpp +++ b/folly/test/JsonTest.cpp @@ -401,6 +401,39 @@ TEST(Json, ParseDoubleFallback) { 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) { folly::json::serialization_opts opts_on, opts_off; opts_on.sort_keys = true; -- 2.34.1