Using Enums while parsing JSON with GSON - java

This is related to a previous question that I asked here earlier
JSON parsing using Gson
I am trying to parse the same JSON, but now I have changed my classes a little bit.
{
"lower": 20,
"upper": 40,
"delimiter": " ",
"scope": ["${title}"]
}
My class now looks like:
public class TruncateElement {
private int lower;
private int upper;
private String delimiter;
private List<AttributeScope> scope;
// getters and setters
}
public enum AttributeScope {
TITLE("${title}"),
DESCRIPTION("${description}"),
private String scope;
AttributeScope(String scope) {
this.scope = scope;
}
public String getScope() {
return this.scope;
}
}
This code throws an exception,
com.google.gson.JsonParseException: The JsonDeserializer EnumTypeAdapter failed to deserialized json object "${title}" given the type class com.amazon.seo.attribute.template.parse.data.AttributeScope
at
The exception is understandable, because as per the solution to my previous question, GSON is expecting the Enum objects to be actually be created as
${title}("${title}"),
${description}("${description}");
But since this is syntactically impossible, what are the recommended solutions, workarounds?

I want to expand a bit NAZIK/user2724653 answer (for my case). Here is a Java code:
public class Item {
#SerializedName("status")
private Status currentState = null;
// other fields, getters, setters, constructor and other code...
public enum Status {
#SerializedName("0")
BUY,
#SerializedName("1")
DOWNLOAD,
#SerializedName("2")
DOWNLOADING,
#SerializedName("3")
OPEN
}
}
in the json file you have just a field "status": "N",, where N=0,1,2,3 - depend on the Status values. So that's all, GSON works fine with the values for the nested enum class. In my case i've parsed a list of Items from json array:
List<Item> items = new Gson().<List<Item>>fromJson(json,
new TypeToken<List<Item>>(){}.getType());

From the documentation for Gson:
Gson provides default serialization and deserialization for Enums... If you would prefer to change the default representation, you can do so by registering a type adapter through GsonBuilder.registerTypeAdapter(Type, Object).
Following is one such approach.
import java.io.FileReader;
import java.lang.reflect.Type;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
public class GsonFoo
{
public static void main(String[] args) throws Exception
{
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(AttributeScope.class, new AttributeScopeDeserializer());
Gson gson = gsonBuilder.create();
TruncateElement element = gson.fromJson(new FileReader("input.json"), TruncateElement.class);
System.out.println(element.lower);
System.out.println(element.upper);
System.out.println(element.delimiter);
System.out.println(element.scope.get(0));
}
}
class AttributeScopeDeserializer implements JsonDeserializer<AttributeScope>
{
#Override
public AttributeScope deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException
{
AttributeScope[] scopes = AttributeScope.values();
for (AttributeScope scope : scopes)
{
if (scope.scope.equals(json.getAsString()))
return scope;
}
return null;
}
}
class TruncateElement
{
int lower;
int upper;
String delimiter;
List<AttributeScope> scope;
}
enum AttributeScope
{
TITLE("${title}"), DESCRIPTION("${description}");
String scope;
AttributeScope(String scope)
{
this.scope = scope;
}
}

Use annotation #SerializedName:
#SerializedName("${title}")
TITLE,
#SerializedName("${description}")
DESCRIPTION

The following snippet removes the need for explicit Gson.registerTypeAdapter(...), using the #JsonAdapter(class) annotation, available since Gson 2.3 (see comment pm_labs).
#JsonAdapter(Level.Serializer.class)
public enum Level {
WTF(0),
ERROR(1),
WARNING(2),
INFO(3),
DEBUG(4),
VERBOSE(5);
int levelCode;
Level(int levelCode) {
this.levelCode = levelCode;
}
static Level getLevelByCode(int levelCode) {
for (Level level : values())
if (level.levelCode == levelCode) return level;
return INFO;
}
static class Serializer implements JsonSerializer<Level>, JsonDeserializer<Level> {
#Override
public JsonElement serialize(Level src, Type typeOfSrc, JsonSerializationContext context) {
return context.serialize(src.levelCode);
}
#Override
public Level deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
try {
return getLevelByCode(json.getAsNumber().intValue());
} catch (JsonParseException e) {
return INFO;
}
}
}
}

