Skip to content

Commit

Permalink
Fix incorrect json decoding iterator's .hasNext() behavior on array-w…
Browse files Browse the repository at this point in the history
…rapped inputs:

hasNext() shouldn't throw exception if it was called more than once
after the stream has ended.

Fixes #2267
  • Loading branch information
sandwwraith committed Apr 11, 2023
1 parent 15aacd2 commit 9349f71
Show file tree
Hide file tree
Showing 4 changed files with 16 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class JsonLazySequenceTest {
iter.assertNext(StringData("b"))
iter.assertNext(StringData("c"))
assertFalse(iter.hasNext())
assertFalse(iter.hasNext()) // Subsequent calls to .hasNext() should not throw EOF or anything
assertFailsWithMessage<SerializationException>("EOF") {
iter.next()
}
Expand Down Expand Up @@ -186,4 +187,11 @@ class JsonLazySequenceTest {
assertEquals(inputList, json.decodeToSequence(paddedWs.asInputStream(), StringData.serializer(), DecodeSequenceMode.ARRAY_WRAPPED).toList())
}

@Test
fun testToIteratorAndBack() = withInputs { ins ->
val iterator = Json.decodeToSequence(ins, StringData.serializer()).iterator()
val values = iterator.asSequence().take(3).toList()
assertEquals(inputList, values)
assertFalse(iterator.hasNext())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public enum class DecodeSequenceMode {

/**
* Declares that parser itself should select between [WHITESPACE_SEPARATED] and [ARRAY_WRAPPED] modes.
* The selection is performed by looking on the first meaningful character of the stream.
* The selection is performed by looking at the first meaningful character of the stream.
*
* In most cases, auto-detection is sufficient to correctly parse an input.
* If the input is _whitespace-separated stream of the arrays_, parser could select an incorrect mode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ private class JsonIteratorArrayWrapped<T>(
private val deserializer: DeserializationStrategy<T>
) : Iterator<T> {
private var first = true
private var sawClosingBrace = false

override fun next(): T {
if (first) {
Expand All @@ -84,6 +85,7 @@ private class JsonIteratorArrayWrapped<T>(
*/
override fun hasNext(): Boolean {
if (lexer.peekNextToken() == TC_END_LIST) {
sawClosingBrace = true
lexer.consumeNextToken(TC_END_LIST)
if (lexer.isNotEof()) {
if (lexer.peekNextToken() == TC_BEGIN_LIST) lexer.fail("There is a start of the new array after the one parsed to sequence. " +
Expand All @@ -93,7 +95,7 @@ private class JsonIteratorArrayWrapped<T>(
}
return false
}
if (!lexer.isNotEof()) lexer.fail(TC_END_LIST)
return true
if (!lexer.isNotEof() && !sawClosingBrace) lexer.fail(TC_END_LIST)
return !sawClosingBrace
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ internal class ArrayAsSequence(private val buffer: CharArray) : CharSequence {
fun trim(newSize: Int) {
length = minOf(buffer.size, newSize)
}

// source.toString() is used in JsonDecodingException
override fun toString(): String = substring(0, length)
}

internal class ReaderJsonLexer(
Expand Down

0 comments on commit 9349f71

Please sign in to comment.