Merge pull request #232 from nlohmann/issue228
make serialization locale-independent (fixes #228)
diff --git a/src/json.hpp b/src/json.hpp
index 0594b38..8f671fb 100644
--- a/src/json.hpp
+++ b/src/json.hpp
@@ -88,6 +88,19 @@
static constexpr bool value = sizeof(test<T>(0)) == 1;
};
+/*!
+@brief helper class to create locales with decimal point
+@sa https://github.com/nlohmann/json/issues/51#issuecomment-86869315
+*/
+class DecimalSeparator : public std::numpunct<char>
+{
+ protected:
+ char do_decimal_point() const
+ {
+ return '.';
+ }
+};
+
}
/*!
@@ -6114,24 +6127,26 @@
case value_t::number_float:
{
- // buffer size: precision (2^8-1 = 255) + other ('-.e-xxx' = 7) + null (1)
- char buf[263];
- int len;
-
// check if number was parsed from a string
if (m_type.bits.parsed)
{
// check if parsed number had an exponent given
if (m_type.bits.has_exp)
{
+ // buffer size: precision (2^8-1 = 255) + other ('-.e-xxx' = 7) + null (1)
+ char buf[263];
+ int len;
+
// handle capitalization of the exponent
if (m_type.bits.exp_cap)
{
- len = snprintf(buf, sizeof(buf), "%.*E", m_type.bits.precision, m_value.number_float) + 1;
+ len = snprintf(buf, sizeof(buf), "%.*E",
+ m_type.bits.precision, m_value.number_float) + 1;
}
else
{
- len = snprintf(buf, sizeof(buf), "%.*e", m_type.bits.precision, m_value.number_float) + 1;
+ len = snprintf(buf, sizeof(buf), "%.*e",
+ m_type.bits.precision, m_value.number_float) + 1;
}
// remove '+' sign from the exponent if necessary
@@ -6152,40 +6167,40 @@
}
}
}
+
+ o << buf;
}
else
{
// no exponent - output as a decimal
- snprintf(buf, sizeof(buf), "%.*f",
- m_type.bits.precision, m_value.number_float);
+ std::stringstream ss;
+ ss.imbue(std::locale(std::locale(), new DecimalSeparator)); // fix locale problems
+ ss << std::setprecision(m_type.bits.precision)
+ << std::fixed << m_value.number_float;
+ o << ss.str();
}
}
- else if (m_value.number_float == 0)
- {
- // special case for zero to get "0.0"/"-0.0"
- if (std::signbit(m_value.number_float))
- {
- o << "-0.0";
- }
- else
- {
- o << "0.0";
- }
- return;
- }
else
{
- // Otherwise 6, 15 or 16 digits of precision allows
- // round-trip IEEE 754 string->float->string,
- // string->double->string or string->long double->string;
- // to be safe, we read this value from
- // std::numeric_limits<number_float_t>::digits10
- snprintf(buf, sizeof(buf), "%.*g",
- std::numeric_limits<double>::digits10,
- m_value.number_float);
+ if (m_value.number_float == 0)
+ {
+ // special case for zero to get "0.0"/"-0.0"
+ o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0");
+ }
+ else
+ {
+ // Otherwise 6, 15 or 16 digits of precision allows
+ // round-trip IEEE 754 string->float->string,
+ // string->double->string or string->long double->string;
+ // to be safe, we read this value from
+ // std::numeric_limits<number_float_t>::digits10
+ std::stringstream ss;
+ ss.imbue(std::locale(std::locale(), new DecimalSeparator)); // fix locale problems
+ ss << std::setprecision(std::numeric_limits<double>::digits10)
+ << m_value.number_float;
+ o << ss.str();
+ }
}
-
- o << buf;
return;
}
diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c
index f4ddacf..ebf83d8 100644
--- a/src/json.hpp.re2c
+++ b/src/json.hpp.re2c
@@ -88,6 +88,19 @@
static constexpr bool value = sizeof(test<T>(0)) == 1;
};
+/*!
+@brief helper class to create locales with decimal point
+@sa https://github.com/nlohmann/json/issues/51#issuecomment-86869315
+*/
+class DecimalSeparator : public std::numpunct<char>
+{
+ protected:
+ char do_decimal_point() const
+ {
+ return '.';
+ }
+};
+
}
/*!
@@ -6114,24 +6127,26 @@
case value_t::number_float:
{
- // buffer size: precision (2^8-1 = 255) + other ('-.e-xxx' = 7) + null (1)
- char buf[263];
- int len;
-
// check if number was parsed from a string
if (m_type.bits.parsed)
{
// check if parsed number had an exponent given
if (m_type.bits.has_exp)
{
+ // buffer size: precision (2^8-1 = 255) + other ('-.e-xxx' = 7) + null (1)
+ char buf[263];
+ int len;
+
// handle capitalization of the exponent
if (m_type.bits.exp_cap)
{
- len = snprintf(buf, sizeof(buf), "%.*E", m_type.bits.precision, m_value.number_float) + 1;
+ len = snprintf(buf, sizeof(buf), "%.*E",
+ m_type.bits.precision, m_value.number_float) + 1;
}
else
{
- len = snprintf(buf, sizeof(buf), "%.*e", m_type.bits.precision, m_value.number_float) + 1;
+ len = snprintf(buf, sizeof(buf), "%.*e",
+ m_type.bits.precision, m_value.number_float) + 1;
}
// remove '+' sign from the exponent if necessary
@@ -6152,40 +6167,40 @@
}
}
}
+
+ o << buf;
}
else
{
// no exponent - output as a decimal
- snprintf(buf, sizeof(buf), "%.*f",
- m_type.bits.precision, m_value.number_float);
+ std::stringstream ss;
+ ss.imbue(std::locale(std::locale(), new DecimalSeparator)); // fix locale problems
+ ss << std::setprecision(m_type.bits.precision)
+ << std::fixed << m_value.number_float;
+ o << ss.str();
}
}
- else if (m_value.number_float == 0)
- {
- // special case for zero to get "0.0"/"-0.0"
- if (std::signbit(m_value.number_float))
- {
- o << "-0.0";
- }
- else
- {
- o << "0.0";
- }
- return;
- }
else
{
- // Otherwise 6, 15 or 16 digits of precision allows
- // round-trip IEEE 754 string->float->string,
- // string->double->string or string->long double->string;
- // to be safe, we read this value from
- // std::numeric_limits<number_float_t>::digits10
- snprintf(buf, sizeof(buf), "%.*g",
- std::numeric_limits<double>::digits10,
- m_value.number_float);
+ if (m_value.number_float == 0)
+ {
+ // special case for zero to get "0.0"/"-0.0"
+ o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0");
+ }
+ else
+ {
+ // Otherwise 6, 15 or 16 digits of precision allows
+ // round-trip IEEE 754 string->float->string,
+ // string->double->string or string->long double->string;
+ // to be safe, we read this value from
+ // std::numeric_limits<number_float_t>::digits10
+ std::stringstream ss;
+ ss.imbue(std::locale(std::locale(), new DecimalSeparator)); // fix locale problems
+ ss << std::setprecision(std::numeric_limits<double>::digits10)
+ << m_value.number_float;
+ o << ss.str();
+ }
}
-
- o << buf;
return;
}
diff --git a/test/unit.cpp b/test/unit.cpp
index b440a28..ab96364 100644
--- a/test/unit.cpp
+++ b/test/unit.cpp
@@ -12369,5 +12369,43 @@
j_long_double = 1.23e45L;
CHECK(j_long_double.get<long double>() == 1.23e45L);
}
+
+ SECTION("issue #228 - double values are serialized with commas as decimal points")
+ {
+ json j1a = 23.42;
+ json j1b = json::parse("23.42");
+
+ json j2a = 2342e-2;
+ //issue #230
+ //json j2b = json::parse("2342e-2");
+
+ json j3a = 10E3;
+ json j3b = json::parse("10E3");
+ json j3c = json::parse("10e3");
+
+ // class to create a locale that would use a comma for decimals
+ class CommaDecimalSeparator : public std::numpunct<char>
+ {
+ protected:
+ char do_decimal_point() const
+ {
+ return ',';
+ }
+ };
+
+ // change locale to mess with decimal points
+ std::locale::global(std::locale(std::locale(), new CommaDecimalSeparator));
+
+ CHECK(j1a.dump() == "23.42");
+ CHECK(j1b.dump() == "23.42");
+
+ CHECK(j2a.dump() == "23.42");
+ //issue #230
+ //CHECK(j2b.dump() == "23.42");
+
+ CHECK(j3a.dump() == "10000");
+ CHECK(j3b.dump() == "1E04");
+ CHECK(j3c.dump() == "1e04");
+ }
}