With GSON version 2.2.2 enum will be marshalled and unmarshalled easily.
import com.google.gson.annotations.SerializedName;
enum AttributeScope
{
#SerializedName("${title}")
TITLE("${title}"),
#SerializedName("${description}")
DESCRIPTION("${description}");
private String scope;
AttributeScope(String scope)
{
this.scope = scope;
}
public String getScope() {
return scope;
}
}

If you really want to use the Enum's ordinal value you can register a type adapter factory to override Gson's default factory.
public class EnumTypeAdapter <T extends Enum<T>> extends TypeAdapter<T> {
private final Map<Integer, T> nameToConstant = new HashMap<>();
private final Map<T, Integer> constantToName = new HashMap<>();
public EnumTypeAdapter(Class<T> classOfT) {
for (T constant : classOfT.getEnumConstants()) {
Integer name = constant.ordinal();
nameToConstant.put(name, constant);
constantToName.put(constant, name);
}
}
#Override public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
return nameToConstant.get(in.nextInt());
}
#Override public void write(JsonWriter out, T value) throws IOException {
out.value(value == null ? null : constantToName.get(value));
}
public static final TypeAdapterFactory ENUM_FACTORY = new TypeAdapterFactory() {
#SuppressWarnings({"rawtypes", "unchecked"})
#Override 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 EnumTypeAdapter(rawType);
}
};
}
Then just register the factory.
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(EnumTypeAdapter.ENUM_FACTORY)
.create();

use this method
GsonBuilder.enableComplexMapKeySerialization();

Related

Gson deserialize JSON array with multiple object types

I have some odd JSON like:
[
{
"type":"0",
"value":"my string"
},
{
"type":"1",
"value":42
},
{
"type":"2",
"value": {
}
}
]
Based on some field, the object in the array is a certain type.
Using Gson, my thought is to have a TypeAdapterFactory that sends delegate adapters for those certain types to a TypeAdapter, but I'm hung up on understanding a good way of reading that "type" field to know which type to create.
In the TypeAdapter,
Object read(JsonReader in) throws IOException {
String type = in.nextString();
switch (type) {
// delegate to creating certain types.
}
}
would assume the "type" field comes first in my JSON. Is there a decent way to remove that assumption?
Here is some code I wrote to handle an array of NewsFeedArticle and NewsFeedAd items in Json. Both items implement a marker interface NewsFeedItem to allow me to easily check if the TypeAdater should be used for a particular field.
public class NewsFeedItemTypeAdapterFactory implements TypeAdapterFactory {
#Override
#SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (!NewsFeedItem.class.isAssignableFrom(type.getRawType())) {
return null;
}
TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
TypeAdapter<NewsFeedArticle> newsFeedArticleAdapter = gson.getDelegateAdapter(this, TypeToken.get(NewsFeedArticle.class));
TypeAdapter<NewsFeedAd> newsFeedAdAdapter = gson.getDelegateAdapter(this, TypeToken.get(NewsFeedAd.class));
return (TypeAdapter<T>) new NewsFeedItemTypeAdapter(jsonElementAdapter, newsFeedArticleAdapter, newsFeedAdAdapter).nullSafe();
}
private static class NewsFeedItemTypeAdapter extends TypeAdapter<NewsFeedItem> {
private final TypeAdapter<JsonElement> jsonElementAdapter;
private final TypeAdapter<NewsFeedArticle> newsFeedArticleAdapter;
private final TypeAdapter<NewsFeedAd> newsFeedAdAdapter;
NewsFeedItemTypeAdapter(TypeAdapter<JsonElement> jsonElementAdapter,
TypeAdapter<NewsFeedArticle> newsFeedArticleAdapter,
TypeAdapter<NewsFeedAd> newsFeedAdAdapter) {
this.jsonElementAdapter = jsonElementAdapter;
this.newsFeedArticleAdapter = newsFeedArticleAdapter;
this.newsFeedAdAdapter = newsFeedAdAdapter;
}
#Override
public void write(JsonWriter out, NewsFeedItem value) throws IOException {
if (value.getClass().isAssignableFrom(NewsFeedArticle.class)) {
newsFeedArticleAdapter.write(out, (NewsFeedArticle) value);
} else if (value.getClass().isAssignableFrom(NewsFeedAd.class)) {
newsFeedAdAdapter.write(out, (NewsFeedAd) value);
}
}
#Override
public NewsFeedItem read(JsonReader in) throws IOException {
JsonObject objectJson = jsonElementAdapter.read(in).getAsJsonObject();
if (objectJson.has("Title")) {
return newsFeedArticleAdapter.fromJsonTree(objectJson);
} else if (objectJson.has("CampaignName")) {
return newsFeedAdAdapter.fromJsonTree(objectJson);
}
return null;
}
}
}
You can then register this with Gson using the following code.
return new GsonBuilder()
.registerTypeAdapterFactory(new NewsFeedItemTypeAdapterFactory())
.create();

