Skip to content

Commit

Permalink
Merge pull request #232 from nlohmann/issue228
Browse files Browse the repository at this point in the history
make serialization locale-independent (fixes #228)
  • Loading branch information
nlohmann committed Apr 7, 2016
2 parents 9c233be + a744c62 commit 170b70f
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 54 deletions.
69 changes: 42 additions & 27 deletions src/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ struct has_mapped_type
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 '.';
}
};

}

/*!
Expand Down Expand Up @@ -6114,24 +6127,26 @@ class basic_json

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
Expand All @@ -6152,40 +6167,40 @@ class basic_json
}
}
}

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)
else
{
// special case for zero to get "0.0"/"-0.0"
if (std::signbit(m_value.number_float))
if (m_value.number_float == 0)
{
o << "-0.0";
// special case for zero to get "0.0"/"-0.0"
o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0");
}
else
{
o << "0.0";
// 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();
}
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);
}

o << buf;
return;
}

Expand Down
69 changes: 42 additions & 27 deletions src/json.hpp.re2c
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ struct has_mapped_type
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 '.';
}
};

}

/*!
Expand Down Expand Up @@ -6114,24 +6127,26 @@ class basic_json

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
Expand All @@ -6152,40 +6167,40 @@ class basic_json
}
}
}

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)
else
{
// special case for zero to get "0.0"/"-0.0"
if (std::signbit(m_value.number_float))
if (m_value.number_float == 0)
{
o << "-0.0";
// special case for zero to get "0.0"/"-0.0"
o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0");
}
else
{
o << "0.0";
// 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();
}
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);
}

o << buf;
return;
}

Expand Down
38 changes: 38 additions & 0 deletions test/unit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12369,5 +12369,43 @@ TEST_CASE("regression tests")
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");
}
}

0 comments on commit 170b70f

Please sign in to comment.