Can these 3 methods be generalized into 1 method - java

I have these 3 methods below. I only don't have a clue to make them as one, as they are type of array, list or just an object. These methods just convert a given object to a json string and then convert it to the specified class that was given.
public static List<Object> objectListSerializer(List<Document> documents, Class entity) {
List<Object> entityList;
String json = com.mongodb.util.JSON.serialize(documents);
entityList = (List<Object>) GSON_INSTANCE.fromJson(json, entity);
return entityList;
}
public static Object objectArraySerializer(Object objectArray, Class clazz) {
String jsonString = GSON_INSTANCE.toJson(objectArray);
Object convert[] = (Object[]) GSON_INSTANCE.fromJson(jsonString, clazz);
return convert;
}
public static Object objectSerializer(Object object, Class clazz) {
String jsonString = GSON_INSTANCE.toJson(object);
Object convert = GSON_INSTANCE.fromJson(jsonString, clazz);
return convert;
}

Basically, you can write a method that includes the some cases of object type like the following lines.
public static Object serialize(Object object, Class<?> entity) {
String jsonString = null;
if (object instanceof List) {
jsonString = com.mongodb.util.JSON.serialize((List<?>) object);
return (List<?>) GSON_INSTANCE.fromJson(jsonString, entity);
} else {
jsonString = GSON_INSTANCE.toJson(object);
if (object.getClass().isArray()) {
return (Object[]) GSON_INSTANCE.fromJson(jsonString, entity);
} else {
return GSON_INSTANCE.fromJson(jsonString, entity);
}
}
}

Related

How to create a method that accepts and return generic type with his wrapper class

I'm not sure how to classify what I'm trying to achieve
I think that I can define it like that:
I'm trying to create a method that will return a generic type with his wrapper class
I'll just show what I'm trying to do
I have a string that represents a JSON that I got from calling an endpoint
String httpBody = "{...}";
I have a class name EndPointResponse
public class EndPointResponse<T> {
private T responseObj = null;
...
public EndPointResponse(T responseObj, ...) {
this.responseObj = responseObj;
...
}
}
in another part of the code, I'm trying to create this object by calling another function
EndPointResponse<SomeObject> o1 = getEndPointResponse(httpBody, SomeObject.class);
I want to be able to get this object in more ways:
EndPointResponse<List<SomeObject>> o2 = getEndPointResponse(httpBody, SomeObject.class, List.class);
or
EndPointResponse<SomeWrapperObject<SomeObject>> o3 = getEndPointResponse(httpBody, SomeObject.class, SomeWrapperObject.class);
what I've done so far:
public static <T> EndPointResponse<T> getEndPointResponse(String httpBody, Class<T> classOfT){
T result = getResultObject(httpBody, classOfT);
if(result == null){
return createEndPointResponse(httpBody, ...with some data);
}
return createEndPointResponse(result, ...with some other data);
}
public static <T> EndPointResponse<List<T>> getEndPointResponse(String httpBody, Class<T> classOfT, Class<?> wrapperClass){
List<T> result = getResultObject(httpBody, classOfT, wrapperClass);
if(result == null){
return createEndPointResponse(httpBody, ...with some data);
}
return createEndPointResponse(result, ...with some other data);
}
private static <T> T getResultObject(String resultString, Class<T> classOfT){
T resultObj = null;
try{
resultObj = gson.fromJson(resultString, classOfT);
}
catch (JsonSyntaxException e){
...some log
}
return resultObj;
}
private static <T> List<T> getResultObject(String resultString, Class<T> classOfT, Class<?> wrapperClass){
List<T> resultObj = null;
try{
resultObj = gson.fromJson(resultString, getType(classOfT, wrapperClass));
}
catch (JsonSyntaxException e){
... some log
}
return resultObj;
}
private static <T> EndPointResponse<T> createEndPointResponse(T resultObj, ...){
return new EndPointResponse<T>(resultObj, ...);
}
private static <T> EndPointResponse<List<T>> createEndPointResponse(List<T> resultObj, ...){
return new EndPointResponse<List<T>>(resultObj, ...);
}
private static <T> Type getType(Class<T> classOfT, Class<?> wrapperClass){
return new ParameterizedType() {
private Type type;
#Override
public Type[] getActualTypeArguments() {
return new Type[] {classOfT};
}
#Override
public Type getRawType() {
return wrapperClass;
}
#Override
public Type getOwnerType() {
return null;
}
};
}
so basically I'm trying to create another overloaded method that instead of a List class it will be any wrapper class that I want or even better is to generalize the List class overload method so it will support any wrapper class
something like this: (just an example it's not going to have the right syntax)
public static <T> EndPointResponse<W<T>> getEndPointResponse(String httpBody, Class<T> classOfT, Class<W> wrapperClass){
W<T> result = getResultObject(httpBody, classOfT, wrapperClass);
if(result == null){
return createEndPointResponse(httpBody, ...with some data);
}
return createEndPointResponse(result, ...with some other data);
}
------------------------------------
private static <T> W<T> getResultObject(String resultString, Class<T> classOfT, Class<W> wrapperClass){
W<T> resultObj = null;
try{
resultObj = gson.fromJson(resultString, getType(classOfT, wrapperClass));
}
catch (JsonSyntaxException e){
... some log
}
return resultObj;
}
------------------------------------
private static <T> EndPointResponse<W<T>> createEndPointResponse(W<T> resultObj, ...){
return new EndPointResponse<W<T>>(resultObj, ...);
}
does anyone have an idea of how I can achieve that?
any help or constructive review will be appreciated :)
thanks