Caused by: java.lang.ClassCastException: libcore.reflect.ParameterizedTypeImpl cannot be cast to java.lang.Class when use Generic Type

I make a class extend AsyncTask to parse Json use Gson, because input can be many type of class I use generic type:
public class ApiResponse<T> extends AsyncTask...
I need know class type of T to pass Gson with:
(1)
Class<T> clazz =
(Class<T>)((ParameterizedType)
getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
new Gson().fromJson(reader, clazz);
However, Here T can be a class that have many type so that some time I pass class:
(2)
public class DataMessage<T> implements Serializable{...}
With these class have this format I received a Exception Caused by:
java.lang.ClassCastException: libcore.reflect.ParameterizedTypeImpl cannot be cast to java.lang.Class
at (1)
How do I do in this case ?
UPDATED:
public class DataMessage<T> implements Serializable{
#SerializedName("pagination")
private Pagination pagination;
#SerializedName("meta")
private Message meta;
#SerializedName("data")
private T data;
public DataMessage(T data) {
this.data = data;
}
public Pagination getPagination() {
return pagination;
}
public void setPagination(Pagination pagination) {
this.pagination = pagination;
}
public Message getMeta() {
return meta;
}
public void setMeta(Message meta) {
this.meta = meta;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public class Pagination {
#SerializedName("next_max_tag_id")
public String nextMaxTagId;
#SerializedName("deprecation_warning")
public String deprecationWarning;
#SerializedName("next_max_id")
public String nextMaxId;
#SerializedName("next_min_id")
public String nextMinId;
#SerializedName("min_tag_id")
public String minTagId;
#SerializedName("next_url")
public String nextUrl;
}
public class Message {
#SerializedName("error_type")
public String errorType;
#SerializedName("code")
public int code;
#SerializedName("error_message")
public String error_message;
}
}
When I need to parse JSON with specific class using Gson, I use GsonBuilder in this way
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try{
return new Date( json.getAsJsonPrimitive().getAsLong()*1000L );
}catch( Exception e ){
return null;
}
}
});
Gson gson = gsonBuilder.create();
In your case I think that you can simply parse your JSON in this way:
DataMessage<YourClass> dataMessage = gson.fromJson(yourJsonObj, DataMessage.class);
Another thing is that you can obtain the exact Type in this way
Type collectionType = new TypeToken<DataMessage<T>>() {}.getType();
But probably you will get a bad Json when you deserialize this...
The best way is to use the class instead of T so Gson know exactly what has to serialie/deserialize
Type collectionType = new TypeToken<DataMessage<YourClass>>() {}.getType();
I found this for you Deserializing Generic Types with GSON
Let's say you want to deserialize a list of DataMessage<T> using a JsonReader.
public <T> T Gson::fromJson(JsonReader reader, Type typeOfT)
Then you have
..
import java.lang.reflect.Type; // make sure you add this import
class DataMessageList<T>
{
public List<DataMessage<T>> list;
public DataMessageList ()
{
list = null;
}
public void readJson (JsonReader reader)
{
Type TT = new TypeToken<ArrayList< DataMessage<T> >>(){}.getType();
this.list = new Gson().fromJson(reader, TT);
}
}
Thanks to #Andrea Catania with your useful help,
Problem here is when I cast ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; to Class type, With DataMessage<T> type it can't cast.
But ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0] have type is Type so that I don't need cast it to Class, I retype clazz variable to Type (not Class) and problem have been resolved.

Map deserializing with Jackson in Java

I have the following class
public class BetWrapper {
private String description;
private Calendar startTime;
private HashMap<String, SimpleEntry<Integer, Double>> map;
public BetWrapper() {
map = new HashMap<>();
}
public Calendar getStartTime() {
return startTime;
}
public void setStartTime(Calendar startTime) {
this.startTime = startTime;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public HashMap<String, SimpleEntry<Integer, Double>> getMap() {
return map;
}
public void setMap(HashMap<String, SimpleEntry<Integer, Double>> map) {
this.map = map;
}
}
And I'm using JSONUtil class
public class JSONUtil {
private JSONUtil() {}
public static <T> T fromJSON(String content, Class<T> clazz) throws TechnicalException {
try {
return new ObjectMapper().readValue(content, clazz);
} catch (IOException e) {
throw new TechnicalException(e);
}
}
public static String toJSON(Object obj) throws TechnicalException {
try {
return new ObjectMapper().writeValueAsString(obj);
} catch (JsonProcessingException ex) {
throw new TechnicalException(ex);
}
}
}
I want to deserialzize a JSON to an BetWrapper object. But the following code produces some exceptions.
BetWrapper betWrapper = new BetWrapper();
betWrapper.setDescription("Stoke City - Arsenal");
betWrapper.setStartTime(Calendar.getInstance());
HashMap<String, AbstractMap.SimpleEntry<Integer, Double>> map = new HashMap<>();
map.put("home_team", new AbstractMap.SimpleEntry<>(1, 2.85));
betWrapper.setMap(map);
String json = JSONUtil.toJSON(betWrapper);
JSONUtil.fromJSON(json, BetWrapper.class);
The exceptions are:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class java.util.AbstractMap$SimpleEntry<java.lang.Integer,java.lang.Double>]: can not instantiate from JSON object (need to add/enable type information?)
at [Source: {"description":"Stoke City - Arsenal","startTime":1417648132139,"map":{"home_team":"key":1,"value":2.85}}}; line: 1, column: 85] (through reference chain: by.bsu.kolodyuk.bettingapp.model.entity.BetWrapper["map"])
How to deserialize it correctly? It seems like that the problem is that types K,V in SimpleEntry should be specified for Jackson in some way.
Any Ideas?
The type SimpleEntry has the following constructor
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
By default, Jackson expects a parameterless constructor. If such a constructor doesn't exist, it looks for one with #JsonProperty annotations. (I might have this backwards, but don't ever code like that.) Since you're working with a JDK type, it obviously won't have those annotations.
You can use Mixins. Jackson uses these as templates to deserialize your target type.
abstract class Mixin<K, V> {
public Mixin(#JsonProperty("key") K key, #JsonProperty("value") V value) {}
}
...
public static <T> T fromJSON(String content, Class<T> clazz) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(SimpleEntry.class, Mixin.class);
return mapper.readValue(content, clazz);
}
Jackson will use the meta type Mixin, to deserialize to SimpleEntry objects.
(Note, the type parameters of Mixin and the constructor parameter types don't really matter. It's the fact that there are two constructor parameters and that the constructor parameters are annotated that matters.)
In Jackson, you can define custom deserializers. So for your case, it may look like this:
import java.io.IOException;
import java.util.AbstractMap;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
public class SomeDeserializer extends StdDeserializer<AbstractMap.SimpleEntry>
{
public SomeDeserializer()
{
super( AbstractMap.SimpleEntry.class );
}
#Override
public AbstractMap.SimpleEntry deserialize( JsonParser jp, DeserializationContext ctxt ) throws IOException, JsonProcessingException
{
Integer key = null;
Double value = null;
JsonToken token;
while ( ( token = jp.nextValue() ) != null )
{
if ( token.isNumeric() )
{
String propertyName = jp.getCurrentName();
if ( "key".equalsIgnoreCase( propertyName ) )
{
key = jp.getIntValue();
}
else if ( "value".equalsIgnoreCase( propertyName ) )
{
value = jp.getDoubleValue();
}
}
}
if ( key != null && value != null )
{
return new AbstractMap.SimpleEntry( key, value );
}
return null;
}
}
Deserializers should be registered using ObjectMapper.registerModule(Module m). In your case, you can do it in your JSONUtil utility class:
SimpleModule module = new SimpleModule();
module.addDeserializer( AbstractMap.SimpleEntry.class, new SomeDeserializer() );
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule( module );
Note that deserializer is registered with ObjectMapper instance. Thus you better store the instance as a field of your utility class.
The deserializer class above is not comprehensive! This is just for demonstration for the case in hand. Further optimizations and refactorings can be applied as needed.

How to serialize Optional<T> classes with Gson?

I have an object with the following attributes.
private final String messageBundle;
private final List<String> messageParams;
private final String actionBundle;
private final Map<String, String> data;
private final Optional<Pair<Integer,TimeUnit>> ttl;
private final Optional<Integer> badgeNumber;
private final Optional<String> collapseKey;
The object is in a library, i would rather not modify it just for serialization purpose, and would like to avoid the cost of creating another DTO.
How can i serialize / unserialize Optional attributes? Optional doesn't have a default constructor (neither apache commons Pair), but i can't use the InstanceCreator, and don't really understand how to create a TypeAdapter that would simply delegate the serialization to the underlying Optional content.
After several hours of gooling and coding - there is my version:
public class OptionalTypeAdapter<E> extends TypeAdapter<Optional<E>> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<T> rawType = (Class<T>) type.getRawType();
if (rawType != Optional.class) {
return null;
}
final ParameterizedType parameterizedType = (ParameterizedType) type.getType();
final Type actualType = parameterizedType.getActualTypeArguments()[0];
final TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(actualType));
return new OptionalTypeAdapter(adapter);
}
};
private final TypeAdapter<E> adapter;
public OptionalTypeAdapter(TypeAdapter<E> adapter) {
this.adapter = adapter;
}
#Override
public void write(JsonWriter out, Optional<E> value) throws IOException {
if(value.isPresent()){
adapter.write(out, value.get());
} else {
out.nullValue();
}
}
#Override
public Optional<E> read(JsonReader in) throws IOException {
final JsonToken peek = in.peek();
if(peek != JsonToken.NULL){
return Optional.ofNullable(adapter.read(in));
}
in.nextNull();
return Optional.empty();
}
}
You can simple registered it with GsonBuilder like this:
instance.registerTypeAdapterFactory(OptionalTypeAdapter.FACTORY)
Please keep attention that Gson does not set values to your class field if field does not present in json. So you need to set default value Optional.empty() in your entity.
The solution by Ilya ignores type parameters, so it can't really work in the general case. My solution is rather complicated, because of the need to distinguish between null and Optional.absent() -- otherwise you could strip away the encapsulation as a list.
public class GsonOptionalDeserializer<T>
implements JsonSerializer<Optional<T>>, JsonDeserializer<Optional<T>> {
#Override
public Optional<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
final JsonArray asJsonArray = json.getAsJsonArray();
final JsonElement jsonElement = asJsonArray.get(0);
final T value = context.deserialize(jsonElement, ((ParameterizedType) typeOfT).getActualTypeArguments()[0]);
return Optional.fromNullable(value);
}
#Override
public JsonElement serialize(Optional<T> src, Type typeOfSrc, JsonSerializationContext context) {
final JsonElement element = context.serialize(src.orNull());
final JsonArray result = new JsonArray();
result.add(element);
return result;
}
}
Just as an addition to maaartinus solution, the version without the encapsulating list, where Optional.absent is simply serialized as null:
public class GsonOptionalDeserializer<T> implements JsonSerializer<Optional<T>>, JsonDeserializer<Optional<T>> {
#Override
public Optional<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
final T value = context.deserialize(json, ((ParameterizedType) typeOfT).getActualTypeArguments()[0]);
return Optional.fromNullable(value);
}
#Override
public JsonElement serialize(Optional<T> src, Type typeOfSrc, JsonSerializationContext context) {
return context.serialize(src.orNull());
}
}
I'll add to Anton Onikiychuk's answer
#Override
public Optional<E> read(JsonReader in) throws IOException {
JsonToken peek = in.peek();
if (peek == JsonToken.NULL) {
in.nextNull(); // consuming JSON null
return Optional.empty();
}
return Optional.ofNullable(adapter.read(in));
}

gson invoking standard deserialization in custom deserializer

