jackson-dataformat-xml icon indicating copy to clipboard operation
jackson-dataformat-xml copied to clipboard

Unable to serialize top-level Java8 Stream

Open sunruh opened this issue 7 years ago • 17 comments

A Stream nested in another object is serialized using a value wrapper just as a collection or array, serializing a Stream directly fails when more than one element is available as it tries to write a second root. If the Stream emits only a single element serialization works but without wrapping:

public class StreamTest {
    private static final ObjectMapper OBJECT_MAPPER = new XmlMapper()
            .registerModule(new Jdk8Module());
    
    private static class StreamContainer {
        @JsonProperty
        private Stream<String> stream;
        public StreamContainer(Stream<String> stream) { this.stream = stream; }
    }

    @Test
    public void testTopLevelOneElement() throws JsonProcessingException {
        assertNotEquals("<Head>a</Head>",
                OBJECT_MAPPER.writeValueAsString(Stream.of("a")));
    }
    
    @Test
    public void testTopLevelTwoElements() throws JsonProcessingException {
        try {
            assertEquals("<Head><item>a</item><item>b</item></Head>",
                    OBJECT_MAPPER.writeValueAsString(Stream.of("a", "b")));
        } catch (JsonMappingException e) {
            fail("Trying to output second root?", e);
        }
    }
    
    @Test
    public void testNestedOneElement() throws JsonProcessingException {
        assertEquals("<StreamContainer><stream>a</stream></StreamContainer>",
                OBJECT_MAPPER.writeValueAsString(new StreamContainer(Stream.of("a"))));
    }
    
    
    @Test
    public void testNestedTwoElements() throws JsonProcessingException {
        assertEquals("<StreamContainer><stream>a</stream><stream>b</stream></StreamContainer>",
                OBJECT_MAPPER.writeValueAsString(new StreamContainer(Stream.of("a", "b"))));
    }
}

The root element name is Head for Stream serialization as it's determined from the actual class name ReferencePipeline$Head.

The cause lies in TypeUtil.isIndexedType(Class<?>) which just checks for Arrays and Collections and only if this returns true, a field name is written (and _nextName set).

It works for the nested Stream as _nextName is set due to the Stream being written as a field (multiple times).

sunruh avatar Jul 20 '18 15:07 sunruh

Hmmh. Yes, so the work-around(s) used for Collections will not apply to Streams. I suspect same problem would apply to Iterators / Iterables as well... although in most cases objects probably implement Collection.

It'd be nice to figure out something more general for iterable types (since they are not quite what CollectionLikeType covers unfortunately), but on short term (2.10, maybe even 2.9) I guess added check might be acceptable.

cowtowncoder avatar Aug 22 '18 17:08 cowtowncoder

Realized that support probably requires upgrade of JDK baseline for XML module to be Java 8 -- something that is probably reasonable for 2.11, but can not be done in a patch for 2.10.

cowtowncoder avatar Oct 03 '19 20:10 cowtowncoder

At this point Java 8 requirement is no longer blocker, fwtw.

cowtowncoder avatar Jul 25 '22 21:07 cowtowncoder