I've faced with a requirement to deserialize fields that possibly can be transient using XStream 1.4.2. Despite of that, such fields may be annotated with both #XStreamAlias and #XStreamAsAttribute. Yes, I know, it sounds weird, and this is an indicator of bad design, but this is what I currently have. Since XStream offers a way to specify custom converter, I tried to extend com.thoughtworks.xstream.converters.reflection.ReflectionConverter in order to override the default way of omitting all transient fields trying to make XStream allow to deserialize them. However, I've fully stuck having two ideas to implement such a converter, but none of them works. So here is what I tried:
The 1st way doesn't work:
public final class TransientSimpleConverter extends ReflectionConverter {
private final Class<?> type;
private TransientSimpleConverter(Class<?> type, Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, reflectionProvider);
this.type = type;
}
public static TransientSimpleConverter transientSimpleConverter(Class<?> type, XStream xStream) {
return new TransientSimpleConverter(type, xStream.getMapper(), xStream.getReflectionProvider());
}
#Override
protected boolean shouldUnmarshalTransientFields() {
return true;
}
#Override
public boolean canConvert(Class type) {
return this.type == type;
}
}
The 2nd way doesn't work either:
public final class TransientComplexConverter extends ReflectionConverter {
private final Class<?> type;
private TransientComplexConverter(Class<?> type, Mapper mapper, ReflectionProvider provider) {
super(mapper, provider);
this.type = type;
}
public static TransientComplexConverter transientComplexConverter(Class<?> type, Mapper mapper, Iterable<String> fieldNames) {
return new TransientComplexConverter(type, mapper, TransientHackReflectionProvider.transientHackReflectionProvider(type, fieldNames));
}
#Override
public boolean canConvert(Class type) {
return this.type == type;
}
private static final class TransientHackReflectionProvider extends PureJavaReflectionProvider {
private final Class<?> type;
private final Collection<Field> allowedFields;
private final Collection<String> allowedAliases;
private TransientHackReflectionProvider(Class<?> type, Collection<Field> allowedFields, Collection<String> allowedAliases) {
this.type = type;
this.allowedFields = allowedFields;
this.allowedAliases = allowedAliases;
}
public static TransientHackReflectionProvider transientHackReflectionProvider(final Class<?> type, Iterable<String> fieldNames) {
final Collection<Field> allowedFields = from(fieldNames).transform(new Function<String, Field>() {
#Override
public Field apply(String name) {
return field(type, name);
}
}).toList();
final Collection<String> allowedAliases = transform(allowedFields, new Function<Field, String>() {
#Override
public String apply(Field f) {
return f.getName();
}
});
return new TransientHackReflectionProvider(type, allowedFields, allowedAliases);
}
#Override
protected boolean fieldModifiersSupported(Field field) {
return allowedFields.contains(field) ? true : super.fieldModifiersSupported(field);
}
#Override
public boolean fieldDefinedInClass(String fieldName, Class type) {
return type == this.type && allowedAliases.contains(fieldName) ? true : super.fieldDefinedInClass(fieldName, type);
}
private static final Field field(Class<?> type, String name) {
try {
final Field field = type.getDeclaredField(name);
checkArgument(isTransient(field.getModifiers()), name + " is not transient");
checkArgument(field.getAnnotation(XStreamAsAttribute.class) != null, name + " must be annotated with XStreamAsAttribute");
checkArgument(field.getAnnotation(XStreamAlias.class) != null, name + " must be annotated with XStreamAlias");
return field;
} catch (final SecurityException ex) {
throw new RuntimeException(ex);
} catch (final NoSuchFieldException ex) {
throw new RuntimeException(ex);
}
}
}
}
Any suggestions or ideas for a workaround? Thanks in advance.
I know this post is old, but maybe someone is still interested. My solution:
XStream xstream = new XStream(new MyPureJavaReflectionProvider());
class MyPureJavaReflectionProvider extends PureJavaReflectionProvider {
public MyPureJavaReflectionProvider() {
this(new FieldDictionary(new ImmutableFieldKeySorter()));
}
public MyPureJavaReflectionProvider(FieldDictionary fieldDictionary) {
super(fieldDictionary);
}
protected boolean fieldModifiersSupported(Field field) {
int modifiers = field.getModifiers();
return !Modifier.isStatic(modifiers);
}
public boolean fieldDefinedInClass(String fieldName, Class type) {
Field field = fieldDictionary.fieldOrNull(type, fieldName, null);
return field != null && fieldModifiersSupported(field);
}
}
Related
Could someone please help.
I would like to create a Factory, which would return specific class.
Having problem on the line - filterFactory.getFilter(myColumn.getType()).setMin(5);
There is an error: Cannot resolve method 'setMin' in 'Object'
public enum Columns {
Name(ColumnType.String),
Age(ColumnType.Numeric);
private final ColumnType type;
Columns(ColumnType type) {
this.type = type;
}
public ColumnType getType() {
return type;
}
}
public enum ColumnType {
String,
Numeric
}
public class NumericFilter extends ColumnFilter {
public void setMin(int min) {
System.out.println("min is set" + min);
}
}
public class StringFilter extends ColumnFilter {
public void setFilter(String filter) {
System.out.println("filter is set to:" + filter);
}
}
public class ColumnFilterFactory {
public <T> T getFilter(ColumnType type) {
if (type == null) {
return null;
}
if (type == ColumnType.String) {
return (T) new StringFilter();
} else if (type == ColumnType.Numeric) {
return (T) new NumericFilter();
}
return null;
}
}
public class BasicTest {
public static void main(String[] args) {
Columns myColumn = Columns.Age;
ColumnFilterFactory filterFactory = new ColumnFilterFactory();
filterFactory.getFilter(myColumn.getType()).setMin(5);
}
}
There's no way the compiler can know what type the factory is going to return, so you need to give it a little help. For example by using an intermediate variable:
NumericFilter nf = filterFactory.getFilter(myColumn.getType());
nf.setMin(5);
Let's start from example:
If the data is correct, it should be ( the Beijing cities is empty )
{
"code":200,
"msg":"success",
"data":[
{
"id":1,
"name":"Beijing",
"cities":[]
},
{
"id":2,
"name":"Guangdong",
"cities":[
{
"id":1,
"name":"Guangzhou"
}
]
}
]
}
Now I got a wrong data. ( the Beijing cities is null )
{
"code":200,
"msg":"success",
"data":[
{
"id":1,
"name":"Beijing",
"cities":null
},
{
"id":2,
"name":"Guangdong",
"cities":[
{
"id":1,
"name":"Guangzhou"
}
]
}
]
}
I am using the Retrofit2 ResponseBodyConverter ,the entity class:
public class Result<T> {
private int code;
private String msg;
private T data;
// getters, setters
}
public class Province {
private int id;
private String name;
private List<City> cities;
}
public class City {
private int id;
private String name;
}
The data obtained after deserialization is like this:
but the data I need is like this:
In order to have better fault tolerance, when the data is list, I want to process it by myself.
First of all,I tried to use JsonDeserializer
Gson gson = new GsonBuilder()
.serializeNulls()
.registerTypeHierarchyAdapter(List.class, new GsonListAdapter())
.create();
static class GsonListAdapter implements JsonDeserializer<List<?>> {
#Override
public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonArray()) {
JsonArray array = json.getAsJsonArray();
Type itemType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
List list = new ArrayList<>();
for (int i = 0; i < array.size(); i++) {
JsonElement element = array.get(i);
Object item = context.deserialize(element, itemType);
list.add(item);
}
return list;
} else {
return Collections.EMPTY_LIST;
}
}
}
JsonDeserializer is valid when the data is "", {}, and [],but data is null, it will not work.
Then I tried to use TypeAdapter
static class GsonListAdapter extends TypeAdapter<List<?>> {
#Override
public void write(JsonWriter out, List<?> value) throws IOException {
out.value(String.valueOf(value));
}
#Override
public List<?> read(JsonReader reader) throws IOException {
if (reader.peek() != JsonToken.BEGIN_ARRAY) {
reader.skipValue();
return Collections.EMPTY_LIST;
}
return new Gson().fromJson(reader, new TypeToken<List<?>>() {}.getType());
}
}
In this way, no matter what the data is, it can work properly.We know that using TypeToken<List<?>> will give us the LinkedHashMap,So although TypeAdapter can work properly, but I don't know how to convert JsonReader to the List <?>.
So I wonder if there are other ways that I can handle the wrong list data? Or convert JsonReader to the List <?> data I want.
I found the CollectionTypeAdapterFactory in Gson source code.I tried to modify it,it has been tested and it is useful.
public class CollectionTypeAdapterFactory implements TypeAdapterFactory {
private final ConstructorConstructor constructorConstructor;
public CollectionTypeAdapterFactory(ConstructorConstructor constructorConstructor) {
this.constructorConstructor = constructorConstructor;
}
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
Class<? super T> rawType = typeToken.getRawType();
if (!Collection.class.isAssignableFrom(rawType)) {
return null;
}
Type elementType = $Gson$Types.getCollectionElementType(type, rawType);
TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
ObjectConstructor<T> constructor = constructorConstructor.get(typeToken);
#SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
TypeAdapter<T> result = new Adapter(gson, elementType, elementTypeAdapter, constructor);
return result;
}
private static final class Adapter<E> extends TypeAdapter<Collection<E>> {
private final TypeAdapter<E> elementTypeAdapter;
private final ObjectConstructor<? extends Collection<E>> constructor;
public Adapter(Gson context, Type elementType,
TypeAdapter<E> elementTypeAdapter,
ObjectConstructor<? extends Collection<E>> constructor) {
this.elementTypeAdapter =
new TypeAdapterRuntimeTypeWrapper<E>(context, elementTypeAdapter, elementType);
this.constructor = constructor;
}
public Collection<E> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
//In the source code is return null, I changed to return an empty collection
return constructor.construct();
}
Collection<E> collection = constructor.construct();
in.beginArray();
while (in.hasNext()) {
E instance = elementTypeAdapter.read(in);
collection.add(instance);
}
in.endArray();
return collection;
}
public void write(JsonWriter out, Collection<E> collection) throws IOException {
if (collection == null) {
out.nullValue();
return;
}
out.beginArray();
for (E element : collection) {
elementTypeAdapter.write(out, element);
}
out.endArray();
}
}
}
In the source code the TypeAdapterRuntimeTypeWrapper is protected,We must make a copy.
public class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
private final Gson context;
private final TypeAdapter<T> delegate;
private final Type type;
TypeAdapterRuntimeTypeWrapper(Gson context, TypeAdapter<T> delegate, Type type) {
this.context = context;
this.delegate = delegate;
this.type = type;
}
#Override
public T read(JsonReader in) throws IOException {
return delegate.read(in);
}
#SuppressWarnings({"rawtypes", "unchecked"})
#Override
public void write(JsonWriter out, T value) throws IOException {
TypeAdapter chosen = delegate;
Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value);
if (runtimeType != type) {
TypeAdapter runtimeTypeAdapter = context.getAdapter(TypeToken.get(runtimeType));
if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
// The user registered a type adapter for the runtime type, so we will use that
chosen = runtimeTypeAdapter;
} else if (!(delegate instanceof ReflectiveTypeAdapterFactory.Adapter)) {
// The user registered a type adapter for Base class, so we prefer it over the
// reflective type adapter for the runtime type
chosen = delegate;
} else {
// Use the type adapter for runtime type
chosen = runtimeTypeAdapter;
}
}
chosen.write(out, value);
}
private Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
if (value != null
&& (type == Object.class || type instanceof TypeVariable<?> || type instanceof Class<?>)) {
type = value.getClass();
}
return type;
}
}
How to use
Gson gson = new GsonBuilder().serializeNulls()
.registerTypeAdapterFactory(
new CollectionTypeAdapterFactory(new ConstructorConstructor(new HashMap<>()))
)
.create();
Result<List<Province>> result = gson.fromJson(jsonStr, new TypeToken<Result<List<Province>>>() {}.getType());
prints:
Result{code=200, msg='success', data=[Province{id=1, name='Beijing', cities=[]}, Province{id=2, name='Guangdong', cities=[City{id=1, name='Guangzhou'}]}]}
I have to do Empty String check for 6 attributes of my class which has total 7 attributes, Is there any better way to do rather than checking each attribute separately ?
You can make one common method in class like this
class MyClass {
String attr1, attr2, attr3;
public boolean isValid() {
return !attr1.isEmpty() && !attr2.isEmpty() ;
}
}
One option is to use Hibernate Validator api, you can set #NotNull attributes on 6 of the params.
A note : Frameworks should not only be used if this is required at many places or is already being used as it comes with an overhead.
public class Abc{
#NotNull
private String var1;
#NotNull
#Size(min = 1, max = 4)
private String var2;
#Min(5)
private int var3;
// ...
}
If you are using java 8, you can do like this:
create a generic class Validator :
public class Validator<T> {
private T t;
private List<Throwable> exceptions = new ArrayList<>();
private Validator(T t) {
this.t = t;
}
public static <T> Validator<T> of(final T t) {
return new Validator<T>(t);
}
public <U> Validator<T> validate(final Function<T, U> projection, final Predicate<U> filter, final String message) {
if (!filter.test(projection.apply(t))) {
this.exceptions.add(new IllegalStateException(message));
}
return this;
}
public List<Throwable> get() {
return exceptions;
}
}
in your validator class :
YourObject yourObject = new yourObject("attr1", "attr2", "attr3");
List<Throwable> result = Validator.of(yourObject)
.validate(YourObject::getAttr1, a -> !a.isEmpty(), "attr1 should not be empty")
.validate(YourObject::getAttr2, a -> !a.isEmpty(), "attr2 should not be empty")
.validate(YourObject::getAttr3, a -> !a.isEmpty(), "attr3 should not be empty").get();
check if result is not empty.
If you are using java 7, you can adapt previous code like this :
public class StringValidator {
private List<Throwable> exceptions = new ArrayList<>();
private Validator() {
}
public static StringValidator instance() {
return new Validator();
}
public StringValidator validate(final String value, final String message) {
if (value.isEmpty()) {
this.exceptions.add(new IllegalStateException(message));
}
return this;
}
public List<Throwable> get() {
return exceptions;
}
}
in your validator class :
YourObject yourObject = new yourObject("attr1", "attr2", "attr3");
List<Throwable> result = Validator.instance()
.validate(yourObject.getAttr1(), "attr1 should not be empty")
.validate(yourObject.getAttr2(), "attr2 should not be empty")
.validate(yourObject.getAttr3(), "attr3 should not be empty").get();
I want to define map in Java, which keys are enums, and types of value depend of key. For example, suppose that we have following enum type:
enum KeyType {
HEIGHT(Integer.class),
NAME(String.class),
WEIGHT(Double.class)
// constructor and getter for Class field
}
and some map:
Map< KeyType, Object > map = new EnumMap<>(KeyType.class);
Is there any simple and safe way to write generic method:
public < T > T get(KeyType key) {
//...
}
that would get value from that map and cast it to corresponding with type class?
UPDATE!!!:
With this in mind:
enum KeyType {
//your enums ...
private final Class val;
//constructor ...
//and generic(!) access to the class field:
<T> Class<T> val() {
return val;
}
}
...this is possible:
public <T> T get(KeyType key) {
return (T) key.val().cast(map.get(key));
}
Your map definition would need to be
Map< KeyType, ?> map = new EnumMap<>(KeyType.class);
If you specify Object as a generic type, only actual instances of Object are allowed, not sub-types.
I don't believe there's any straight forward, generic way (no pun intended) to do what you want. You would need to create some mapping function that translates the object to the correct type based on the enum.
You can't do it with enums. But you could write a "fake" enum (the way Java code did it before Java 1.5, with private constructors and public static instances), and attach a generic type to each constant:
import java.io.Serializable;
import java.util.Map;
public final class KeyType<T>
implements Serializable {
private static final long serialVersionUID = 1;
public static final KeyType<Integer> HEIGHT =
new KeyType<>("HEIGHT", Integer.class);
public static final KeyType<String> NAME =
new KeyType<>("NAME", String.class);
public static final KeyType<Double> WEIGHT =
new KeyType<>("WEIGHT", Double.class);
private static final KeyType<?>[] allValues = {
HEIGHT, NAME, WEIGHT
};
/** #serial */
private final String name;
/** #serial */
private final Class<T> type;
private KeyType(String name,
Class<T> type) {
this.name = name;
this.type = type;
}
public String name() {
return name;
}
public Class<T> getType() {
return type;
}
#Override
public String toString() {
return name();
}
public static KeyType<?>[] values() {
return allValues.clone();
}
public static KeyType<?> valueOf(String name) {
for (KeyType<?> value : allValues) {
if (value.name.equals(name)) {
return value;
}
}
throw new IllegalArgumentException("No such value: \"" + name + "\"");
}
#Override
public boolean equals(Object obj) {
return (obj instanceof KeyType &&
this.name.equals(((KeyType<?>) obj).name));
}
#Override
public int hashCode() {
return name.hashCode();
}
public T getValue(Map<KeyType<?>, ?> map) {
return type.cast(map.get(this));
}
}
I've seen that the default TypeAdapter for Enum doesn't fit my need:
private static final class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
private final Map<String, T> nameToConstant = new HashMap<String, T>();
private final Map<T, String> constantToName = new HashMap<T, String>();
public EnumTypeAdapter(Class<T> classOfT) {
try {
for (T constant : classOfT.getEnumConstants()) {
String name = constant.name();
SerializedName annotation = classOfT.getField(name).getAnnotation(SerializedName.class);
if (annotation != null) {
name = annotation.value();
}
nameToConstant.put(name, constant);
constantToName.put(constant, name);
}
} catch (NoSuchFieldException e) {
throw new AssertionError();
}
}
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
return nameToConstant.get(in.nextString());
}
public void write(JsonWriter out, T value) throws IOException {
out.value(value == null ? null : constantToName.get(value));
}
}
If the Enum has value ONE and TWO, when we try to parse THREE, then this value is unknown and Gson will map null instead of raising a parsing exception. I need something more fail-fast.
But I also need something which permits me to know the name of the field which is currently read and creates a parsing failure.
Is it possible with Gson?
Yes.
Gson is quite modular to allow you to use your own TypeAdapterFactory for the enum case. Your custom adapter will return your own EnumTypeAdapter and manage the wanted case. Let the code speak.
package stackoverflow.questions.q16715117;
import java.io.IOException;
import java.util.*;
import com.google.gson.*;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.*;
public class Q16715117 {
public static void main(String[] args) {
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapterFactory(CUSTOM_ENUM_FACTORY);
Container c1 = new Container();
Gson g = gb.create();
String s1 = "{\"colour\":\"RED\",\"number\":42}";
c1 = g.fromJson(s1, Container.class);
System.out.println("Result: "+ c1.toString());
}
public static final TypeAdapterFactory CUSTOM_ENUM_FACTORY = newEnumTypeHierarchyFactory();
public static TypeAdapterFactory newEnumTypeHierarchyFactory() {
return new TypeAdapterFactory() {
#SuppressWarnings({"rawtypes", "unchecked"})
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType();
if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) {
return null;
}
if (!rawType.isEnum()) {
rawType = rawType.getSuperclass(); // handle anonymous subclasses
}
return (TypeAdapter<T>) new CustomEnumTypeAdapter(rawType);
}
};
}
private static final class CustomEnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
private final Map<String, T> nameToConstant = new HashMap<String, T>();
private final Map<T, String> constantToName = new HashMap<T, String>();
private Class<T> classOfT;
public CustomEnumTypeAdapter(Class<T> classOfT) {
this.classOfT = classOfT;
try {
for (T constant : classOfT.getEnumConstants()) {
String name = constant.name();
SerializedName annotation = classOfT.getField(name).getAnnotation(SerializedName.class);
if (annotation != null) {
name = annotation.value();
}
nameToConstant.put(name, constant);
constantToName.put(constant, name);
}
} catch (NoSuchFieldException e) {
throw new AssertionError();
}
}
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
String nextString = in.nextString();
T enumValue = nameToConstant.get(nextString);
if (enumValue == null)
throw new GsonEnumParsinException(nextString, classOfT.getName());
return enumValue;
}
public void write(JsonWriter out, T value) throws IOException {
out.value(value == null ? null : constantToName.get(value));
}
}
}
Plus I declared a custom runtime exception:
public class GsonEnumParsinException extends RuntimeException {
String notFoundEnumValue;
String enumName;
String fieldName;
public GsonEnumParsinException(String notFoundEnumValue, String enumName) {
this.notFoundEnumValue = notFoundEnumValue;
this.enumName = enumName;
}
#Override
public String toString() {
return "GsonEnumParsinException [notFoundEnumValue="
+ notFoundEnumValue + ", enumName=" + enumName + "]";
}
public String getNotFoundEnumValue() {
return notFoundEnumValue;
}
#Override
public String getMessage() {
return "Cannot found " + notFoundEnumValue + " for enum " + enumName;
}
}
These are the classes I used in the example:
public enum Colour {
WHITE, YELLOW, BLACK;
}
public class Container {
private Colour colour;
private int number;
public Colour getColour() {
return colour;
}
public void setColour(Colour colour) {
this.colour = colour;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
#Override
public String toString() {
return "Container [colour=" + colour + ", number=" + number + "]";
}
}
This gives this stacktrace:
Exception in thread "main" GsonEnumParsinException [notFoundEnumValue=RED, enumName=stackoverflow.questions.q16715117.Colour]
at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:77)
at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:1)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172)
at com.google.gson.Gson.fromJson(Gson.java:803)
at com.google.gson.Gson.fromJson(Gson.java:768)
at com.google.gson.Gson.fromJson(Gson.java:717)
at com.google.gson.Gson.fromJson(Gson.java:689)
at stackoverflow.questions.q16715117.Q16715117.main(Q16715117.java:22)
Unfortunately, the EnumTypeAdapter does not know anything about the context it's called, so this solution is not enough to catch the field name.
Edit
So you have to use also another TypeAdapter that I called CustomReflectiveTypeAdapterFactory and is almost a copy of CustomReflectiveTypeAdapterFactory and I changed a bit the exception, so:
public final class CustomReflectiveTypeAdapterFactory implements TypeAdapterFactory {
private final ConstructorConstructor constructorConstructor;
private final FieldNamingStrategy fieldNamingPolicy;
private final Excluder excluder;
public CustomReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
FieldNamingStrategy fieldNamingPolicy, Excluder excluder) {
this.constructorConstructor = constructorConstructor;
this.fieldNamingPolicy = fieldNamingPolicy;
this.excluder = excluder;
}
public boolean excludeField(Field f, boolean serialize) {
return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize);
}
private String getFieldName(Field f) {
SerializedName serializedName = f.getAnnotation(SerializedName.class);
return serializedName == null ? fieldNamingPolicy.translateName(f) : serializedName.value();
}
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
Class<? super T> raw = type.getRawType();
if (!Object.class.isAssignableFrom(raw)) {
return null; // it's a primitive!
}
ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
}
private CustomReflectiveTypeAdapterFactory.BoundField createBoundField(
final Gson context, final Field field, final String name,
final TypeToken<?> fieldType, boolean serialize, boolean deserialize) {
final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
// special casing primitives here saves ~5% on Android...
return new CustomReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) {
final TypeAdapter<?> typeAdapter = context.getAdapter(fieldType);
#SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree
#Override void write(JsonWriter writer, Object value)
throws IOException, IllegalAccessException {
Object fieldValue = field.get(value);
TypeAdapter t =
new CustomTypeAdapterRuntimeTypeWrapper(context, this.typeAdapter, fieldType.getType());
t.write(writer, fieldValue);
}
#Override void read(JsonReader reader, Object value)
throws IOException, IllegalAccessException {
Object fieldValue = null;
try {
fieldValue = typeAdapter.read(reader);
} catch (GsonEnumParsinException e){
e.setFieldName(field.getName());
throw e;
}
if (fieldValue != null || !isPrimitive) {
field.set(value, fieldValue);
}
}
};
}
// more copy&paste code follows
The most important part is read method where I catch the exception and add the field name and throw again exception. Note that class CustomTypeAdapterRuntimeTypeWrapper is simply a renamed copy of TypeAdapterRuntimeTypeWrapper in library internals since class is private.
So, main method changes as follows:
Map<Type, InstanceCreator<?>> instanceCreators
= new HashMap<Type, InstanceCreator<?>>();
Excluder excluder = Excluder.DEFAULT;
FieldNamingStrategy fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapterFactory(new CustomReflectiveTypeAdapterFactory(new ConstructorConstructor(instanceCreators), fieldNamingPolicy, excluder));
gb.registerTypeAdapterFactory(CUSTOM_ENUM_FACTORY);
Gson g = gb.create();
and now you have this stacktrace (changes to exception are so simple that I omitted them):
Exception in thread "main" GsonEnumParsinException [notFoundEnumValue=RED, enumName=stackoverflow.questions.q16715117.Colour, fieldName=colour]
at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:90)
at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:1)
at stackoverflow.questions.q16715117.CustomReflectiveTypeAdapterFactory$1.read(CustomReflectiveTypeAdapterFactory.java:79)
at stackoverflow.questions.q16715117.CustomReflectiveTypeAdapterFactory$Adapter.read(CustomReflectiveTypeAdapterFactory.java:162)
at com.google.gson.Gson.fromJson(Gson.java:803)
at com.google.gson.Gson.fromJson(Gson.java:768)
at com.google.gson.Gson.fromJson(Gson.java:717)
at com.google.gson.Gson.fromJson(Gson.java:689)
at stackoverflow.questions.q16715117.Q16715117.main(Q16715117.java:35)
Of course this solution comes at some costs.
First off all, you have to copy some private/final classes and do your changes. If library get updated, you have to check again your code (a fork of source code would be the same, but at least you do not have to copy all that code).
If you customize field exclusion strategy, constructors or field naming policies you have to replicate them into the CustomReflectiveTypeAdapterFactory since I do not find any possibility to pass them from the builder.