Skip to content
This repository has been archived by the owner on Jan 22, 2019. It is now read-only.

Commit

Permalink
Trying to clean up, improve handling of Guava Optional type, wrt po…
Browse files Browse the repository at this point in the history
…lymorphic types
  • Loading branch information
cowtowncoder committed Nov 4, 2013
1 parent d367057 commit 0b3cbb7
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 16 deletions.
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ com.fasterxml.jackson.core.util,
com.fasterxml.jackson.databind,
com.fasterxml.jackson.databind.deser,
com.fasterxml.jackson.databind.deser.std,
com.fasterxml.jackson.databind.introspect,
com.fasterxml.jackson.databind.jsontype,
com.fasterxml.jackson.databind.ser,
com.fasterxml.jackson.databind.ser.std,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
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.KeyDeserializer;
import com.fasterxml.jackson.databind.deser.Deserializers;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapLikeType;
Expand Down Expand Up @@ -261,10 +263,24 @@ public JsonDeserializer<?> findMapLikeDeserializer(MapLikeType type,
public JsonDeserializer<?> findBeanDeserializer(final JavaType type, DeserializationConfig config,
BeanDescription beanDesc) throws JsonMappingException {
Class<?> raw = type.getRawClass();
if(Optional.class.isAssignableFrom(raw)){
return new GuavaOptionalDeserializer(type);
if (Optional.class.isAssignableFrom(raw)){
JsonDeserializer<?> valueDeser = type.getValueHandler();
TypeDeserializer typeDeser = type.getTypeHandler();
return new GuavaOptionalDeserializer(type, typeDeser, valueDeser);
}
return super.findBeanDeserializer(type, config, beanDesc);
}

// Copied from jackson-databind's "BasicDeserializerFactory":
protected JsonDeserializer<Object> findDeserializerFromAnnotation(DeserializationContext ctxt,
Annotated ann)
throws JsonMappingException
{
Object deserDef = ctxt.getAnnotationIntrospector().findDeserializer(ann);
if (deserDef == null) {
return null;
}
return ctxt.deserializerInstance(ann, deserDef);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,114 @@

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.BeanProperty;
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.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.google.common.base.Optional;

public final class GuavaOptionalDeserializer extends StdDeserializer<Optional<?>>
public class GuavaOptionalDeserializer
extends StdDeserializer<Optional<?>>
implements ContextualDeserializer
{
private static final long serialVersionUID = 1L;

private final JavaType _referenceType;
protected final JavaType _referenceType;

protected final JsonDeserializer<?> _valueDeserializer;

protected final TypeDeserializer _valueTypeDeserializer;

@Deprecated // since 2.3,
public GuavaOptionalDeserializer(JavaType valueType) {
this(valueType, null, null);
}

public GuavaOptionalDeserializer(JavaType valueType,
TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser)
{
super(valueType);
_referenceType = valueType.containedType(0);
_valueTypeDeserializer = typeDeser;
_valueDeserializer = valueDeser;
}

@Override
public Optional<?> getNullValue() {
return Optional.absent();
}

/**
* Overridable fluent factory method used for creating contextual
* instances.
*/
public GuavaOptionalDeserializer withResolved(
TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser)
{
return new GuavaOptionalDeserializer(_referenceType,
typeDeser, valueDeser);
}

/*
/**********************************************************
/* Validation, post-processing
/**********************************************************
*/

/**
* Method called to finalize setup of this deserializer,
* after deserializer itself has been registered. This
* is needed to handle recursive and transitive dependencies.
*/
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
{
JsonDeserializer<?> deser = _valueDeserializer;
TypeDeserializer typeDeser = _valueTypeDeserializer;
if (deser == null) {
deser = ctxt.findContextualValueDeserializer(_referenceType, property);
}
if (typeDeser != null) {
typeDeser = typeDeser.forProperty(property);
}
if (deser == _valueDeserializer && typeDeser == _valueTypeDeserializer) {
return this;
}
return withResolved(typeDeser, deser);
}

@Override
public Optional<?> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
JsonProcessingException {
Object reference = ctxt.findRootValueDeserializer(_referenceType).deserialize(jp, ctxt);
JsonProcessingException
{
Object reference = _valueDeserializer.deserialize(jp, ctxt);
return Optional.of(reference);
}

@Override
public Optional<?> deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
throws IOException, JsonProcessingException {
return deserialize(jp, ctxt);
throws IOException, JsonProcessingException
{
final JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NULL) {
return getNullValue();
}
/* 03-Nov-2013, tatu: This gets rather tricky with "natural" types
* (String, Integer, Boolean), which do NOT include type information.
* These might actually be handled ok except that nominal type here
* is `Optional`, so special handling is not invoked; instead, need
* to do a work-around here.
*/
if (t != null && t.isScalarValue()) {
return deserialize(jp, ctxt);
}
Object ref = _valueTypeDeserializer.deserializeTypedFromAny(jp, ctxt);
return Optional.of(ref);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
return (_createContextual(type, kd, etd, ed, creatorMethod));
}

protected abstract JsonDeserializer<?> _createContextual(MapLikeType type,
KeyDeserializer keyDeserializer, TypeDeserializer typeDeserializer,
JsonDeserializer<?> elementDeserializer, Method method);
protected abstract JsonDeserializer<?> _createContextual(MapLikeType t,
KeyDeserializer kd, TypeDeserializer typeDeserializer,
JsonDeserializer<?> ed, Method method);

@Override
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public Holder(Object v) {
value = v;
}
}

/*
/**********************************************************************
/* Unit tests for verifying handling in absence of module registration
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.fasterxml.jackson.datatype.guava;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

import com.fasterxml.jackson.core.type.TypeReference;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.google.common.base.Optional;

public class TestOptional extends BaseTest
Expand All @@ -21,6 +24,26 @@ public static final class OptionalGenericData<T>{
private Optional<T> myData;
}

@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class)
public static class Unit
{
// @JsonIdentityReference(alwaysAsId=true)
public Optional<Unit> baseUnit;

public Unit() { }
public Unit(Optional<Unit> u) { baseUnit = u; }

public void link(Unit u) {
baseUnit = Optional.of(u);
}
}

/*
/**********************************************************************
/* Test methods
/**********************************************************************
*/

public void testDeserAbsent() throws Exception {
Optional<?> value = MAPPER.readValue("null", new TypeReference<Optional<String>>() {});
assertFalse(value.isPresent());
Expand Down Expand Up @@ -99,17 +122,32 @@ public void testSerOptNull() throws Exception {
assertEquals("{}", value);
}

public void testWithTypingEnabled() throws Exception {
public void testWithTypingEnabled() throws Exception
{
final ObjectMapper objectMapper = mapperWithModule();
// ENABLE TYPING
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);

final OptionalData myData = new OptionalData();
myData.myString = Optional.fromNullable("");
myData.myString = Optional.fromNullable("abc");

final String json = objectMapper.writeValueAsString(myData);

final OptionalData deserializedMyData = objectMapper.readValue(json, OptionalData.class);
assertEquals(myData.myString, deserializedMyData.myString);
}

// for [Issue#17]
public void testObjectId() throws Exception
{
final Unit input = new Unit();
input.link(input);
String json = MAPPER.writeValueAsString(input);
Unit result = MAPPER.readValue(json, Unit.class);
assertNotNull(result);
assertNotNull(result.baseUnit);
assertTrue(result.baseUnit.isPresent());
Unit base = result.baseUnit.get();
assertSame(result, base);
}
}

0 comments on commit 0b3cbb7

Please sign in to comment.