-
Notifications
You must be signed in to change notification settings - Fork 123
Closed
Milestone
Description
(moved from earlier issue filed by @jmax01)
Here is a first pass at serializing Streams.
It works for 2.6.x and above. A 2.8.1 version is also shown.
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.stream.Stream;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BasicSerializerFactory;
import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
import com.fasterxml.jackson.databind.ser.std.AsArraySerializerBase;
import com.fasterxml.jackson.databind.type.CollectionLikeType;
import com.fasterxml.jackson.databind.type.TypeBindings;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.type.TypeModifier;
/**
* The Class StreamModule.
*
* @author jmaxwell
*/
public class StreamModule extends SimpleModule {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = -1324033833221219001L;
@Override
public void setupModule(final SetupContext context) {
context.addTypeModifier(new StreamTypeModifier());
context.addSerializers(new StreamSerializers());
}
/**
* The Class StreamTypeModifier.
*/
public static final class StreamTypeModifier extends TypeModifier {
/**
* Tested for both 2.6.x and 2.8.1
*/
@Override
public JavaType modifyType(final JavaType type, final Type jdkType, final TypeBindings context,
final TypeFactory typeFactory) {
if (type.isReferenceType() || type.isContainerType()) {
return type;
}
final Class<?> raw = type.getRawClass();
if (Stream.class.isAssignableFrom(raw)) {
final JavaType[] params = typeFactory.findTypeParameters(type, Stream.class);
if (params == null || params.length == 0) {
return typeFactory.constructReferenceType(raw, TypeFactory.unknownType());
}
return typeFactory.constructCollectionLikeType(raw, params[0]);
}
return type;
}
//
// the 2.8.1 and above way
// @Override
// public JavaType modifyType(JavaType type, Type jdkType, TypeBindings context, TypeFactory typeFactory) {
//
// if (type.isReferenceType() || type.isContainerType()) {
// return type;
// }
//
// Class<?> raw = type.getRawClass();
//
// if (Stream.class.isAssignableFrom(raw)) {
//
// JavaType[] params = typeFactory.findTypeParameters(type, Stream.class);
//
// if (params == null || params.length == 0) {
//
// return ReferenceType.upgradeFrom(type, type.containedTypeOrUnknown(0));
// }
//
// return typeFactory.constructCollectionLikeType(raw, params[0]);
//
// }
// return type;
// }
//
}
/**
* The Class StreamSerializers.
*/
public static final class StreamSerializers extends com.fasterxml.jackson.databind.ser.Serializers.Base {
@Override
public JsonSerializer<?> findCollectionLikeSerializer(final SerializationConfig config,
final CollectionLikeType type, final BeanDescription beanDesc,
final TypeSerializer elementTypeSerializer, final JsonSerializer<Object> elementValueSerializer) {
final Class<?> raw = type.getRawClass();
if (Stream.class.isAssignableFrom(raw)) {
final TypeFactory typeFactory = config.getTypeFactory();
final JavaType[] params = typeFactory.findTypeParameters(type, Stream.class);
final JavaType vt = (params == null || params.length != 1) ? TypeFactory.unknownType() : params[0];
return new StreamSerializer(type.getContentType(), usesStaticTyping(config, beanDesc, null),
BeanSerializerFactory.instance.createTypeSerializer(config, vt));
}
return null;
}
/**
* Uses static typing. Copied from {@link BasicSerializerFactory}
*
* @param config the config
* @param beanDesc the bean desc
* @param typeSer the type ser
* @return true, if successful
*/
private static final boolean usesStaticTyping(final SerializationConfig config, final BeanDescription beanDesc,
final TypeSerializer typeSer) {
/*
* 16-Aug-2010, tatu: If there is a (value) type serializer, we can not force
* static typing; that would make it impossible to handle expected subtypes
*/
if (typeSer != null) {
return false;
}
final AnnotationIntrospector intr = config.getAnnotationIntrospector();
final JsonSerialize.Typing t = intr.findSerializationTyping(beanDesc.getClassInfo());
if (t != null && t != JsonSerialize.Typing.DEFAULT_TYPING) {
return (t == JsonSerialize.Typing.STATIC);
}
return config.isEnabled(MapperFeature.USE_STATIC_TYPING);
}
/**
* The Class StreamSerializer.
*/
public static final class StreamSerializer extends AsArraySerializerBase<Stream<?>> {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = -455534622397905995L;
/**
* Instantiates a new stream serializer.
*
* @param elemType the elem type
* @param staticTyping the static typing
* @param vts the vts
*/
public StreamSerializer(final JavaType elemType, final boolean staticTyping, final TypeSerializer vts) {
super(Stream.class, elemType, staticTyping, vts, null);
}
/**
* Instantiates a new stream serializer.
*
* @param src the src
* @param property the property
* @param vts the vts
* @param valueSerializer the value serializer
*/
public StreamSerializer(final StreamSerializer src, final BeanProperty property, final TypeSerializer vts,
final JsonSerializer<?> valueSerializer) {
super(src, property, vts, valueSerializer, false);
}
@Override
public void serialize(final Stream<?> value, final JsonGenerator gen, final SerializerProvider provider)
throws IOException {
this.serializeContents(value, gen, provider);
}
/**
* withResolved.
*
* @param property the property
* @param vts the vts
* @param elementSerializer the element serializer
* @param unwrapSingle ignored always false since streams are one time use I don't believe we can get a
* single element
* @return the as array serializer base
*/
@Override
public StreamSerializer withResolved(final BeanProperty property, final TypeSerializer vts,
final JsonSerializer<?> elementSerializer, final Boolean unwrapSingle) {
return new StreamSerializer(this, property, vts, elementSerializer);
}
@Override
protected void serializeContents(final Stream<?> value, final JsonGenerator gen,
final SerializerProvider provider) throws IOException {
provider.findValueSerializer(Iterator.class, null)
.serialize(value.iterator(), gen, provider);
}
@Override
public boolean hasSingleElement(final Stream<?> value) {
// no really good way to determine (without consuming stream), so:
return false;
}
@Override
protected StreamSerializer _withValueTypeSerializer(final TypeSerializer vts) {
return new StreamSerializer(this, this._property, vts, this._elementSerializer);
}
}
}
}
Tests:
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
import org.junit.Test;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
import com.fasterxml.jackson.module.mrbean.MrBeanModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import com.theice.cds.common.serialization.json.jackson2.StreamModule;
@SuppressWarnings("javadoc")
public class StreamModuleTest {
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModule(new GuavaModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule())
.registerModule(new ParameterNamesModule())
.registerModule(new AfterburnerModule())
.registerModule(new StreamModule())
.registerModule(new MrBeanModule());
static <T> void assertRoundTrip(final Collection<T> original, final ObjectMapper objectMapper) throws IOException {
final Stream<T> asStream = original.stream();
final String asJsonString = objectMapper.writeValueAsString(asStream);
System.out.println("original: " + original + " -> " + asJsonString);
final Collection<T> fromJsonString = OBJECT_MAPPER.readValue(asJsonString,
new TypeReference<Collection<T>>() {});
assertEquals(original, fromJsonString);
}
@SuppressWarnings("deprecation")
@Test
public void testEmptyStream() throws IOException {
assertRoundTrip(new ArrayList<>(), OBJECT_MAPPER.copy()
.enable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT));
// shouldn't this fail?
assertRoundTrip(new ArrayList<>(), OBJECT_MAPPER.copy()
.disable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS)
.disable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT));
}
@Test
public void testSingleElementStream() throws IOException {
final List<String> collection = new ArrayList<>();
collection.add("element1");
assertRoundTrip(collection, OBJECT_MAPPER.copy()
.enable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
assertRoundTrip(collection, OBJECT_MAPPER.copy()
.disable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
.disable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
// should fail but can't for stream
assertRoundTrip(collection, OBJECT_MAPPER.copy()
.enable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
.disable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
}
@Test
public void testMultipleElementStream() throws IOException {
final List<String> collection = new ArrayList<>();
collection.add("element1");
collection.add("element2");
assertRoundTrip(collection, OBJECT_MAPPER);
}
}
eperretadrian-bakerhmdebenque, KyleChamberlin, goelprateek, npgall, Bill and 3 more
Metadata
Metadata
Assignees
Labels
No labels