4

Assuming I have the following object

public class DataObjectA {
    private Stream<DataObjectB> dataObjectBStream;
}

How can I serialize them using Jackson?

Wins
  • 3,420
  • 4
  • 36
  • 70
  • 3
    You will have to write a [custom serializer/deserializer](http://wiki.fasterxml.com/JacksonHowToCustomSerializers). But i think that stream doesn't fit well in a DTO, they are intended to perform a computation in a pipeline. – gontard Mar 17 '15 at 15:47
  • I know someone is going to say that the stream doesn't fit well here, but I have reason here. I'm wondering why Jackson doesn't include serializer/deserializer for Stream as part of jackson-JDK8 module – Wins Mar 17 '15 at 15:54
  • 3
    Exactly because they didn't fit. Jackson add support for _"data types"_ likes the new Date API and `Optional` – gontard Mar 17 '15 at 16:07

4 Answers4

4

As others have pointed out, you can only iterate once over a stream. If that works for you, you can use this to serialize:

new ObjectMapper().writerFor(Iterator.class).writeValueAsString(dataObjectBStream.iterator())

If you're using a Jackson version prior to 2.5, use writerWithType() instead of writerFor().

shmosel
  • 49,289
  • 6
  • 73
  • 138
3

See https://github.com/FasterXML/jackson-modules-java8/issues/3 for the open issue to add java.util.Stream support to Jackson. There's a preliminary version of the code included. (edit: this is now merged and supported in 2.9.0).

Streaming support feels like it would work naturally/safely if the stream is the top level object you were (de)serializing, eg returning a java.util.stream.Stream<T> from a JAX-RS resource, or reading a Stream from a JAX-RS client.

A Stream as a member variable of a (de)serialized object, as you have in your example, is trickier, because it's mutable and single use:

private Stream<DataObjectB> dataObjectBStream;

Assuming it was supported, all of the caveats around storing references to streams would apply. You wouldn't be able to serialize the object more than once, and once you deserialized the wrapping object presumably it's stream member would retain a live connection back through the JAX-RS client and HTTP connection, which could create surprises.

Adrian Baker
  • 9,297
  • 1
  • 26
  • 22
  • -1 down vote I am running this Groovy snippet using Jackson 2.9.2 println new ObjectMapper().version() println new ObjectMapper().writeValueAsString([1, 2].stream()) and it outputs 2.9.2 {"parallel":false} – Peter Rietzler Dec 14 '17 at 13:09
  • 2
    Try registering the JDK 8 module ? – Adrian Baker Dec 21 '17 at 22:17
0

You don’t.

A Stream is a single-use chain of operations and never meant to be persistent. Even storing it into an instance field like in your question is an indicator for a misunderstanding of it’s purpose. Once a terminal operation has been applied on the stream, it is useless and streams can’t be cloned. This, there is no point in remembering the unusable stream in a field then.

Since the only operations offered by Stream are chaining more operations to the pipeline and finally evaluating it, there is no way of querying its state such that it would allow to create an equivalent stream regarding its behavior. Therefore, no persistence framework can store it. The only thing a framework could do, is traversing the resulting elements of the stream operation and store them but that means effectively storing a kind of collection of objects rather than the Stream. Besides that, the single-use nature of a Stream also implies that a storage framework traversing the stream in order to store the elements had the side-effect of making the stream unusable at the same time.

If you want to store elements, resort to an ordinary Collection.

On the other hand, if you really want to store behavior, you’ll end up storing an object instance whose actual class implements the behavior. This still works with Streams as you can store an instance of a class which has a factory method producing the desired stream. Of course, you are not really storing the behavior but a symbolic reference to it, but this is always the case when you use an OO storage framework to store behavior rather than data.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • 1
    The question is about serialization, not persistence or storage. – Adrian Baker Mar 06 '17 at 20:34
  • 2
    @Adrian Baker: Serialization *is* a persistence mechanism. Even if you use it for transferring data from one machine to another, everything said here, holds. The is no sense in storing a stream, which you even acknowledge in your answer. In the end, you are only transferring the *data* and making it look like “Serialization Support for Streams” is a disguise. – Holger Mar 07 '17 at 08:25
  • 2
    _"You don’t."_ <- This certainly doesn't hold. java.util.Stream to/from JSON using Jackson is perfectly sensible, and holding a reference to a Stream in a member variable isn't automatically wrong. Programming with streams throws up challenges and pitfalls, but the answer to those isn't "don't". – Adrian Baker Mar 07 '17 at 21:05
  • 1
    @Adrian Baker: you are mixing up “streaming to/from JSON” with “serialize/deserialize a Stream”. A serialization that makes the original Stream unusable isn’t a serialization. And this question is about serialization. Point. – Holger Mar 08 '17 at 08:08
  • 2
    I can only guess you must be referring to the strict contract of `java.io.Serializable` ? I think the question, and generally Jackson, refers to serialization in a much broader and basic sense of, producing some JSON based on some Java objects, which don't need to implement `java.io.Serializable`. [Here's a really basic example](https://gist.github.com/adrian-baker/24df92918fb656b0c8b57597de958eff). – Adrian Baker Mar 08 '17 at 08:36
  • 2
    @Adrian Baker: there is no point in trying to read the OP’s mind of a two year old question. Generally, the term Serialization does *not* refer to a destructive process. You can call whatever you want “Serialization”, but that doesn’t prove anything. You are only transferring the *contents* of a Stream, but not the Stream itself. The Stream you construct at the other side has entirely different properties. There is an established term for data transfers, i.e. which might not retain the source, and that term is *streaming*. Anyway, you already told that you disagree, so we can stop here. – Holger Mar 08 '17 at 08:51
  • "You are only transferring the contents of a Stream, but not the Stream itself. " - I do agree with this, so if we take the question literally as written you're quite correct. – Adrian Baker Dec 26 '22 at 02:12
  • There is a very practical reason for reading JSON input as stream - e.g. if you are posting a huge number of DTOs to a service via a single request (e.g. 20.000), you want to read that JSON as a stream and perform mapping, validation, persistence as a stream, and ultimately persist in batches of e.g. 500. If you want to deserialize into a stream, you are most probably aware of the implications it holds. – Vanja D. Jun 19 '23 at 10:27
  • 1
    @VanjaD. there is a difference between using Stream in a persistence API and “serializing a Stream”. Declaring an instance field as `Stream`, like in the question’s code, is the wrong approach. There is nothing wrong with persistence methods taking a Stream as parameter or returning a Stream. – Holger Jun 19 '23 at 11:55
0

I had below class having 2 elements one of them was Stream, had to annotate the getterStream method with@JsonSerializer and then override Serialize method, produces stream of JSON in my Response API:

public class DataSetResultBean extends ResultBean { private static final long serialVersionUID = 1L;

private final List<ComponentBean> structure;
private final Stream<DataPoint> datapoints;

private static class DataPointSerializer extends JsonSerializer<Stream<DataPoint>> 
{
    @Override
    public void serialize(Stream<DataPoint> stream, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException
    {
        gen.writeStartArray();
        try
        {
            stream.forEach(dp -> serializeSingle(gen, dp));
        }
        catch (UncheckedIOException e)
        {
            throw (IOException) e.getCause();
        }
        finally
        {
            stream.close();
        }
        gen.writeEndArray();
    }
    
    public synchronized void serializeSingle(JsonGenerator gen, DataPoint dp) throws UncheckedIOException
    {
        try
        {
            gen.writeStartObject();
            
            for (Entry<DataStructureComponent<?, ?, ?>, ScalarValue<?, ?, ?>> entry: dp.entrySet())
            {
                gen.writeFieldName(entry.getKey().getName());
                gen.writeRawValue(entry.getValue().toString());
            }
            
            gen.writeEndObject();
        }
        catch (IOException e)
        {
            throw new UncheckedIOException(e);
        }
    }
}

public DataSetResultBean(DataSet dataset)
{
    super("DATASET");
    
    structure = dataset.getMetadata().stream().map(ComponentBean::new).collect(toList());
    datapoints = dataset.stream();
}

public List<ComponentBean> getStructure()
{
    return structure;
}

@JsonSerialize(using = DataPointSerializer.class)
public Stream<DataPoint> getDatapoints()
{
    return datapoints;
}

}

Excalibur
  • 21
  • 2