Question

I have Multiset<String> objects, which I am serializing as Json. I am doing this using Gson as follows:

Multiset<String> mset = ... ;
Gson gson = new Gson();
Files.write(Gson.toJson(mset), new File(abosulte_path_string), Charset.defaultCharset());

When I try to deserialize it, I do the following:

String json_string = ... // read from file
Type type = new TypeToken<Multiset<String>>(){}.getType();
Multiset<String> treated = gson.fromJson(json_string, type);

I get this error:

java.lang.ClassCastException: java.util.ArrayList cannot be cast to com.google.common.collect.Multiset

When I opened the json file, I see that the Multiset<String> object is, indeed, represented as an ArrayList ([string1, string2, ... ]), with repetitions for strings that had count > 1 in the multiset.

I can, of course, cast it into an ArrayList and then use the create(Iterable<>) constructor to get my multiset, but it seems to be a roundabout way. Is there a more direct way of deserializing the json object to retrieve my multisets?

Was it helpful?

Solution

EDIT: It looks like in this case there is a much easier way to solve this problem, namely by registering an instance creator for Multisets:

private static class MultisetInstanceCreator implements InstanceCreator<Multiset<?>> {
    @Override
    public Multiset<?> createInstance(Type type) {
        return HashMultiset.create();
    }
}

Gson gson = new GsonBuilder()
        .registerTypeAdapter(Multiset.class, new MultisetInstanceCreator())
        .create();

The instance creator just defines how a Multiset should be created, since the Guava collections don't have default constructors (and Multiset is the interface anyway).

Original answer: I'm not sure if this is the best or easiest way to achieve what you want, but it is one way that worked for us recently for a similar problem (in our case it was deserialising to an ImmutableMap).

The basic idea is to register a custom deserialiser which basically does what you already discovered as a possible solution: deserialise to an ArrayList, then turn it into a Multiset. The advantage here is that you only have to do register the custom deserialiser once, rather than having to know everywhere to first use an ArrayList type.

This custom deserialiser looks like this:

private static class MultisetDeserializer implements JsonDeserializer<Multiset<?>> {
    @Override
    public Multiset<?> deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
        ParameterizedType parameterizedType = (ParameterizedType) type;
        Type[] typeArguments = parameterizedType.getActualTypeArguments();

        ParameterizedType listType = new ListParameterizedType(typeArguments);
        List<?> list = context.deserialize(json, listType);

        return HashMultiset.create(list);
    }
}

In short, this casts the expected type to deserialise to (e.g. new TypeToken<Multiset<String>>(){}.getType() in your case) to ParameterizedType to get at the type arguments (String in your example). It then creates a new ParameterizedType which is the type of an ArrayList with the same type arguments (shown below). After using the context to deserialise the JSON to this new type, all you have to do is call HashMultiset.create.

The ListParameterizedType looks like this:

private static class ListParameterizedType implements ParameterizedType {
    private final Type[] typeArguments;

    private ListParameterizedType(Type[] typeArguments) {
        this.typeArguments = typeArguments;
    }

    @Override
    public Type[] getActualTypeArguments() {
        return typeArguments;
    }

    @Override
    public Type getRawType() {
        return ArrayList.class;
    }

    @Override
    public Type getOwnerType() {
        return null;
    }
}

Note that you could replace ArrayList with pretty much any list class here, as long as it has one type argument and a default constructor.

There may also be easier ways to achieve the same thing, for example you could do some of the parsing manually by inspecting the JsonElement with its methods like isJsonArray(). This could save you from creating an ArrayList which you throw away immediately afterwards.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top