I want to replay the feed by streaming the content of that file, using ObjectReader::readValues<Content>(stream)
Test excerpt
val om = XmlMapper()
val it = om.readerFor(object : TypeReference<Content>() {}).readValues<Content>(inp)
assertEquals(3, it.readAll().size)
The above execution fails with a MismatchedInputException.
The problem is that the input is not valid XML: a root element is missing.
Since I deem the scenario reasonable I wonder if the Jackson library could address this use case in a seamless way such as happens with Json (See code at the end of this post for comparison).
Workarounds
The most straightforward way for me to workaround this issue was to wrap the input stream with another object RootWrappedInputStream that exhausts in sequence three streams reading out:
an opening root entity
the original stream
the closing of the root entity
(See code at the end of this post.)
I got this suggestion from Tatu in the jackson-user google group:
But what you would probably need to do would be to construct FromXmlReader (subtype of JsonParser) first, advance stream to the first XML element of the first value, and then construct MappingIterator from ObjectMapper (through ObjectReader).
I was not able to make this work.
Executable code
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.xml.XmlMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import java.io.InputStream
import java.nio.charset.Charset
import kotlin.test.assertEquals
internal class RootWrappedInputStream(inp: InputStream) : InputStream() {
constructor(s: String, charset: Charset = Charsets.UTF_8) : this(s.byteInputStream(charset))
private val root = "INJECTED>"
private val streams = mutableListOf("<$root".byteInputStream(), inp, "</$root".byteInputStream())
override fun read(): Int {
if (streams.isEmpty()) {
return -1
} else {
val q = streams.first().read()
if (q == -1) {
streams.removeFirst()
return read()
} else {
return q
}
}
}
}
val problematicXMLUnwrapped = """
<Content>
<a>i1</a>
<b>1</b>
</Content>
<Content>
<a>i2</a>
<b>2</b>
</Content>
<Content>
<a>i3</a>
<b>3</b>
</Content>""".trimIndent()
data class Content(val a: String, val b: Int)
fun ObjectMapper.initMapper(): ObjectMapper {
val kotmod = KotlinModule.Builder().build()
registerModule(kotmod)
return this
}
fun withReadValues(om: ObjectMapper, inp: InputStream): Set<Content> {
val result = mutableSetOf<Content>()
val it = om.readerFor(object : TypeReference<Content>() {}).readValues<Content>(inp)
try {
it.readAll(result)
assertEquals(3, result.size)
println("Ok")
} catch (t: Throwable) {
println("Error")
throw t
}
return result
}
fun main() {
val xm = XmlMapper().initMapper()
val jm = ObjectMapper().initMapper()
// Both succeed as per readValues' specification. No input-specific configuration required
withReadValues(jm, """[{"a":"i1", "b":1},{"a":"i2", "b":2},{"a":"i3", "b":2}]""".byteInputStream())
withReadValues(jm, """{"a":"i1", "b":1}{"a":"i2", "b":2}{"a":"i3", "b":2}""".byteInputStream())
// Succeeds because there is a root element
withReadValues(xm, "<BBB>$problematicXMLUnwrapped</BBB>".trimIndent().byteInputStream())
// Succeeds because RootWrappedInputStream wraps a root item around the stream
withReadValues(xm, RootWrappedInputStream(problematicXMLUnwrapped.byteInputStream()))
// Fails as there is no root element
withReadValues(xm, problematicXMLUnwrapped.byteInputStream())
}
Description
I describe the issue with short snippets of code. A self-contained example trails this post.
Versions
Using gradle, I include:
Scenario
<Content>
XML objects.ObjectReader::readValues<Content>(stream)
Test excerpt
MismatchedInputException
.Workarounds
The most straightforward way for me to workaround this issue was to wrap the input stream with another object
RootWrappedInputStream
that exhausts in sequence three streams reading out:(See code at the end of this post.)
I got this suggestion from Tatu in the
jackson-user
google group:I was not able to make this work.
Executable code