Gson ClassCastException (LinkedTreeMap)

Using Gson to deserialize objects of a parameterized class Container<T>, results in the java.lang.ClassCastException for certain values of T, e.g. for a simple Record type consisting of String and List<String> fields:
com.google.gson.internal.LinkedTreeMap cannot be cast to Record
Interestingly enough, the same code works when calling the fromJson() method inline, i.e. the following statement does return a valid value:
Container<Record> value = new Gson().fromJson(
json, new TypeToken<Container<Record>>(){}.getType());
The definition of Container<T> is simple, too:
public class Container<T> {
private static final Gson PARSER = new Gson();
private String id;
private List<T> content;
private Object data;
public static <T> Container<T> deserialize(String json, Class<T> type) {
return PARSER.fromJson(json, new TypeToken<Container<T>>(){}.getType());
}
}
Changing the deserialize() method to non-static does not resolve the issue.
Any ideas?
You want ParameterizedType my solution is create
public static <T> Container<T> deserialize(String json, Class<T> clazz) {
Type type = TypeToken.getParameterized(Container.class,clazz).getType();
return new Gson().fromJson(json, type);
}
problem is T because Java does not know what i kind and generate Type of T
public static <T> Container<T> sec(String json, Class<T> clazz) {
Type type1 = new TypeToken<Container<T>>() { }.getType();
Type type = TypeToken.getParameterized(Container.class,clazz).getType();
System.out.println(type1); //==>pl.jac.container.Container<T>
System.out.println(type); //==>pl.jac.container.Container<pl.jac.container.Record>
return new Gson().fromJson(json, type);
}
this is test for more example to correct run
test testContainerRecord2 is for your problem
import java.lang.reflect.Type;
import org.junit.Test;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import static org.junit.Assert.assertEquals;
public class ContainerTest {
#Test
public void testContainerRecord() {
//given
String json = "{\"id\":\"new ID\",\"content\":[{\"id\":\"50e0300a-6668-42b3-a474-81a6a08f773f\"},{\"id\":\"f0bee3f3-2c40-4b44-8608-a6fedb226b7a\"}],\"data\":\"AAAAAAAA\"}";
//when
Container<Record> containerRecord = Container.deserializeClass(json, ContainerRecord.class);
//then
assertEquals("50e0300a-6668-42b3-a474-81a6a08f773f", containerRecord.content.get(0).id);
assertEquals("f0bee3f3-2c40-4b44-8608-a6fedb226b7a", containerRecord.content.get(1).id);
}
#Test
public void testContainerRecord2() {
//given
String json = "{\"id\":\"new ID\",\"content\":[{\"id\":\"50e0300a-6668-42b3-a474-81a6a08f773f\"},{\"id\":\"f0bee3f3-2c40-4b44-8608-a6fedb226b7a\"}],\"data\":\"AAAAAAAA\"}";
//when
Container<Record> containerRecord = Container.deserialize(json, Record.class);
//then
assertEquals("50e0300a-6668-42b3-a474-81a6a08f773f", containerRecord.content.get(0).id);
assertEquals("f0bee3f3-2c40-4b44-8608-a6fedb226b7a", containerRecord.content.get(1).id);
}
#Test
public void testGenericWithType() {
//given
String json = "{\"id\":\"new ID\",\"content\":[{\"id\":\"50e0300a-6668-42b3-a474-81a6a08f773f\"},{\"id\":\"f0bee3f3-2c40-4b44-8608-a6fedb226b7a\"}],\"data\":\"AAAAAAAA\"}";
//when
Type type = new TypeToken<Container<Record>>() {
}.getType();
Container<Record> containerRecord = Container.deserializeType(json, type);
//then
assertEquals("50e0300a-6668-42b3-a474-81a6a08f773f", containerRecord.content.get(0).id);
assertEquals("f0bee3f3-2c40-4b44-8608-a6fedb226b7a", containerRecord.content.get(1).id);
}
#Test
public void testRecord() {
//given
String json = "{\"id\":\"new ID\",\"content\":[{\"id\":\"50e0300a-6668-42b3-a474-81a6a08f773f\"},{\"id\":\"f0bee3f3-2c40-4b44-8608-a6fedb226b7a\"}],\"data\":\"AAAAAAAA\"}";
//when
ContainerRecord containerRecord = new Gson().fromJson(json, ContainerRecord.class);
//then
assertEquals("50e0300a-6668-42b3-a474-81a6a08f773f", containerRecord.content.get(0).id);
assertEquals("f0bee3f3-2c40-4b44-8608-a6fedb226b7a", containerRecord.content.get(1).id);
}
#Test
public void testRecordWithType() {
//given
String json = "{\"id\":\"new ID\",\"content\":[{\"id\":\"50e0300a-6668-42b3-a474-81a6a08f773f\"},{\"id\":\"f0bee3f3-2c40-4b44-8608-a6fedb226b7a\"}],\"data\":\"AAAAAAAA\"}";
//when
Type type = new TypeToken<Container<Record>>() {
}.getType();
Container<Record> containerRecord = new Gson().fromJson(json, type);
//then
assertEquals("50e0300a-6668-42b3-a474-81a6a08f773f", containerRecord.content.get(0).id);
assertEquals("f0bee3f3-2c40-4b44-8608-a6fedb226b7a", containerRecord.content.get(1).id);
}
#Test
public void testContainerString() {
//given
String json = "{\"id\":\"new ID\",\"content\":[\"37c84304-ab80-4f92-8b2a-710b362ecb3f\"],\"data\":\"AAAAAAAA\"}";
//when
Type type = new TypeToken<Container<String>>() {
}.getType();
Container<String> containerRecord = new Gson().fromJson(json, type);
//then
assertEquals("37c84304-ab80-4f92-8b2a-710b362ecb3f", containerRecord.content.get(0));
}
}
my class Record
public class Record {
public String id;
}
and my Container
public class ContainerRecord extends Container<Record> {
}
and Container
public class Container<T> {
public String id;
public List<T> content;
public Object data;
public static <T> Container<T> deserializeClass(String json, Class<? extends Container<T>> type) {
return new Gson().fromJson(json, type);
}
public static <T> Container<T> deserializeType(String json, Type type) {
return new Gson().fromJson(json, type);
}
public static <T> Container<T> deserialize(String json, Class<T> clazz) {
Type type = TypeToken.getParameterized(Container.class,clazz).getType();
return new Gson().fromJson(json, type);
}
}

