Skip to content

Commit

Permalink
Issue #20 Add support for enum subtype deserialization
Browse files Browse the repository at this point in the history
  • Loading branch information
nmorel committed Jun 14, 2014
1 parent 0eab750 commit a43b11b
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.github.nmorel.gwtjackson.client.JsonDeserializationContext;
import com.github.nmorel.gwtjackson.client.JsonDeserializer;
import com.github.nmorel.gwtjackson.client.JsonDeserializerParameters;
Expand All @@ -36,7 +37,8 @@
*
* @author Nicolas Morel
*/
public abstract class AbstractBeanJsonDeserializer<T> extends JsonDeserializer<T> {
public abstract class AbstractBeanJsonDeserializer<T> extends JsonDeserializer<T> implements InternalDeserializer<T,
AbstractBeanJsonDeserializer<T>> {

private final InstanceBuilder<T> instanceBuilder;

Expand Down Expand Up @@ -141,8 +143,10 @@ public T doDeserialize( JsonReader reader, JsonDeserializationContext ctx, JsonD
final IdentityDeserializationInfo identityInfo = null == params.getIdentityInfo() ? defaultIdentityInfo : params.getIdentityInfo();
final TypeDeserializationInfo typeInfo = null == params.getTypeInfo() ? defaultTypeInfo : params.getTypeInfo();

// If it's not a json object, it must be an identifier
if ( null != identityInfo && !JsonToken.BEGIN_OBJECT.equals( reader.peek() ) ) {
JsonToken token = reader.peek();

// If it's not a json object or array, it must be an identifier
if ( null != identityInfo && !JsonToken.BEGIN_OBJECT.equals( token ) && !JsonToken.BEGIN_ARRAY.equals( token ) ) {
Object id;
if ( identityInfo.isProperty() ) {
BeanPropertyDeserializer<T, ?> propertyDeserializer = deserializers.get( identityInfo.getPropertyName() );
Expand All @@ -160,7 +164,16 @@ public T doDeserialize( JsonReader reader, JsonDeserializationContext ctx, JsonD
T result;

if ( null != typeInfo ) {
switch ( typeInfo.getInclude() ) {

As include;
if ( JsonToken.BEGIN_ARRAY.equals( token ) ) {
// in case of an enum subtype, we can have a wrapper array even if the user specified As.PROPERTY
include = As.WRAPPER_ARRAY;
} else {
include = typeInfo.getInclude();
}

switch ( include ) {
case PROPERTY:
// the type info is the first property of the object
reader.beginObject();
Expand All @@ -183,7 +196,8 @@ public T doDeserialize( JsonReader reader, JsonDeserializationContext ctx, JsonD
throw ctx.traceError( "Cannot find the property " + typeInfo
.getPropertyName() + " containing the type information", reader );
}
result = deserializeSubtype( reader, ctx, params, identityInfo, typeInfo, typeInfoProperty, bufferedProperties );
result = getDeserializer( reader, ctx, typeInfo, typeInfoProperty )
.deserializeInline( reader, ctx, params, identityInfo, typeInfo, typeInfoProperty, bufferedProperties );
reader.endObject();
break;

Expand All @@ -192,9 +206,8 @@ public T doDeserialize( JsonReader reader, JsonDeserializationContext ctx, JsonD
// info and the value the object
reader.beginObject();
String typeInfoWrapObj = reader.nextName();
reader.beginObject();
result = deserializeSubtype( reader, ctx, params, identityInfo, typeInfo, typeInfoWrapObj, null );
reader.endObject();
result = getDeserializer( reader, ctx, typeInfo, typeInfoWrapObj )
.deserializeWrapped( reader, ctx, params, identityInfo, typeInfo, typeInfoWrapObj );
reader.endObject();
break;

Expand All @@ -203,26 +216,32 @@ public T doDeserialize( JsonReader reader, JsonDeserializationContext ctx, JsonD
// info and the second one the object
reader.beginArray();
String typeInfoWrapArray = reader.nextString();
reader.beginObject();
result = deserializeSubtype( reader, ctx, params, identityInfo, typeInfo, typeInfoWrapArray, null );
reader.endObject();
result = getDeserializer( reader, ctx, typeInfo, typeInfoWrapArray )
.deserializeWrapped( reader, ctx, params, identityInfo, typeInfo, typeInfoWrapArray );
reader.endArray();
break;

default:
throw ctx.traceError( "JsonTypeInfo.As." + typeInfo.getInclude() + " is not supported", reader );
}
} else if ( null != instanceBuilder ) {
reader.beginObject();
result = deserializeObject( reader, ctx, params, identityInfo, null, null, null );
reader.endObject();
result = deserializeWrapped( reader, ctx, params, identityInfo, null, null );
} else {
throw ctx.traceError( "Cannot instantiate the type " + getDeserializedType().getName(), reader );
}

return result;
}

@Override
public T deserializeWrapped( JsonReader reader, JsonDeserializationContext ctx, JsonDeserializerParameters params,
IdentityDeserializationInfo identityInfo, TypeDeserializationInfo typeInfo, String typeInformation ) {
reader.beginObject();
T result = deserializeInline( reader, ctx, params, identityInfo, typeInfo, typeInformation, null );
reader.endObject();
return result;
}

/**
* Deserializes all the properties of the bean. The {@link JsonReader} must be in a json object.
*
Expand All @@ -231,8 +250,8 @@ public T doDeserialize( JsonReader reader, JsonDeserializationContext ctx, JsonD
* @param type in case of a subtype, it's the corresponding type value
* @param bufferedProperties Buffered properties in case the type info property was not in 1st position
*/
public final T deserializeObject( final JsonReader reader, final JsonDeserializationContext ctx, JsonDeserializerParameters params,
IdentityDeserializationInfo identityInfo, TypeDeserializationInfo typeInfo, String type,
@Override
public final T deserializeInline( final JsonReader reader, final JsonDeserializationContext ctx, JsonDeserializerParameters params, IdentityDeserializationInfo identityInfo, TypeDeserializationInfo typeInfo, String type,
Map<String, String> bufferedProperties ) {
final boolean ignoreUnknown = params.isIgnoreUnknown() || isDefaultIgnoreUnknown();
final Set<String> ignoredProperties;
Expand Down Expand Up @@ -291,8 +310,8 @@ public final T deserializeObject( final JsonReader reader, final JsonDeserializa
return bean;
}

private Map<String, String> readIdentityProperty( IdentityDeserializationInfo identityInfo, T bean, Map<String, String> bufferedProperties,
JsonReader reader, final JsonDeserializationContext ctx, Set<String> ignoredProperties ) {
private Map<String, String> readIdentityProperty( IdentityDeserializationInfo identityInfo, T bean, Map<String,
String> bufferedProperties, JsonReader reader, final JsonDeserializationContext ctx, Set<String> ignoredProperties ) {
if ( null == identityInfo ) {
return bufferedProperties;
}
Expand Down Expand Up @@ -350,8 +369,8 @@ private Map<String, String> readIdentityProperty( IdentityDeserializationInfo id
return bufferedProperties;
}

private void flushBufferedProperties( T bean, Map<String, String> bufferedProperties, Set<String> requiredPropertiesLeft, JsonDeserializationContext ctx,
boolean ignoreUnknown, Set<String> ignoredProperties ) {
private void flushBufferedProperties( T bean, Map<String, String> bufferedProperties, Set<String> requiredPropertiesLeft,
JsonDeserializationContext ctx, boolean ignoreUnknown, Set<String> ignoredProperties ) {
if ( null != bufferedProperties && !bufferedProperties.isEmpty() ) {
for ( Entry<String, String> bufferedProperty : bufferedProperties.entrySet() ) {
String propertyName = bufferedProperty.getKey();
Expand Down Expand Up @@ -381,19 +400,19 @@ private void flushBufferedProperties( T bean, Map<String, String> bufferedProper
return property;
}

public final T deserializeSubtype( JsonReader reader, JsonDeserializationContext ctx, JsonDeserializerParameters params,
IdentityDeserializationInfo identityInfo, TypeDeserializationInfo typeInfo,
String typeInformation, Map<String, String> bufferedProperties ) {
private InternalDeserializer<T, ? extends JsonDeserializer<T>> getDeserializer( JsonReader reader, JsonDeserializationContext ctx,
TypeDeserializationInfo typeInfo,
String typeInformation ) {
Class typeClass = typeInfo.getTypeClass( typeInformation );
if ( null == typeClass ) {
throw ctx.traceError( "Could not find the type associated to " + typeInformation, reader );
}

return getDeserializer( reader, ctx, typeClass )
.deserializeObject( reader, ctx, params, identityInfo, typeInfo, typeInformation, bufferedProperties );
return getDeserializer( reader, ctx, typeClass );
}

private AbstractBeanJsonDeserializer<T> getDeserializer( JsonReader reader, JsonDeserializationContext ctx, Class typeClass ) {
private InternalDeserializer<T, ? extends JsonDeserializer<T>> getDeserializer( JsonReader reader, JsonDeserializationContext ctx,
Class typeClass ) {
if ( typeClass == getDeserializedType() ) {
return this;
}
Expand All @@ -402,7 +421,12 @@ private AbstractBeanJsonDeserializer<T> getDeserializer( JsonReader reader, Json
if ( null == deserializer ) {
throw ctx.traceError( "No deserializer found for the type " + typeClass.getName(), reader );
}
return (AbstractBeanJsonDeserializer<T>) deserializer.getDeserializer();
return deserializer;
}

@Override
public AbstractBeanJsonDeserializer<T> getDeserializer() {
return this;
}

@Override
Expand All @@ -411,7 +435,7 @@ public void setBackReference( String referenceName, Object reference, T value, J
return;
}

AbstractBeanJsonDeserializer<T> deserializer = getDeserializer( null, ctx, value.getClass() );
JsonDeserializer<T> deserializer = getDeserializer( null, ctx, value.getClass() ).getDeserializer();
if ( deserializer.getClass() != getClass() ) {
// we test if it's not this deserializer to avoid an infinite loop
deserializer.setBackReference( referenceName, reference, value, ctx );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2014 Nicolas Morel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.github.nmorel.gwtjackson.client.deser.bean;

import java.util.Map;

import com.github.nmorel.gwtjackson.client.JsonDeserializationContext;
import com.github.nmorel.gwtjackson.client.JsonDeserializer;
import com.github.nmorel.gwtjackson.client.JsonDeserializerParameters;
import com.github.nmorel.gwtjackson.client.stream.JsonReader;

/**
* Interface hiding the actual implementation doing the bean deserialization.
*
* @author Nicolas Morel.
*/
interface InternalDeserializer<T, S extends JsonDeserializer<T>> {

S getDeserializer();

T deserializeInline( JsonReader reader, JsonDeserializationContext ctx, JsonDeserializerParameters params,
IdentityDeserializationInfo identityInfo, TypeDeserializationInfo typeInfo, String typeInformation, Map<String,
String> bufferedProperties );

T deserializeWrapped( JsonReader reader, JsonDeserializationContext ctx, JsonDeserializerParameters params,
IdentityDeserializationInfo identityInfo, TypeDeserializationInfo typeInfo, String typeInformation );

}

Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,61 @@

package com.github.nmorel.gwtjackson.client.deser.bean;

import java.util.Map;

import com.github.nmorel.gwtjackson.client.JsonDeserializationContext;
import com.github.nmorel.gwtjackson.client.JsonDeserializer;
import com.github.nmorel.gwtjackson.client.JsonDeserializerParameters;
import com.github.nmorel.gwtjackson.client.deser.EnumJsonDeserializer;
import com.github.nmorel.gwtjackson.client.stream.JsonReader;

/**
* Delegate the deserialization of a subtype to a corresponding {@link AbstractBeanJsonDeserializer}
*
* @author Nicolas Morel
*/
public abstract class SubtypeDeserializer<T> extends HasDeserializer<T, AbstractBeanJsonDeserializer<T>> {}
public abstract class SubtypeDeserializer<T, D extends JsonDeserializer<T>> extends HasDeserializer<T,
D> implements InternalDeserializer<T, D> {

/**
* Delegate the deserialization of a subtype to a corresponding {@link AbstractBeanJsonDeserializer}
*
* @author Nicolas Morel
*/
public abstract static class BeanSubtypeDeserializer<T> extends SubtypeDeserializer<T, AbstractBeanJsonDeserializer<T>> {

@Override
public T deserializeInline( JsonReader reader, JsonDeserializationContext ctx, JsonDeserializerParameters params,
IdentityDeserializationInfo identityInfo, TypeDeserializationInfo typeInfo, String typeInformation,
Map<String, String> bufferedProperties ) {
return getDeserializer().deserializeInline( reader, ctx, params, identityInfo, typeInfo, typeInformation, bufferedProperties );
}

@Override
public T deserializeWrapped( JsonReader reader, JsonDeserializationContext ctx, JsonDeserializerParameters params,
IdentityDeserializationInfo identityInfo, TypeDeserializationInfo typeInfo, String typeInformation ) {
return getDeserializer().deserializeWrapped( reader, ctx, params, identityInfo, typeInfo, typeInformation );
}
}

/**
* Delegate the deserialization of an enum subtype to a corresponding {@link EnumJsonDeserializer}
*
* @author Nicolas Morel
*/
public abstract static class EnumSubtypeDeserializer<E extends Enum<E>> extends SubtypeDeserializer<E, EnumJsonDeserializer<E>> {

@Override
public E deserializeInline( JsonReader reader, JsonDeserializationContext ctx, JsonDeserializerParameters params,
IdentityDeserializationInfo identityInfo, TypeDeserializationInfo typeInfo, String typeInformation,
Map<String, String> bufferedProperties ) {
throw ctx.traceError( "Cannot have an object when deserializing an enum" );
}

@Override
public E deserializeWrapped( JsonReader reader, JsonDeserializationContext ctx, JsonDeserializerParameters params,
IdentityDeserializationInfo identityInfo, TypeDeserializationInfo typeInfo, String typeInformation ) {
return getDeserializer().deserialize( reader, ctx, params );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@
import java.util.Map.Entry;
import java.util.Set;

import com.github.nmorel.gwtjackson.client.deser.EnumJsonDeserializer;
import com.github.nmorel.gwtjackson.client.deser.bean.HasDeserializerAndParameters;
import com.github.nmorel.gwtjackson.client.deser.bean.IdentityDeserializationInfo;
import com.github.nmorel.gwtjackson.client.deser.bean.SimpleStringMap;
import com.github.nmorel.gwtjackson.client.deser.bean.SubtypeDeserializer;
import com.github.nmorel.gwtjackson.client.deser.bean.SubtypeDeserializer.BeanSubtypeDeserializer;
import com.github.nmorel.gwtjackson.client.deser.bean.SubtypeDeserializer.EnumSubtypeDeserializer;
import com.github.nmorel.gwtjackson.client.stream.JsonReader;
import com.github.nmorel.gwtjackson.client.stream.JsonToken;
import com.github.nmorel.gwtjackson.rebind.FieldAccessor.Accessor;
Expand Down Expand Up @@ -687,16 +690,23 @@ private void generateInitMapSubtypeClassToDeserializerMethod( SourceWriter sourc
source.println();

for ( JClassType subtype : subtypes ) {
if ( null != subtype.isEnum() ) {
// TODO Issue #20
continue;
String subtypeClass;
String deserializerClass;
if ( null == subtype.isEnum() ) {
subtypeClass = BeanSubtypeDeserializer.class.getCanonicalName();
deserializerClass = String.format( "%s<?>", ABSTRACT_BEAN_JSON_DESERIALIZER_CLASS );
} else {
// enum can be a subtype for interface. In this case, we handle them differently
subtypeClass = EnumSubtypeDeserializer.class.getCanonicalName();
deserializerClass = String.format( "%s<%s>", EnumJsonDeserializer.class.getName(), getQualifiedClassName( subtype ) );
}
source.println( "map.put( %s.class, new %s<%s>() {", subtype.getQualifiedSourceName(), SubtypeDeserializer.class
.getName(), getQualifiedClassName( subtype ) );

source.println( "map.put( %s.class, new %s<%s>() {", subtype
.getQualifiedSourceName(), subtypeClass, getQualifiedClassName( subtype ) );
source.indent();

source.println( "@Override" );
source.println( "protected %s<?> newDeserializer() {", ABSTRACT_BEAN_JSON_DESERIALIZER_CLASS );
source.println( "protected %s newDeserializer() {", deserializerClass );
source.indent();
source.println( "return %s;", getJsonDeserializerFromType( subtype ).getInstance() );
source.outdent();
Expand Down

0 comments on commit a43b11b

Please sign in to comment.