Skip to content

Commit

Permalink
Collection serializers and deserializer should be contextual (#155)
Browse files Browse the repository at this point in the history
* Reproduce issue

* ArraySerializer should be contextual

* ArrayDeserializer should be contextual
  • Loading branch information
mincong-h authored Jun 7, 2020
1 parent 3af8494 commit cea184d
Show file tree
Hide file tree
Showing 13 changed files with 391 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;

import java.io.IOException;
import java.util.ArrayList;
Expand All @@ -34,33 +33,74 @@
import static com.fasterxml.jackson.core.JsonToken.END_ARRAY;
import static com.fasterxml.jackson.core.JsonToken.VALUE_NULL;

abstract class ArrayDeserializer<T> extends ValueDeserializer<T> {
abstract class ArrayDeserializer<T> extends ValueDeserializer<T> implements ContextualDeserializer {

private static final long serialVersionUID = 1L;

private final JavaType valueType;
private final boolean deserializeNullAsEmptyCollection;
protected final JavaType collectionType;
protected final JavaType elementType;
protected final TypeDeserializer elementTypeDeserializer;
protected final JsonDeserializer<?> elementDeserializer;
protected final boolean deserializeNullAsEmptyCollection;

ArrayDeserializer(JavaType valueType, int typeCount, boolean deserializeNullAsEmptyCollection) {
super(valueType, typeCount);
this.valueType = valueType;
ArrayDeserializer(JavaType collectionType, int typeCount, JavaType elementType,
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer,
boolean deserializeNullAsEmptyCollection) {
super(collectionType, typeCount);
this.collectionType = collectionType;
this.elementType = elementType;
this.elementTypeDeserializer = elementTypeDeserializer;
this.elementDeserializer = elementDeserializer;
this.deserializeNullAsEmptyCollection = deserializeNullAsEmptyCollection;
}

abstract T create(List<Object> list, DeserializationContext ctxt) throws JsonMappingException;

/**
* Creates a new deserializer from the original one (this).
*
* @param elementTypeDeserializer the new deserializer for the element type
* @param elementDeserializer the new deserializer for the element itself
* @return a new deserializer
*/
abstract ArrayDeserializer<T> createDeserializer(TypeDeserializer elementTypeDeserializer,
JsonDeserializer<?> elementDeserializer);

@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
JsonDeserializer<?> elementDeser = elementDeserializer;
TypeDeserializer elementTypeDeser = elementTypeDeserializer;

if (elementDeser == null) {
elementDeser = ctxt.findContextualValueDeserializer(elementType, property);
} else {
elementDeser = ctxt.handleSecondaryContextualization(elementDeser, property, elementType);
}
if (elementTypeDeser != null) {
elementTypeDeser = elementTypeDeser.forProperty(property);
}
return createDeserializer(elementTypeDeser, elementDeser);
}

@Override
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonDeserializer<?> deserializer = deserializer(0);
List<Object> list = new ArrayList<>();
if (!p.isExpectedStartArrayToken()) {
throw mappingException(ctxt, valueType.getRawClass(), p.getCurrentToken());
public T deserialize(JsonParser parser, DeserializationContext context) throws IOException {
if (!parser.isExpectedStartArrayToken()) {
throw mappingException(context, collectionType.getRawClass(), parser.getCurrentToken());
}
for (JsonToken jsonToken = p.nextToken(); jsonToken != END_ARRAY; jsonToken = p.nextToken()) {
Object value = (jsonToken != VALUE_NULL) ? deserializer.deserialize(p, ctxt) : deserializer.getNullValue(ctxt);
list.add(value);

List<Object> elements = new ArrayList<>();
for (JsonToken jsonToken = parser.nextToken(); jsonToken != END_ARRAY; jsonToken = parser.nextToken()) {
Object element;
if (jsonToken == VALUE_NULL) {
element = elementDeserializer.getNullValue(context);
} else if (elementTypeDeserializer == null) {
element = elementDeserializer.deserialize(parser, context);
} else {
element = elementDeserializer.deserializeWithType(parser, context, elementTypeDeserializer);
}
elements.add(element);
}
return create(list, ctxt);
return create(elements, context);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@
*/
package io.vavr.jackson.datatype.deserialize;

import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import io.vavr.collection.PriorityQueue;

import java.io.Serializable;
Expand All @@ -32,17 +31,34 @@ class PriorityQueueDeserializer extends ArrayDeserializer<PriorityQueue<?>> {

private static final long serialVersionUID = 1L;

private final JavaType javaType;
PriorityQueueDeserializer(JavaType collectionType, JavaType elementType, TypeDeserializer elementTypeDeserializer,
JsonDeserializer<?> elementDeserializer, boolean deserializeNullAsEmptyCollection) {
super(collectionType, 1, elementType, elementTypeDeserializer, elementDeserializer, deserializeNullAsEmptyCollection);
}

PriorityQueueDeserializer(JavaType valueType, boolean deserializeNullAsEmptyCollection) {
super(valueType, 1, deserializeNullAsEmptyCollection);
javaType = valueType;
/**
* Creates a new deserializer from the original one.
*
* @param origin the original deserializer
* @param elementTypeDeserializer the new deserializer for the element type
* @param elementDeserializer the new deserializer for the element itself
*/
PriorityQueueDeserializer(PriorityQueueDeserializer origin, TypeDeserializer elementTypeDeserializer,
JsonDeserializer<?> elementDeserializer) {
this(origin.collectionType, origin.elementType, elementTypeDeserializer, elementDeserializer,
origin.deserializeNullAsEmptyCollection);
}

@SuppressWarnings("unchecked")
@Override
PriorityQueue<?> create(List<Object> list, DeserializationContext ctxt) throws JsonMappingException {
checkContainedTypeIsComparable(ctxt, javaType.containedTypeOrUnknown(0));
checkContainedTypeIsComparable(ctxt, collectionType.containedTypeOrUnknown(0));
return PriorityQueue.ofAll((Comparator<Object> & Serializable) (o1, o2) -> ((Comparable) o1).compareTo(o2), list);
}

@Override
PriorityQueueDeserializer createDeserializer(TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer) {
return new PriorityQueueDeserializer(this, elementTypeDeserializer, elementDeserializer);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,47 +19,58 @@
*/
package io.vavr.jackson.datatype.deserialize;

import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import io.vavr.collection.Array;
import io.vavr.collection.IndexedSeq;
import io.vavr.collection.Queue;
import io.vavr.collection.Seq;
import io.vavr.collection.Stream;
import io.vavr.collection.Vector;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import io.vavr.collection.*;

import java.util.List;

class SeqDeserializer extends ArrayDeserializer<Seq<?>> {

private static final long serialVersionUID = 1L;

private final JavaType javaType;
SeqDeserializer(JavaType collectionType, JavaType elementType, TypeDeserializer elementTypeDeserializer,
JsonDeserializer<?> elementDeserializer, boolean deserializeNullAsEmptyCollection) {
super(collectionType, 1, elementType, elementTypeDeserializer, elementDeserializer, deserializeNullAsEmptyCollection);
}

SeqDeserializer(JavaType valueType, boolean deserializeNullAsEmptyCollection) {
super(valueType, 1, deserializeNullAsEmptyCollection);
javaType = valueType;
/**
* Creates a new deserializer from the original one.
*
* @param origin the original deserializer
* @param elementTypeDeserializer the new deserializer for the element type
* @param elementDeserializer the new deserializer for the element itself
*/
private SeqDeserializer(SeqDeserializer origin, TypeDeserializer elementTypeDeserializer,
JsonDeserializer<?> elementDeserializer) {
this(origin.collectionType, origin.elementType, elementTypeDeserializer, elementDeserializer,
origin.deserializeNullAsEmptyCollection);
}

@Override
Seq<?> create(List<Object> result, DeserializationContext ctxt) throws JsonMappingException {
if (Array.class.isAssignableFrom(javaType.getRawClass())) {
if (Array.class.isAssignableFrom(collectionType.getRawClass())) {
return Array.ofAll(result);
}
if (Queue.class.isAssignableFrom(javaType.getRawClass())) {
if (Queue.class.isAssignableFrom(collectionType.getRawClass())) {
return Queue.ofAll(result);
}
if (Stream.class.isAssignableFrom(javaType.getRawClass())) {
if (Stream.class.isAssignableFrom(collectionType.getRawClass())) {
return Stream.ofAll(result);
}
if (Vector.class.isAssignableFrom(javaType.getRawClass())) {
if (Vector.class.isAssignableFrom(collectionType.getRawClass())) {
return Vector.ofAll(result);
}
if (IndexedSeq.class.isAssignableFrom(javaType.getRawClass())) {
if (IndexedSeq.class.isAssignableFrom(collectionType.getRawClass())) {
return Array.ofAll(result);
}
// default deserialization [...] -> Seq
return io.vavr.collection.List.ofAll(result);
}

@Override
SeqDeserializer createDeserializer(TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer) {
return new SeqDeserializer(this, elementTypeDeserializer, elementDeserializer);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@

import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import io.vavr.collection.HashSet;
import io.vavr.collection.Set;

Expand All @@ -33,24 +35,41 @@ class SetDeserializer extends ArrayDeserializer<Set<?>> {

private static final long serialVersionUID = 1L;

private final JavaType javaType;
SetDeserializer(JavaType collectionType, JavaType elementType, TypeDeserializer elementTypeDeserializer,
JsonDeserializer<?> elementDeserializer, boolean deserializeNullAsEmptyCollection) {
super(collectionType, 1, elementType, elementTypeDeserializer, elementDeserializer, deserializeNullAsEmptyCollection);
}

SetDeserializer(JavaType valueType, boolean deserializeNullAsEmptyCollection) {
super(valueType, 1, deserializeNullAsEmptyCollection);
javaType = valueType;
/**
* Creates a new deserializer from the original one.
*
* @param origin the original deserializer
* @param elementTypeDeserializer the new deserializer for the element type
* @param elementDeserializer the new deserializer for the element itself
*/
private SetDeserializer(SetDeserializer origin, TypeDeserializer elementTypeDeserializer,
JsonDeserializer<?> elementDeserializer) {
this(origin.collectionType, origin.elementType, elementTypeDeserializer, elementDeserializer,
origin.deserializeNullAsEmptyCollection);
}

@SuppressWarnings("unchecked")
@Override
Set<?> create(List<Object> result, DeserializationContext ctx) throws JsonMappingException {
if (io.vavr.collection.SortedSet.class.isAssignableFrom(javaType.getRawClass())) {
checkContainedTypeIsComparable(ctx, javaType.containedTypeOrUnknown(0));
if (io.vavr.collection.SortedSet.class.isAssignableFrom(collectionType.getRawClass())) {
checkContainedTypeIsComparable(ctx, collectionType.containedTypeOrUnknown(0));
return io.vavr.collection.TreeSet.ofAll((Comparator<Object> & Serializable) (o1, o2) -> ((Comparable) o1).compareTo(o2), result);
}
if (io.vavr.collection.LinkedHashSet.class.isAssignableFrom(javaType.getRawClass())) {
if (io.vavr.collection.LinkedHashSet.class.isAssignableFrom(collectionType.getRawClass())) {
return io.vavr.collection.LinkedHashSet.ofAll(result);
}
// default deserialization [...] -> Set
return HashSet.ofAll(result);
}

@Override
SetDeserializer createDeserializer(TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer) {
return new SetDeserializer(this, elementTypeDeserializer, elementDeserializer);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -152,25 +152,28 @@ public JsonDeserializer<?> findReferenceDeserializer(ReferenceType type,
}

@Override
public JsonDeserializer<?> findCollectionLikeDeserializer(CollectionLikeType type,
public JsonDeserializer<?> findCollectionLikeDeserializer(CollectionLikeType collectionType,
DeserializationConfig config, BeanDescription beanDesc,
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer)
throws JsonMappingException
{
Class<?> raw = type.getRawClass();
Class<?> raw = collectionType.getRawClass();
if (raw == CharSeq.class) {
return new CharSeqDeserializer(type);
return new CharSeqDeserializer(collectionType);
}
if (Seq.class.isAssignableFrom(raw)) {
return new SeqDeserializer(type, settings.deserializeNullAsEmptyCollection());
return new SeqDeserializer(collectionType, collectionType.getContentType(), elementTypeDeserializer,
elementDeserializer, settings.deserializeNullAsEmptyCollection());
}
if (Set.class.isAssignableFrom(raw)) {
return new SetDeserializer(type, settings.deserializeNullAsEmptyCollection());
return new SetDeserializer(collectionType, collectionType.getContentType(), elementTypeDeserializer,
elementDeserializer, settings.deserializeNullAsEmptyCollection());
}
if (PriorityQueue.class.isAssignableFrom(raw)) {
return new PriorityQueueDeserializer(type, settings.deserializeNullAsEmptyCollection());
return new PriorityQueueDeserializer(collectionType, collectionType.getContentType(),
elementTypeDeserializer, elementDeserializer, settings.deserializeNullAsEmptyCollection());
}
return super.findCollectionLikeDeserializer(type, config, beanDesc, elementTypeDeserializer, elementDeserializer);
return super.findCollectionLikeDeserializer(collectionType, config, beanDesc, elementTypeDeserializer, elementDeserializer);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,35 @@
*/
package io.vavr.jackson.datatype.serialize;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.type.CollectionLikeType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.vavr.Value;

import java.io.IOException;
import java.util.ArrayList;

class ArraySerializer<T extends Value<?>> extends ValueSerializer<T> {
class ArraySerializer<T extends Value<?>> extends ValueSerializer<T> implements ContextualSerializer {

private static final long serialVersionUID = 1L;

ArraySerializer(JavaType type) {
super(type);
ArraySerializer(JavaType collectionType, BeanProperty property) {
super(collectionType, property);
}

ArraySerializer(JavaType collectionType) {
this(collectionType, null);
}

/**
* Creates a new serializer from the original one.
*
* @param origin the original serializer
* @param property the new bean property
*/
ArraySerializer(ArraySerializer<T> origin, BeanProperty property) {
this(origin.type, property);
}

@Override
Expand All @@ -51,4 +65,13 @@ JavaType emulatedJavaType(JavaType type, TypeFactory typeFactory) {
public boolean isEmpty(SerializerProvider provider, T value) {
return value.isEmpty();
}

@Override
public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property)
throws JsonMappingException {
if (property == beanProperty) {
return this;
}
return new ArraySerializer<>(this, property);
}
}
Loading

0 comments on commit cea184d

Please sign in to comment.