Using Enums while parsing JSON with GSON

JavaJsonGson

Java Problem Overview


This is related to a previous question that I asked here earlier

https://stackoverflow.com/questions/8207274/json-parsing-using-gson/8207343#8207343

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?

Java Solutions


Solution 1 - Java

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());

Solution 2 - Java

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;
  }
}

Solution 3 - Java

Use annotation @SerializedName:

@SerializedName("${title}")
TITLE,
@SerializedName("${description}")
DESCRIPTION

Solution 4 - Java

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;
			}
		}
	}
}

Solution 5 - Java

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;
  }
}

Solution 6 - Java

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();

Solution 7 - Java

use this method

GsonBuilder.enableComplexMapKeySerialization();

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSachin KulkarniView Question on Stackoverflow
Solution 1 - JavavalidcatView Answer on Stackoverflow
Solution 2 - JavaProgrammer BruceView Answer on Stackoverflow
Solution 3 - JavaIncView Answer on Stackoverflow
Solution 4 - JavaWoutView Answer on Stackoverflow
Solution 5 - Javauser2601995View Answer on Stackoverflow
Solution 6 - JavaTom BollwittView Answer on Stackoverflow
Solution 7 - JavaAhamadullah SaikatView Answer on Stackoverflow