Gson uses TypeAdapter or Json Deserializer to convert data from an error list to an empty list

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'}]}]}

Gson: can we get the serialized field name in a type adapter?

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.

GSON deserialize with passed generics

I am working a simple facade for JSON mapping libraries. For my default implementation I am using GSON but have run into an issue. I've defined a class similar to this:
public class QueryResult<T> {
private List<T> results;
private String nextOffset;
public List<T> getResults() { return results; }
public void setResults(List<T> results) { this.results = results; }
public String getNextOffset() { return nextOffset; }
public void setNextOffset(String nextOffset) { this.nextOffset = nextOffset; }
}
and would like to do something like this with it:
public <T> QueryResult<T> fromJsonQuery(String string, Class<T> clazz) {
Type type = new TypeToken<QueryResult<T>>() {}.getType();
return gson.fromJson(string, type);
}
However, whenever I call this method I get an error:
QueryResult<Foo> qr = mapper.fromJsonQuery(json, Foo.class);
for (Foo foo : qr.getResults()) {
System.out.println(foo.toString());
}
java.lang.ClassCastException: com.google.gson.internal.StringMap cannot be cast to example.Foo
Any help would be greatly appreciated. Thanks.
I've come up with a solution that will work for me. Basically, I define inner classes that extend QueryResult for the various types I need. In my facade I defined this method:
public <T> T fromJson(String string, Class<T> clazz) {
return gson.fromJson(string, clazz);
}
Then I can do the following and it works:
public class Main {
class FooQuery extends QueryResult<Foo> {}
public static void main(String[] args) {
Mapper mapper = new GsonMapper();
String json = args[0];
QueryResult<Foo> qr = mapper.fromJson(json, FooQuery.class);
for (Foo foo : qr.getResults()) {
System.out.println(foo.toString());
}
}

Categories

Resources