Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
J
json
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Libraries
json
Commits
6408402a
Unverified
Commit
6408402a
authored
Feb 16, 2017
by
Niels Lohmann
Browse files
Options
Browse Files
Download
Plain Diff
🔀
merge #378 (for #362 and #454)
parents
057b1e60
94906107
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
346 additions
and
81 deletions
+346
-81
src/json.hpp
src/json.hpp
+159
-36
src/json.hpp.re2c
src/json.hpp.re2c
+159
-36
test/src/unit-regression.cpp
test/src/unit-regression.cpp
+28
-9
No files found.
src/json.hpp
View file @
6408402a
...
...
@@ -2646,14 +2646,6 @@ class basic_json
string_t
dump
(
const
int
indent
=
-
1
)
const
{
std
::
stringstream
ss
;
// fix locale problems
ss
.
imbue
(
std
::
locale
::
classic
());
// 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
ss
.
precision
(
std
::
numeric_limits
<
double
>::
digits10
);
if
(
indent
>=
0
)
{
...
...
@@ -6214,10 +6206,6 @@ class basic_json
`std::setw(4)` on @a o sets the indentation level to `4` and the
serialization result is the same as calling `dump(4)`.
@note During serialization, the locale and the precision of the output
stream @a o are changed. The original values are restored when the
function returns.
@param[in,out] o stream to serialize to
@param[in] j JSON value to serialize
...
...
@@ -6239,22 +6227,9 @@ class basic_json
// reset width to 0 for subsequent calls to this stream
o
.
width
(
0
);
// fix locale problems
const
auto
old_locale
=
o
.
imbue
(
std
::
locale
::
classic
());
// set precision
// 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
const
auto
old_precision
=
o
.
precision
(
std
::
numeric_limits
<
double
>::
digits10
);
// do the actual serialization
j
.
dump
(
o
,
pretty_print
,
static_cast
<
unsigned
int
>
(
indentation
));
// reset locale and precision
o
.
imbue
(
old_locale
);
o
.
precision
(
old_precision
);
return
o
;
}
...
...
@@ -8244,6 +8219,162 @@ class basic_json
return
result
;
}
/*!
@brief locale-independent serialization for built-in arithmetic types
*/
struct
numtostr
{
public:
template
<
typename
T
>
numtostr
(
T
value
)
{
x_write
(
value
,
std
::
is_integral
<
T
>
());
}
operator
const
char
*
()
const
{
return
m_buf
.
data
();
}
const
char
*
c_str
()
const
{
return
m_buf
.
data
();
}
private:
static
constexpr
size_t
s_capacity
=
30
;
std
::
array
<
char
,
s_capacity
+
2
>
m_buf
{{}};
// +2 for leading '-'
// and trailing '\0'
template
<
typename
T
>
void
x_write
(
T
x
,
std
::
true_type
)
{
static_assert
(
std
::
numeric_limits
<
T
>::
digits10
<=
s_capacity
,
""
);
const
bool
is_neg
=
x
<
0
;
size_t
i
=
0
;
while
(
x
and
i
<
s_capacity
)
{
const
auto
digit
=
std
::
labs
(
static_cast
<
long
>
(
x
%
10
));
m_buf
[
i
++
]
=
static_cast
<
char
>
(
'0'
+
digit
);
x
/=
10
;
}
assert
(
i
<
s_capacity
);
if
(
i
==
0
)
{
m_buf
[
i
++
]
=
'0'
;
}
if
(
is_neg
)
{
m_buf
[
i
++
]
=
'-'
;
}
std
::
reverse
(
m_buf
.
begin
(),
m_buf
.
begin
()
+
i
);
}
template
<
typename
T
>
void
x_write
(
T
x
,
std
::
false_type
)
{
if
(
x
==
0
)
{
std
::
strcpy
(
m_buf
.
data
(),
std
::
signbit
(
x
)
?
"-0.0"
:
"0.0"
);
return
;
}
static
constexpr
auto
d
=
std
::
numeric_limits
<
number_float_t
>::
digits10
;
static_assert
(
d
==
6
or
d
==
15
or
d
==
16
or
d
==
17
,
""
);
static
constexpr
auto
fmt
=
d
==
6
?
"%.7g"
:
d
==
15
?
"%.16g"
:
d
==
16
?
"%.17g"
:
d
==
17
?
"%.18g"
:
"%.19g"
;
// I'm not sure why we need to +1 the precision,
// but without it there's a unit-test that fails
// that asserts precision of the output
snprintf
(
m_buf
.
data
(),
m_buf
.
size
(),
fmt
,
x
);
#if 0
// C locales and C++ locales are similar but
// different.
//
// If working with C++ streams we'd've used
// these, but for C formatting functions we
// have to use C locales (setlocale / localeconv),
// rather than C++ locales (std::locale installed
// by std::locale::global()).
const std::locale loc;
const char thousands_sep =
std::use_facet< std::numpunct<char>>(
loc).thousands_sep();
const char decimal_point =
std::use_facet< std::numpunct<char>>(
loc).decimal_point();
#else
const
auto
loc
=
localeconv
();
assert
(
loc
!=
nullptr
);
const
char
thousands_sep
=
!
loc
->
thousands_sep
?
'\0'
:
loc
->
thousands_sep
[
0
];
const
char
decimal_point
=
!
loc
->
decimal_point
?
'\0'
:
loc
->
decimal_point
[
0
];
#endif
// erase thousands separator
if
(
thousands_sep
)
{
auto
end
=
std
::
remove
(
m_buf
.
begin
(),
m_buf
.
end
(),
thousands_sep
);
std
::
fill
(
end
,
m_buf
.
end
(),
'\0'
);
}
// convert decimal point to '.'
if
(
decimal_point
and
decimal_point
!=
'.'
)
{
for
(
auto
&
c
:
m_buf
)
{
if
(
c
==
decimal_point
)
{
c
=
'.'
;
break
;
}
}
}
// determine if need to apperd ".0"
auto
data_end
=
m_buf
.
begin
()
+
strlen
(
m_buf
.
data
());
const
bool
value_is_int_like
=
std
::
find_if
(
m_buf
.
begin
(),
data_end
,
[](
const
char
c
)
{
return
c
==
'.'
or
c
==
'e'
or
c
==
'E'
;
})
==
data_end
;
assert
(
data_end
+
2
<
m_buf
.
end
());
if
(
value_is_int_like
)
{
strcat
(
m_buf
.
data
(),
".0"
);
}
}
};
/*!
@brief internal implementation of the serialization function
...
...
@@ -8363,27 +8494,19 @@ class basic_json
case
value_t
:
:
number_integer
:
{
o
<<
m_value
.
number_integer
;
o
<<
numtostr
(
m_value
.
number_integer
).
c_str
()
;
return
;
}
case
value_t
:
:
number_unsigned
:
{
o
<<
m_value
.
number_unsigned
;
o
<<
numtostr
(
m_value
.
number_unsigned
).
c_str
()
;
return
;
}
case
value_t
:
:
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
{
o
<<
m_value
.
number_float
;
}
o
<<
numtostr
(
m_value
.
number_float
).
c_str
();
return
;
}
...
...
src/json.hpp.re2c
View file @
6408402a
...
...
@@ -2646,14 +2646,6 @@ class basic_json
string_t dump(const int indent = -1) const
{
std::stringstream ss;
// fix locale problems
ss.imbue(std::locale::classic());
// 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
ss.precision(std::numeric_limits<double>::digits10);
if (indent >= 0)
{
...
...
@@ -6214,10 +6206,6 @@ class basic_json
`std::setw(4)` on @a o sets the indentation level to `4` and the
serialization result is the same as calling `dump(4)`.
@note During serialization, the locale and the precision of the output
stream @a o are changed. The original values are restored when the
function returns.
@param[in,out] o stream to serialize to
@param[in] j JSON value to serialize
...
...
@@ -6239,22 +6227,9 @@ class basic_json
// reset width to 0 for subsequent calls to this stream
o.width(0);
// fix locale problems
const auto old_locale = o.imbue(std::locale::classic());
// set precision
// 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
const auto old_precision = o.precision(std::numeric_limits<double>::digits10);
// do the actual serialization
j.dump(o, pretty_print, static_cast<unsigned int>(indentation));
// reset locale and precision
o.imbue(old_locale);
o.precision(old_precision);
return o;
}
...
...
@@ -8244,6 +8219,162 @@ class basic_json
return result;
}
/*!
@brief locale-independent serialization for built-in arithmetic types
*/
struct numtostr
{
public:
template<typename T>
numtostr(T value)
{
x_write(value, std::is_integral<T>());
}
operator const char* () const
{
return m_buf.data();
}
const char* c_str() const
{
return m_buf.data();
}
private:
static constexpr size_t s_capacity = 30;
std::array < char, s_capacity + 2 > m_buf{{}}; // +2 for leading '-'
// and trailing '\0'
template<typename T>
void x_write(T x, std::true_type)
{
static_assert(std::numeric_limits<T>::digits10 <= s_capacity, "");
const bool is_neg = x < 0;
size_t i = 0;
while (x and i < s_capacity)
{
const auto digit = std::labs(static_cast<long>(x % 10));
m_buf[i++] = static_cast<char>('0' + digit);
x /= 10;
}
assert(i < s_capacity);
if (i == 0)
{
m_buf[i++] = '0';
}
if (is_neg)
{
m_buf[i++] = '-';
}
std::reverse(m_buf.begin(), m_buf.begin() + i);
}
template<typename T>
void x_write(T x, std::false_type)
{
if (x == 0)
{
std::strcpy(m_buf.data(),
std::signbit(x) ? "-0.0" : "0.0");
return;
}
static constexpr auto d =
std::numeric_limits<number_float_t>::digits10;
static_assert(d == 6 or d == 15 or d == 16 or d == 17, "");
static constexpr auto fmt = d == 6 ? "%.7g"
: d == 15 ? "%.16g"
: d == 16 ? "%.17g"
: d == 17 ? "%.18g"
: "%.19g";
// I'm not sure why we need to +1 the precision,
// but without it there's a unit-test that fails
// that asserts precision of the output
snprintf(m_buf.data(), m_buf.size(), fmt, x);
#if 0
// C locales and C++ locales are similar but
// different.
//
// If working with C++ streams we'd've used
// these, but for C formatting functions we
// have to use C locales (setlocale / localeconv),
// rather than C++ locales (std::locale installed
// by std::locale::global()).
const std::locale loc;
const char thousands_sep =
std::use_facet< std::numpunct<char>>(
loc).thousands_sep();
const char decimal_point =
std::use_facet< std::numpunct<char>>(
loc).decimal_point();
#else
const auto loc = localeconv();
assert(loc != nullptr);
const char thousands_sep = !loc->thousands_sep ? '\0'
: loc->thousands_sep[0];
const char decimal_point = !loc->decimal_point ? '\0'
: loc->decimal_point[0];
#endif
// erase thousands separator
if (thousands_sep)
{
auto end = std::remove(m_buf.begin(),
m_buf.end(),
thousands_sep);
std::fill(end, m_buf.end(), '\0');
}
// convert decimal point to '.'
if (decimal_point and decimal_point != '.')
{
for (auto& c : m_buf)
{
if (c == decimal_point)
{
c = '.';
break;
}
}
}
// determine if need to apperd ".0"
auto data_end = m_buf.begin() + strlen(m_buf.data());
const bool value_is_int_like =
std::find_if(m_buf.begin(), data_end,
[](const char c)
{
return c == '.'
or c == 'e'
or c == 'E';
})
== data_end;
assert(data_end + 2 < m_buf.end());
if (value_is_int_like)
{
strcat(m_buf.data(), ".0");
}
}
};
/*!
@brief internal implementation of the serialization function
...
...
@@ -8363,27 +8494,19 @@ class basic_json
case value_t::number_integer:
{
o <<
m_value.number_integer
;
o <<
numtostr(m_value.number_integer).c_str()
;
return;
}
case value_t::number_unsigned:
{
o <<
m_value.number_unsigned
;
o <<
numtostr(m_value.number_unsigned).c_str()
;
return;
}
case value_t::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
{
o << m_value.number_float;
}
o << numtostr(m_value.number_float).c_str();
return;
}
...
...
test/src/unit-regression.cpp
View file @
6408402a
...
...
@@ -402,15 +402,35 @@ TEST_CASE("regression tests")
//issue #230
//CHECK(j2b.dump() == "23.42");
CHECK
(
j3a
.
dump
()
==
"10000"
);
CHECK
(
j3b
.
dump
()
==
"10000"
);
CHECK
(
j3c
.
dump
()
==
"10000"
);
CHECK
(
j3a
.
dump
()
==
"10000
.0
"
);
CHECK
(
j3b
.
dump
()
==
"10000
.0
"
);
CHECK
(
j3c
.
dump
()
==
"10000
.0
"
);
//CHECK(j3b.dump() == "1E04"); // roundtrip error
//CHECK(j3c.dump() == "1e04"); // roundtrip error
std
::
locale
::
global
(
orig_locale
);
}
SECTION
(
"issue #378 - locale-independent num-to-str"
)
{
setlocale
(
LC_NUMERIC
,
"de_DE.UTF-8"
);
// Verify that snprintf uses special decimal and grouping characters.
// Disabled, because can't trigger locale-specific behavior in AppVeyor
#ifndef _MSC_VER
{
std
::
array
<
char
,
64
>
buf
;
std
::
snprintf
(
buf
.
data
(),
buf
.
size
(),
"%.2f"
,
12345.67
);
CHECK
(
strcmp
(
buf
.
data
(),
"12345,67"
)
==
0
);
}
#endif
// verify that dumped correctly with '.' and no grouping
const
json
j1
=
12345.67
;
CHECK
(
json
(
12345.67
).
dump
()
==
"12345.67"
);
setlocale
(
LC_NUMERIC
,
"C"
);
}
SECTION
(
"issue #379 - locale-independent str-to-num"
)
{
setlocale
(
LC_NUMERIC
,
"de_DE.UTF-8"
);
...
...
@@ -434,7 +454,6 @@ TEST_CASE("regression tests")
CHECK
(
json
::
parse
(
"1.000000000000000000000000000000000000000000000000000000000000000000000000"
).
get
<
double
>
()
==
1.0
);
}
SECTION
(
"issue #233 - Can't use basic_json::iterator as a base iterator for std::move_iterator"
)
{
json
source
=
{
"a"
,
"b"
,
"c"
};
...
...
@@ -749,9 +768,9 @@ TEST_CASE("regression tests")
CHECK_THROWS_AS
(
json
::
parse
(
vec
),
std
::
invalid_argument
);
}
//
SECTION("issue #454 - doubles are printed as integers")
//
{
//
json j = R"({"bool_value":true,"double_value":2.0,"int_value":10,"level1":{"list_value":[3,"hi",false],"tmp":5.0},"string_value":"hello"})"_json;
// CHECK(j["double_value"].is_number_integer
());
//
}
SECTION
(
"issue #454 - doubles are printed as integers"
)
{
json
j
=
R"({"bool_value":true,"double_value":2.0,"int_value":10,"level1":{"list_value":[3,"hi",false],"tmp":5.0},"string_value":"hello"})"
_json
;
CHECK
(
j
[
"double_value"
].
is_number_float
());
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment