Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GSON fromJson return LinkedHashMap instead of EnumMap

Tags:

java

gson

I want to make gson able to return an EnumMap object. I use the following code

package sandbox;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.util.EnumMap;
import java.util.Map;

/**
 *
 * @author yccheok
 */
public class Sandbox {

    public static void main(String[] args) throws InterruptedException {    
        testGson();
    }

    public static enum Country {
        Malaysia,
        UnitedStates
    }

    public static void testGson() {
        Map<Country, String> enumMap = new EnumMap<Country, String>(Country.class);
        enumMap.put(Country.Malaysia, "RM");
        enumMap.put(Country.UnitedStates, "USD");
        Gson gson = new Gson();
        String string = gson.toJson(enumMap);
        System.out.println("toJSon : " + string);
        enumMap = gson.fromJson(string, new TypeToken<EnumMap<Country, String>>(){}.getType());
        System.out.println("fromJSon : " + enumMap);
        System.out.println("fromJSon : " + enumMap.getClass());
    }
}

However, I'm getting the following

toJSon : {"Malaysia":"RM","UnitedStates":"USD"}
fromJSon : {Malaysia=RM, UnitedStates=USD}
fromJSon : class java.util.LinkedHashMap

even though I had used new TypeToken<EnumMap<Country, String>>(){}.getType() to specific I want EnumMap instead of LinkedHashMap

How can I make gson to return EnumMap?

like image 471
Cheok Yan Cheng Avatar asked Apr 21 '13 04:04

Cheok Yan Cheng


1 Answers

Even with a type token, Gson can only deserialize data into classes that have a default constructor. And EnumMap doesn't have one (it needs to be instantiated with the type of enum that its elements will match). The easiest way around this problem is to define and use an InstanceCreator:

This interface is implemented to create instances of a class that does not define a no-args constructor. If you can modify the class, you should instead add a private, or public no-args constructor. However, that is not possible for library classes, such as JDK classes, or a third-party library that you do not have source-code of. In such cases, you should define an instance creator for the class. Implementations of this interface should be registered with GsonBuilder.registerTypeAdapter(Type, Object) method before Gson will be able to use them.

Heres some example code:

InstanceCreator:

class EnumMapInstanceCreator<K extends Enum<K>, V> implements
        InstanceCreator<EnumMap<K, V>> {
    private final Class<K> enumClazz;

    public EnumMapInstanceCreator(final Class<K> enumClazz) {
        super();
        this.enumClazz = enumClazz;
    }

    @Override
    public EnumMap<K, V> createInstance(final Type type) {
        return new EnumMap<K, V>(enumClazz);
    }
}

Test code:

final Gson gson = new GsonBuilder().registerTypeAdapter(
        new TypeToken<EnumMap<Country, String>>() {
        }.getType(),
        new EnumMapInstanceCreator<Country, String>(Country.class))
        .create();

final Map<Country, String> enumMap = new EnumMap<Country, String>(
        Country.class);
enumMap.put(Country.Malaysia, "RM");
enumMap.put(Country.UnitedStates, "USD");
String string = gson.toJson(enumMap);
System.out.println("toJSon : " + string);

final Map<Country, String> reverseEnumMap = gson.fromJson(string,
        new TypeToken<EnumMap<Country, String>>() {
        }.getType());
System.out.println("fromJSon (Class): " + reverseEnumMap.getClass());
System.out.println("fromJSon : " + reverseEnumMap);
like image 167
Perception Avatar answered Nov 15 '22 18:11

Perception