mirror of
https://github.com/nlohmann/json.git
synced 2026-08-08 10:13:20 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a13902a33f | ||
|
|
c021a09b08 | ||
|
|
e4aaf46d38 | ||
|
|
5bc24e876b | ||
|
|
da7b9bdb3d | ||
|
|
634f49bc5b |
@@ -69,7 +69,8 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
|||||||
[`input_format_t`](input_format_t.md) for more information
|
[`input_format_t`](input_format_t.md) for more information
|
||||||
|
|
||||||
`strict` (in)
|
`strict` (in)
|
||||||
: whether the input has to be consumed completely (optional, `#!cpp true` by default)
|
: whether the input has to be consumed completely (optional, `#!cpp true` by default); when `#!cpp false` and the
|
||||||
|
input is a `#!cpp std::istream`, the stream is left positioned right after the parsed value
|
||||||
|
|
||||||
`ignore_comments` (in)
|
`ignore_comments` (in)
|
||||||
: whether comments should be ignored and treated like whitespace (`#!cpp true`) or yield a parse error
|
: whether comments should be ignored and treated like whitespace (`#!cpp true`) or yield a parse error
|
||||||
@@ -136,6 +137,8 @@ A UTF-8 byte order mark is silently ignored.
|
|||||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
|
- Changed in version 4.0.0 to leave a `#!cpp std::istream` positioned right after the parsed value when `strict` is
|
||||||
|
`#!cpp false`; see [`operator>>`](../operator_gtgt.md#notes).
|
||||||
|
|
||||||
!!! warning "Deprecation"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -33,41 +33,26 @@ A UTF-8 byte order mark is silently ignored.
|
|||||||
Invalid Unicode escapes and unpaired surrogates in the input are reported as
|
Invalid Unicode escapes and unpaired surrogates in the input are reported as
|
||||||
[`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) with a detailed message.
|
[`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) with a detailed message.
|
||||||
|
|
||||||
`operator>>` parses exactly one JSON value, so it can be called repeatedly to read a sequence of concatenated JSON
|
`operator>>` parses exactly one JSON value and leaves the stream positioned right after it, so it can be called
|
||||||
values from the same stream:
|
repeatedly to read a sequence of concatenated JSON values from the same stream:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
json j1, j2;
|
std::istringstream input("1true[2]");
|
||||||
input >> j1; // parses the first value
|
json j1, j2, j3;
|
||||||
input >> j2; // parses the next value
|
input >> j1; // j1 == 1, stream now positioned right after it
|
||||||
|
input >> j2; // j2 == true
|
||||||
|
input >> j3; // j3 == [2]
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! warning "A number must be followed by whitespace"
|
!!! note "Changed behavior for numbers"
|
||||||
|
|
||||||
A number is only terminated by the character that follows it. That character is read from the stream to detect the
|
A number is the only value whose end can be detected solely by reading the character that follows it. Up to
|
||||||
end of the number, and it is **not** put back. When a value that is a number is immediately followed by the next
|
version 3.13.0 that character was consumed and not put back, so the stream was left one byte too far whenever a
|
||||||
value, the first character of that next value is lost:
|
number was immediately followed by another value: reading `1true` yielded `1` and left the stream at `rue`.
|
||||||
|
Values had to be separated by whitespace to work around this.
|
||||||
|
|
||||||
```cpp
|
The terminating character is now only looked at and left in the stream, so no separator is required. Code that
|
||||||
std::istringstream input("1true");
|
relied on the extra byte being swallowed will observe it again.
|
||||||
json j1, j2;
|
|
||||||
input >> j1; // j1 == 1
|
|
||||||
input >> j2; // throws parse_error.101: the stream now starts at "rue"
|
|
||||||
```
|
|
||||||
|
|
||||||
Separating the values with whitespace avoids this, because the character that is eaten is then the separator:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
std::istringstream input("1 true");
|
|
||||||
json j1, j2;
|
|
||||||
input >> j1; // j1 == 1
|
|
||||||
input >> j2; // j2 == true
|
|
||||||
```
|
|
||||||
|
|
||||||
Only numbers are affected. Values ending in a self-delimiting character do not read past themselves, so
|
|
||||||
`truefalse`, `[1][2]`, `{"a":1}{"b":2}`, and `"a""b"` can be read back to back without a separator.
|
|
||||||
|
|
||||||
This is tracked in [#5340](https://github.com/nlohmann/json/issues/5340).
|
|
||||||
|
|
||||||
Note that reading concatenated values does **not** work for [JSON Lines](../features/parsing/json_lines.md)
|
Note that reading concatenated values does **not** work for [JSON Lines](../features/parsing/json_lines.md)
|
||||||
(newline-delimited JSON) input -- see that page for why and for the recommended alternative.
|
(newline-delimited JSON) input -- see that page for why and for the recommended alternative.
|
||||||
@@ -102,3 +87,5 @@ Note that reading concatenated values does **not** work for [JSON Lines](../feat
|
|||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Added in version 1.0.0.
|
- Added in version 1.0.0.
|
||||||
|
- Changed in version 4.0.0 to leave the character that terminates a number in the stream, so that the stream is
|
||||||
|
positioned right after the parsed value for every value type.
|
||||||
|
|||||||
@@ -101,6 +101,9 @@ class input_stream_adapter
|
|||||||
// maintain ifstream flags, except eof
|
// maintain ifstream flags, except eof
|
||||||
if (is != nullptr)
|
if (is != nullptr)
|
||||||
{
|
{
|
||||||
|
// consume the character last returned by get_character() unless it
|
||||||
|
// was given back with release_lookahead()
|
||||||
|
commit_lookahead();
|
||||||
is->clear(is->rdstate() & std::ios::eofbit);
|
is->clear(is->rdstate() & std::ios::eofbit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,29 +118,60 @@ class input_stream_adapter
|
|||||||
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
||||||
|
|
||||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||||
: is(rhs.is), sb(rhs.sb)
|
: is(rhs.is), sb(rhs.sb), lookahead(rhs.lookahead)
|
||||||
{
|
{
|
||||||
rhs.is = nullptr;
|
rhs.is = nullptr;
|
||||||
rhs.sb = nullptr;
|
rhs.sb = nullptr;
|
||||||
|
rhs.lookahead = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether the character last returned by get_character() can be given back
|
||||||
|
// to the input with release_lookahead().
|
||||||
|
static constexpr bool supports_lookahead = true;
|
||||||
|
|
||||||
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
||||||
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
||||||
// end up as the same value, e.g., 0xFFFFFFFF.
|
// end up as the same value, e.g., 0xFFFFFFFF.
|
||||||
|
//
|
||||||
|
// The character is peeked rather than consumed: it is only stepped over
|
||||||
|
// once the next character is requested, or when the adapter is destroyed.
|
||||||
|
// Until then, release_lookahead() can leave it in the input.
|
||||||
std::char_traits<char>::int_type get_character()
|
std::char_traits<char>::int_type get_character()
|
||||||
{
|
{
|
||||||
auto res = sb->sbumpc();
|
if (lookahead)
|
||||||
|
{
|
||||||
|
// step over the character returned by the previous call
|
||||||
|
sb->sbumpc();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto res = sb->sgetc();
|
||||||
// set eof manually, as we don't use the istream interface.
|
// set eof manually, as we don't use the istream interface.
|
||||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||||
{
|
{
|
||||||
|
// there is nothing to step over next time
|
||||||
|
lookahead = false;
|
||||||
is->clear(is->rdstate() | std::ios::eofbit);
|
is->clear(is->rdstate() | std::ios::eofbit);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lookahead = true;
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Leave the character last returned by get_character() in the input, so
|
||||||
|
// that the next read from the stream - by this adapter or by the caller
|
||||||
|
// once parsing is done - sees it again. Unlike putting a consumed
|
||||||
|
// character back, this cannot fail.
|
||||||
|
void release_lookahead() noexcept
|
||||||
|
{
|
||||||
|
lookahead = false;
|
||||||
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
std::size_t get_elements(T* dest, std::size_t count = 1)
|
std::size_t get_elements(T* dest, std::size_t count = 1)
|
||||||
{
|
{
|
||||||
|
commit_lookahead();
|
||||||
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
||||||
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
||||||
{
|
{
|
||||||
@@ -147,9 +181,23 @@ class input_stream_adapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Step over the character last returned by get_character(). The character
|
||||||
|
// has already been peeked successfully, so for every streambuf with a get
|
||||||
|
// area this is a pointer increment that cannot fail.
|
||||||
|
void commit_lookahead()
|
||||||
|
{
|
||||||
|
if (lookahead)
|
||||||
|
{
|
||||||
|
lookahead = false;
|
||||||
|
sb->sbumpc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// the associated input stream
|
/// the associated input stream
|
||||||
std::istream* is = nullptr;
|
std::istream* is = nullptr;
|
||||||
std::streambuf* sb = nullptr;
|
std::streambuf* sb = nullptr;
|
||||||
|
/// whether get_character() peeked a character that is not consumed yet
|
||||||
|
bool lookahead = false;
|
||||||
};
|
};
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,24 @@ constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect whether an input adapter reads with one character of lookahead that
|
||||||
|
// can be left in the input (see input_stream_adapter::supports_lookahead),
|
||||||
|
// detected like supports_seek above.
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
using detect_supports_lookahead = decltype(InputAdapterType::supports_lookahead);
|
||||||
|
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
constexpr bool input_adapter_supports_lookahead(std::true_type /*detected*/)
|
||||||
|
{
|
||||||
|
return InputAdapterType::supports_lookahead;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
constexpr bool input_adapter_supports_lookahead(std::false_type /*detected*/)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief lexical analysis
|
@brief lexical analysis
|
||||||
|
|
||||||
@@ -146,6 +164,12 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
static constexpr bool lazy_token_string =
|
static constexpr bool lazy_token_string =
|
||||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||||
|
|
||||||
|
/// whether a simulated unget can be passed on to the input adapter, which
|
||||||
|
/// then leaves the character in the input; see
|
||||||
|
/// input_adapter_supports_lookahead
|
||||||
|
static constexpr bool can_release_lookahead =
|
||||||
|
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||||
|
|
||||||
@@ -1456,6 +1480,21 @@ scan_number_done:
|
|||||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// adapter without lookahead: nothing to do (see release_lookahead)
|
||||||
|
void release_lookahead_impl(std::false_type /*can_release*/) const noexcept {}
|
||||||
|
|
||||||
|
/// adapter with lookahead: leave the character in the input instead
|
||||||
|
void release_lookahead_impl(std::true_type /*can_release*/)
|
||||||
|
{
|
||||||
|
if (next_unget)
|
||||||
|
{
|
||||||
|
// the character is read from the input again rather than replayed
|
||||||
|
// from current, so the adapter must not step over it
|
||||||
|
next_unget = false;
|
||||||
|
ia.release_lookahead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// seekable adapter: nothing was captured, so nothing to undo
|
/// seekable adapter: nothing was captured, so nothing to undo
|
||||||
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
||||||
|
|
||||||
@@ -1519,6 +1558,29 @@ scan_number_done:
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief pass a pending simulated unget on to the input
|
||||||
|
|
||||||
|
unget() only rewinds the lexer's own bookkeeping, so the character that
|
||||||
|
terminated the last token (e.g. the character after a number) would still
|
||||||
|
be stepped over when the input adapter is done. Callers that hand the
|
||||||
|
input back to the user afterwards - operator>> and non-strict sax_parse -
|
||||||
|
call this once when scanning is done, so that the input is positioned
|
||||||
|
right after the value.
|
||||||
|
|
||||||
|
Adapters without lookahead (see input_adapter_supports_lookahead) are not
|
||||||
|
handed back to the user, so this is a no-op for them.
|
||||||
|
|
||||||
|
Scanning may continue after this call: @a next_unget is cleared, and the
|
||||||
|
character is read from the input again instead of being replayed from
|
||||||
|
@a current. A pending unget of EOF needs no special case, because reaching
|
||||||
|
EOF leaves no lookahead to release.
|
||||||
|
*/
|
||||||
|
void release_lookahead()
|
||||||
|
{
|
||||||
|
release_lookahead_impl(std::integral_constant<bool, can_release_lookahead> {});
|
||||||
|
}
|
||||||
|
|
||||||
/// seekable adapter: rebuild the last read token from the input on demand
|
/// seekable adapter: rebuild the last read token from the input on demand
|
||||||
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -99,8 +99,14 @@ class parser
|
|||||||
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal(&sdp);
|
||||||
|
|
||||||
|
if (!strict)
|
||||||
|
{
|
||||||
|
// the caller keeps using the input: position it right after
|
||||||
|
// the value by leaving the character that terminated it
|
||||||
|
m_lexer.release_lookahead();
|
||||||
|
}
|
||||||
// in strict mode, input must be completely read
|
// in strict mode, input must be completely read
|
||||||
if (strict && (get_token() != token_type::end_of_input))
|
else if (get_token() != token_type::end_of_input)
|
||||||
{
|
{
|
||||||
sdp.parse_error(m_lexer.get_position(),
|
sdp.parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -127,8 +133,13 @@ class parser
|
|||||||
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal(&sdp);
|
||||||
|
|
||||||
|
if (!strict)
|
||||||
|
{
|
||||||
|
// see above
|
||||||
|
m_lexer.release_lookahead();
|
||||||
|
}
|
||||||
// in strict mode, input must be completely read
|
// in strict mode, input must be completely read
|
||||||
if (strict && (get_token() != token_type::end_of_input))
|
else if (get_token() != token_type::end_of_input)
|
||||||
{
|
{
|
||||||
sdp.parse_error(m_lexer.get_position(),
|
sdp.parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -165,8 +176,14 @@ class parser
|
|||||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||||
const bool result = sax_parse_internal(sax);
|
const bool result = sax_parse_internal(sax);
|
||||||
|
|
||||||
|
if (result && !strict)
|
||||||
|
{
|
||||||
|
// the caller keeps using the input: position it right after the
|
||||||
|
// value by leaving the character that terminated it
|
||||||
|
m_lexer.release_lookahead();
|
||||||
|
}
|
||||||
// strict mode: next byte must be EOF
|
// strict mode: next byte must be EOF
|
||||||
if (result && strict && (get_token() != token_type::end_of_input))
|
else if (result && strict && (get_token() != token_type::end_of_input))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
|
|||||||
@@ -7104,6 +7104,9 @@ class input_stream_adapter
|
|||||||
// maintain ifstream flags, except eof
|
// maintain ifstream flags, except eof
|
||||||
if (is != nullptr)
|
if (is != nullptr)
|
||||||
{
|
{
|
||||||
|
// consume the character last returned by get_character() unless it
|
||||||
|
// was given back with release_lookahead()
|
||||||
|
commit_lookahead();
|
||||||
is->clear(is->rdstate() & std::ios::eofbit);
|
is->clear(is->rdstate() & std::ios::eofbit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7118,29 +7121,60 @@ class input_stream_adapter
|
|||||||
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
||||||
|
|
||||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||||
: is(rhs.is), sb(rhs.sb)
|
: is(rhs.is), sb(rhs.sb), lookahead(rhs.lookahead)
|
||||||
{
|
{
|
||||||
rhs.is = nullptr;
|
rhs.is = nullptr;
|
||||||
rhs.sb = nullptr;
|
rhs.sb = nullptr;
|
||||||
|
rhs.lookahead = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether the character last returned by get_character() can be given back
|
||||||
|
// to the input with release_lookahead().
|
||||||
|
static constexpr bool supports_lookahead = true;
|
||||||
|
|
||||||
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
||||||
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
||||||
// end up as the same value, e.g., 0xFFFFFFFF.
|
// end up as the same value, e.g., 0xFFFFFFFF.
|
||||||
|
//
|
||||||
|
// The character is peeked rather than consumed: it is only stepped over
|
||||||
|
// once the next character is requested, or when the adapter is destroyed.
|
||||||
|
// Until then, release_lookahead() can leave it in the input.
|
||||||
std::char_traits<char>::int_type get_character()
|
std::char_traits<char>::int_type get_character()
|
||||||
{
|
{
|
||||||
auto res = sb->sbumpc();
|
if (lookahead)
|
||||||
|
{
|
||||||
|
// step over the character returned by the previous call
|
||||||
|
sb->sbumpc();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto res = sb->sgetc();
|
||||||
// set eof manually, as we don't use the istream interface.
|
// set eof manually, as we don't use the istream interface.
|
||||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||||
{
|
{
|
||||||
|
// there is nothing to step over next time
|
||||||
|
lookahead = false;
|
||||||
is->clear(is->rdstate() | std::ios::eofbit);
|
is->clear(is->rdstate() | std::ios::eofbit);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lookahead = true;
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Leave the character last returned by get_character() in the input, so
|
||||||
|
// that the next read from the stream - by this adapter or by the caller
|
||||||
|
// once parsing is done - sees it again. Unlike putting a consumed
|
||||||
|
// character back, this cannot fail.
|
||||||
|
void release_lookahead() noexcept
|
||||||
|
{
|
||||||
|
lookahead = false;
|
||||||
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
std::size_t get_elements(T* dest, std::size_t count = 1)
|
std::size_t get_elements(T* dest, std::size_t count = 1)
|
||||||
{
|
{
|
||||||
|
commit_lookahead();
|
||||||
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
||||||
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
||||||
{
|
{
|
||||||
@@ -7150,9 +7184,23 @@ class input_stream_adapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Step over the character last returned by get_character(). The character
|
||||||
|
// has already been peeked successfully, so for every streambuf with a get
|
||||||
|
// area this is a pointer increment that cannot fail.
|
||||||
|
void commit_lookahead()
|
||||||
|
{
|
||||||
|
if (lookahead)
|
||||||
|
{
|
||||||
|
lookahead = false;
|
||||||
|
sb->sbumpc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// the associated input stream
|
/// the associated input stream
|
||||||
std::istream* is = nullptr;
|
std::istream* is = nullptr;
|
||||||
std::streambuf* sb = nullptr;
|
std::streambuf* sb = nullptr;
|
||||||
|
/// whether get_character() peeked a character that is not consumed yet
|
||||||
|
bool lookahead = false;
|
||||||
};
|
};
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
|
|
||||||
@@ -7839,6 +7887,24 @@ constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect whether an input adapter reads with one character of lookahead that
|
||||||
|
// can be left in the input (see input_stream_adapter::supports_lookahead),
|
||||||
|
// detected like supports_seek above.
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
using detect_supports_lookahead = decltype(InputAdapterType::supports_lookahead);
|
||||||
|
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
constexpr bool input_adapter_supports_lookahead(std::true_type /*detected*/)
|
||||||
|
{
|
||||||
|
return InputAdapterType::supports_lookahead;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
constexpr bool input_adapter_supports_lookahead(std::false_type /*detected*/)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief lexical analysis
|
@brief lexical analysis
|
||||||
|
|
||||||
@@ -7860,6 +7926,12 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
static constexpr bool lazy_token_string =
|
static constexpr bool lazy_token_string =
|
||||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||||
|
|
||||||
|
/// whether a simulated unget can be passed on to the input adapter, which
|
||||||
|
/// then leaves the character in the input; see
|
||||||
|
/// input_adapter_supports_lookahead
|
||||||
|
static constexpr bool can_release_lookahead =
|
||||||
|
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||||
|
|
||||||
@@ -9170,6 +9242,21 @@ scan_number_done:
|
|||||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// adapter without lookahead: nothing to do (see release_lookahead)
|
||||||
|
void release_lookahead_impl(std::false_type /*can_release*/) const noexcept {}
|
||||||
|
|
||||||
|
/// adapter with lookahead: leave the character in the input instead
|
||||||
|
void release_lookahead_impl(std::true_type /*can_release*/)
|
||||||
|
{
|
||||||
|
if (next_unget)
|
||||||
|
{
|
||||||
|
// the character is read from the input again rather than replayed
|
||||||
|
// from current, so the adapter must not step over it
|
||||||
|
next_unget = false;
|
||||||
|
ia.release_lookahead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// seekable adapter: nothing was captured, so nothing to undo
|
/// seekable adapter: nothing was captured, so nothing to undo
|
||||||
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
||||||
|
|
||||||
@@ -9233,6 +9320,29 @@ scan_number_done:
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief pass a pending simulated unget on to the input
|
||||||
|
|
||||||
|
unget() only rewinds the lexer's own bookkeeping, so the character that
|
||||||
|
terminated the last token (e.g. the character after a number) would still
|
||||||
|
be stepped over when the input adapter is done. Callers that hand the
|
||||||
|
input back to the user afterwards - operator>> and non-strict sax_parse -
|
||||||
|
call this once when scanning is done, so that the input is positioned
|
||||||
|
right after the value.
|
||||||
|
|
||||||
|
Adapters without lookahead (see input_adapter_supports_lookahead) are not
|
||||||
|
handed back to the user, so this is a no-op for them.
|
||||||
|
|
||||||
|
Scanning may continue after this call: @a next_unget is cleared, and the
|
||||||
|
character is read from the input again instead of being replayed from
|
||||||
|
@a current. A pending unget of EOF needs no special case, because reaching
|
||||||
|
EOF leaves no lookahead to release.
|
||||||
|
*/
|
||||||
|
void release_lookahead()
|
||||||
|
{
|
||||||
|
release_lookahead_impl(std::integral_constant<bool, can_release_lookahead> {});
|
||||||
|
}
|
||||||
|
|
||||||
/// seekable adapter: rebuild the last read token from the input on demand
|
/// seekable adapter: rebuild the last read token from the input on demand
|
||||||
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
||||||
{
|
{
|
||||||
@@ -13965,8 +14075,14 @@ class parser
|
|||||||
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal(&sdp);
|
||||||
|
|
||||||
|
if (!strict)
|
||||||
|
{
|
||||||
|
// the caller keeps using the input: position it right after
|
||||||
|
// the value by leaving the character that terminated it
|
||||||
|
m_lexer.release_lookahead();
|
||||||
|
}
|
||||||
// in strict mode, input must be completely read
|
// in strict mode, input must be completely read
|
||||||
if (strict && (get_token() != token_type::end_of_input))
|
else if (get_token() != token_type::end_of_input)
|
||||||
{
|
{
|
||||||
sdp.parse_error(m_lexer.get_position(),
|
sdp.parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -13993,8 +14109,13 @@ class parser
|
|||||||
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal(&sdp);
|
||||||
|
|
||||||
|
if (!strict)
|
||||||
|
{
|
||||||
|
// see above
|
||||||
|
m_lexer.release_lookahead();
|
||||||
|
}
|
||||||
// in strict mode, input must be completely read
|
// in strict mode, input must be completely read
|
||||||
if (strict && (get_token() != token_type::end_of_input))
|
else if (get_token() != token_type::end_of_input)
|
||||||
{
|
{
|
||||||
sdp.parse_error(m_lexer.get_position(),
|
sdp.parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -14031,8 +14152,14 @@ class parser
|
|||||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||||
const bool result = sax_parse_internal(sax);
|
const bool result = sax_parse_internal(sax);
|
||||||
|
|
||||||
|
if (result && !strict)
|
||||||
|
{
|
||||||
|
// the caller keeps using the input: position it right after the
|
||||||
|
// value by leaving the character that terminated it
|
||||||
|
m_lexer.release_lookahead();
|
||||||
|
}
|
||||||
// strict mode: next byte must be EOF
|
// strict mode: next byte must be EOF
|
||||||
if (result && strict && (get_token() != token_type::end_of_input))
|
else if (result && strict && (get_token() != token_type::end_of_input))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
|
|||||||
@@ -14,10 +14,15 @@ using nlohmann::json;
|
|||||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <streambuf>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <valarray>
|
#include <valarray>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
@@ -219,6 +224,58 @@ class proxy_iterator
|
|||||||
iterator* m_it = nullptr;
|
iterator* m_it = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A streambuf that keeps no get area at all and refuses every putback: with an
|
||||||
|
// empty get area, sungetc() always ends up in pbackfail(). Used to check that
|
||||||
|
// the character terminating a number is left in the input without relying on
|
||||||
|
// the streambuf being able to put a consumed character back.
|
||||||
|
class no_putback_streambuf : public std::streambuf
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit no_putback_streambuf(std::string s) : m_data(std::move(s)) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// peek at the next character without consuming it
|
||||||
|
int_type underflow() override
|
||||||
|
{
|
||||||
|
if (m_pos >= m_data.size())
|
||||||
|
{
|
||||||
|
return traits_type::eof();
|
||||||
|
}
|
||||||
|
return traits_type::to_int_type(m_data[m_pos]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume the next character
|
||||||
|
int_type uflow() override
|
||||||
|
{
|
||||||
|
if (m_pos >= m_data.size())
|
||||||
|
{
|
||||||
|
return traits_type::eof();
|
||||||
|
}
|
||||||
|
return traits_type::to_int_type(m_data[m_pos++]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int_type pbackfail(int_type /*c*/) override
|
||||||
|
{
|
||||||
|
return traits_type::eof();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_data;
|
||||||
|
std::size_t m_pos = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// read the characters that are left in a stream
|
||||||
|
std::string remaining(std::istream& is)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
char c = 0;
|
||||||
|
while (is.get(c))
|
||||||
|
{
|
||||||
|
result += c;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// JSON_HAS_CPP_20
|
// JSON_HAS_CPP_20
|
||||||
#if defined(__cpp_char8_t)
|
#if defined(__cpp_char8_t)
|
||||||
bool check_utf8()
|
bool check_utf8()
|
||||||
@@ -1181,6 +1238,122 @@ TEST_CASE("deserialization")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("stream position after extraction (#5340)")
|
||||||
|
{
|
||||||
|
SECTION("a number does not consume the character that terminates it")
|
||||||
|
{
|
||||||
|
// a number is only terminated by the character following it; that
|
||||||
|
// character must be given back so the stream is positioned right
|
||||||
|
// after the value
|
||||||
|
const std::vector<std::pair<std::string, std::string>> tests =
|
||||||
|
{
|
||||||
|
{"1true", "true"},
|
||||||
|
{"1[2]", "[2]"},
|
||||||
|
{"1{}", "{}"},
|
||||||
|
{R"(1"a")", R"("a")"},
|
||||||
|
{"1 true", " true"},
|
||||||
|
{"12,", ","},
|
||||||
|
{"-0.5e3x", "x"},
|
||||||
|
{"1null", "null"}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& test : tests)
|
||||||
|
{
|
||||||
|
CAPTURE(test.first);
|
||||||
|
std::istringstream ss(test.first);
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(j == json::parse(test.first.substr(0, test.first.size() - test.second.size())));
|
||||||
|
CHECK(remaining(ss) == test.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("values that are self-delimiting are unaffected")
|
||||||
|
{
|
||||||
|
const std::vector<std::pair<std::string, std::string>> tests =
|
||||||
|
{
|
||||||
|
{"truefalse", "false"},
|
||||||
|
{"[1][2]", "[2]"},
|
||||||
|
{R"({"a":1}{"b":2})", R"({"b":2})"},
|
||||||
|
{R"("a""b")", R"("b")"},
|
||||||
|
{"null null", " null"}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& test : tests)
|
||||||
|
{
|
||||||
|
CAPTURE(test.first);
|
||||||
|
std::istringstream ss(test.first);
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(remaining(ss) == test.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a number at the end of the input leaves nothing behind")
|
||||||
|
{
|
||||||
|
for (const std::string s :
|
||||||
|
{"1", "12", "-3.5e2", " 7 "
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(s);
|
||||||
|
std::istringstream ss(s);
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(remaining(ss).find_first_not_of(" \t\n\r") == std::string::npos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("repeated extraction of concatenated values")
|
||||||
|
{
|
||||||
|
std::istringstream ss(R"(1true[2]3"x"{"a":4}5)");
|
||||||
|
const std::vector<json> expected =
|
||||||
|
{
|
||||||
|
json(1), json(true), json::parse("[2]"), json(3),
|
||||||
|
json("x"), json::parse(R"({"a":4})"), json(5)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& e : expected)
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(j == e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("sax_parse with strict == false")
|
||||||
|
{
|
||||||
|
std::istringstream ss("1true");
|
||||||
|
SaxEventLogger l;
|
||||||
|
CHECK(json::sax_parse(ss, &l, nlohmann::detail::input_format_t::json, false));
|
||||||
|
CHECK(l.events.size() == 1);
|
||||||
|
CHECK(l.events[0] == "number_unsigned(1)");
|
||||||
|
CHECK(remaining(ss) == "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("strict parsing still rejects trailing data")
|
||||||
|
{
|
||||||
|
std::istringstream ss("1true");
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse(ss),
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - unexpected true literal; expected end of input", json::parse_error&);
|
||||||
|
|
||||||
|
std::istringstream ss2("1true");
|
||||||
|
CHECK_FALSE(json::accept(ss2));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a streambuf that cannot put back is not needed")
|
||||||
|
{
|
||||||
|
// the terminating character is never consumed, so no putback
|
||||||
|
// position is required
|
||||||
|
no_putback_streambuf buf("1true");
|
||||||
|
std::istream is(&buf);
|
||||||
|
json j;
|
||||||
|
is >> j;
|
||||||
|
CHECK(j == json(1));
|
||||||
|
CHECK(remaining(is) == "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// build with C++20
|
// build with C++20
|
||||||
// JSON_HAS_CPP_20
|
// JSON_HAS_CPP_20
|
||||||
#if defined(__cpp_char8_t)
|
#if defined(__cpp_char8_t)
|
||||||
|
|||||||
Reference in New Issue
Block a user