17

I want to print HashMultiMap as json.

HashMultimap<String,Object> multimap = HashMultimap.create();
multimap.put("a",Obj1);
multimap.put("a",Obj3);
multimap.put("b",Obj2);

to

{ 
  "a":[Obj1,Obj3],
  "b":[Obj2]
}

Obj1 and other objects should again be in json(to keep it clean, I have shown it as objects)
I can iterate over the individual keys and convert set of Objects to json using libraries such as Gson.

But to get the entire snapshot of the HashMultimap, I want to convert it to json and inspect it.

Gson could not convert the entire map, but could do individual values(list of objects to json)

sat
  • 40,138
  • 28
  • 93
  • 102

2 Answers2

15

Call asMap() on the MultiMap first. This converts the MultiMap to a standard Map where each value is a Collection.

In your example, the type of the resulting Map is Map<String, Collection<Object>>. Gson should be able to serialise this correctly.

Wesley
  • 10,652
  • 4
  • 37
  • 52
5

You need to write a JsonAdapter or both JsonDeserializer and JsonSerializer. It's rather terrible, but I wanted to try.

Basically, you delegate everything to a Map<String, Collection<V>>.

static class MultimapAdapter implements JsonDeserializer<Multimap<String, ?>>, JsonSerializer<Multimap<String, ?>> {
    @Override public Multimap<String, ?> deserialize(JsonElement json, Type type,
            JsonDeserializationContext context) throws JsonParseException {
        final HashMultimap<String, Object> result = HashMultimap.create();
        final Map<String, Collection<?>> map = context.deserialize(json, multimapTypeToMapType(type));
        for (final Map.Entry<String, ?> e : map.entrySet()) {
            final Collection<?> value = (Collection<?>) e.getValue();
            result.putAll(e.getKey(), value);
        }
        return result;
    }

    @Override public JsonElement serialize(Multimap<String, ?> src, Type type, JsonSerializationContext context) {
        final Map<?, ?> map = src.asMap();
        return context.serialize(map);
    }

    private <V> Type multimapTypeToMapType(Type type) {
        final Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
        assert typeArguments.length == 2;
        @SuppressWarnings("unchecked")
        final TypeToken<Map<String, Collection<V>>> mapTypeToken = new TypeToken<Map<String, Collection<V>>>() {}
        .where(new TypeParameter<V>() {}, (TypeToken<V>) TypeToken.of(typeArguments[1]));
        return mapTypeToken.getType();
    }
}

The full code including a test can be found here.

maaartinus
  • 44,714
  • 32
  • 161
  • 320
  • Care to contribute the adapters to https://github.com/google-gson/typeadapters? ([associated bug](https://github.com/google/gson/issues/526)) – dimo414 Mar 08 '17 at 17:15
  • @dimo414 No time now, but feel free to do it. I guess, the SO license permits it already. – maaartinus Mar 08 '17 at 18:38
  • Is there any reasonable way to implement the `multimapTypeToMapType(Type type)` method without relying on Guava? Can you add the implementation (I know, wishful thinking, but does not hurt to ask) or describe how it might be managed. – GRosenberg Mar 19 '18 at 05:43
  • @GRosenberg I see my link was broken, fixed. Are you sure, you need a `Type` of a Guava class without relying on Guava? – maaartinus Mar 19 '18 at 05:53
  • @maaartinus - I have my own non-Guava Multimap class with specialized features. Looking to use with Gson without having to add Guava as a dependency. – GRosenberg Mar 19 '18 at 06:07
  • @GRosenberg I see. There's a `com.google.gson.reflect.TypeToken` but it has no `where` method. These stuff is pretty ugly and I'm afraid, your best bet is to copy the needed Guava classes and remove what you don't need. – maaartinus Mar 19 '18 at 15:45