Is it possible to write a json deserializer in gson that invokes the default behaviour first and then i can do some post processing on my object. For example:
public class FooDeserializer implements JsonDeserializer<Foo> {
public Foo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Foo foo = context.deserialize(json, typeOfT);//Standard deserialization call?????
foo.doSomething();
return foo();
}
}
I am using gson 1.3 (I cannot use any other version as i can only use the versions in the corporate
repository)
thanks
You can do that by implementing custom TypeAdapterFactory for your object (say CustomClass.class) to be deserialized as below.
public class CustomTypeAdapterFactory implements TypeAdapterFactory {
public final TypeAdapter create(Gson gson, TypeToken type) {
return new TypeAdapter() {
#Override
public void write(JsonWriter out, Object value) throws IOException {
JsonElement tree = delegate.toJsonTree(value);
//add code for writing object
}
#Override
public Object read(JsonReader in) throws IOException {
JsonElement tree = elementAdapter.read(in);
//Add code for reading object
}
};
}
}
And then registering it with Gson as
Gson gson = new GsonBuilder().registerTypeAdapter(CustomClass.class,new CustomTypeAdapterFactory()).create();
public class FooDeserializer implements JsonDeserializer<Foo> {
public Foo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Foo foo=new Gson().fromJson(json, Foo.class); // use default Gson object
foo.doSomething();
return foo;
}
Check out http://gsonfire.io
It's a library I made that extends Gson to handle cases like Post-serialization and Post-deserialization
Also it has many other cool features that I've needed over time with Gson.
public class YourDeserializer<Foo> extends FooDeserializer<Foo>
{
public Foo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Foo foo = super.deserialize(json, typeOfT,context);
foo.doSomething(); //put logic
return foo();
}
}
Here's full implementation based on incomplete answer provided by #user1556622 and discussion in code.google.com/p/google-gson/issues/detail?id=43.
As a result we can serialize list of abstract Field objects and smoothly deserialize it independent on concrete implementation of specific Field and its hierarchy depth.
class MyClass { //class which we would like to serialiaze/deserialize
List<Field> fields; //field is an hierarchy of classes
}
/**
* Purpose of this adapter is simple:
* 1) put during serialization in all Field objects additional property describing class
* 2) during deserialization invoke (based on class info) necessary deserializer to create class
*/
public class FieldTypeAdapterFactory implements TypeAdapterFactory {
private static final String CLASS_META_KEY="clz";
Gson gson;
TypeToken<?> type;
TypeAdapter<Field> fieldAdapter;
TypeAdapter<JsonElement> elementAdapter;
TypeAdapterFactory taf;
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (!Field.class.isAssignableFrom(type.getRawType()))
return null; // this class only serializes 'Field' and its subtypes
this.type=type;
this.gson=gson;
this.taf=this;
fieldAdapter = gson.getDelegateAdapter(taf, TypeToken.get(Field.class));
elementAdapter = gson.getAdapter(JsonElement.class);
TypeAdapter<T> result = new FieldTypeAdapter<T>();
result.nullSafe();
return result;
}
class FieldTypeAdapter<T> extends TypeAdapter<T> {
public FieldTypeAdapter() {
}
#Override
public void write(JsonWriter out, Object value) throws IOException {
if(value instanceof Field) {
JsonObject object = fieldAdapter.toJsonTree((Field )value).getAsJsonObject();
object.addProperty(CLASS_META_KEY, value.getClass().getCanonicalName());
elementAdapter.write(out, object);
}
else {
elementAdapter.write(out, (JsonElement) value);
}
}
#Override
public T read(JsonReader in) throws IOException {
JsonObject object = elementAdapter.read(in).getAsJsonObject();
if (object.has(CLASS_META_KEY)) {
String className=object.get(CLASS_META_KEY).getAsString();
try {
Class<?> clz = Class.forName(className);
TypeAdapter<?> adapter = gson.getDelegateAdapter(taf, TypeToken.get(clz));
return (T) adapter.fromJsonTree(object);
}
catch (Exception e) {
return (T )fieldAdapter.fromJsonTree(object);
}
}
else
return (T )elementAdapter.fromJsonTree(object);
}
}
}
Registration of factory:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new FieldTypeAdapterFactory())
.create();

Categories

Resources