Search code examples
javagsonguava

How to use Gson with guava tables


I'm storing my data in a Guava Table, and need it to save to a JSON file.

I managed to get that working with the Gson#toJson method just fine, but I'm having difficulties reading it back into the table.

Does anyone know what I could do?


Solution

  • You should write your own set of Serializer/Deserializers or even TypeAdapter(Factory). Here's a sample implementation based on https://github.com/acebaggins/guava-gson-serializers:

    static class TableSerializer implements JsonSerializer<Table> {
    
        @Override
        public JsonElement serialize(Table table, Type type, JsonSerializationContext context) {
            return context.serialize(table.rowMap());
        }
    }
    
    static class TableDeserializer implements JsonDeserializer<Table<?, ?, ?>> {
    
        @Override
        public Table deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
            Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
            Type parameterizedType = hashMapOf(
                    typeArguments[0],
                    hashMapOf(typeArguments[1], typeArguments[2]).getType()).getType();
            Map<?, Map<?, ?>> map = context.deserialize(json, parameterizedType);
    
            Table<Object, Object, Object> table = HashBasedTable.create();
            for (Object rowKey : map.keySet()) {
                Map<?, ?> rowMap = map.get(rowKey);
                for (Object columnKey : rowMap.keySet()) {
                    Object value = rowMap.get(columnKey);
                    table.put(rowKey, columnKey, value);
                }
            }
            return table;
        }
    }
    
    // see https://github.com/acebaggins/guava-gson-serializers/blob/master/src/main/java/com/baggonius/gson/immutable/Types.java
    static <K, V> TypeToken<HashMap<K, V>> hashMapOf(Type key, Type value) {
        TypeParameter<K> newKeyTypeParameter = new TypeParameter<K>() {};
        TypeParameter<V> newValueTypeParameter = new TypeParameter<V>() {};
        return new TypeToken<HashMap<K, V>>() {}
                .where(newKeyTypeParameter, typeTokenOf(key))
                .where(newValueTypeParameter, typeTokenOf(value));
    }
    
    private static <E> TypeToken<E> typeTokenOf(Type type) {
        return (TypeToken<E>) TypeToken.of(type);
    }
    

    which should be used like this:

    @Test
    public void shouldSerializeAndDeserializeGuavaTable() {
        Table<Integer, Integer, String> table = ImmutableTable.<Integer, Integer, String>builder()
                .put(1, 1, "eleven")
                .put(1, 0, "ten")
                .put(4, 2, "forty-two")
                .build();
    
        Gson gson = new GsonBuilder()
                .registerTypeHierarchyAdapter(Table.class, new TableSerializer())
                .registerTypeHierarchyAdapter(Table.class, new TableDeserializer())
                .create();
        String tableAsString = gson.toJson(table); // {"1":{"1":"eleven","0":"ten"},"4":{"2":"forty-two"}}
    
        Table<Integer, Integer, String> deserializedTable = gson.fromJson( // {1={0=ten, 1=eleven}, 4={2=forty-two}}
                tableAsString,
                new TypeToken<Table<Integer, Integer, String>>() {}.getType());
    
        Assertions.assertThat(deserializedTable)
                .hasSize(3)
                .containsCell(1, 1, "eleven")
                .containsCell(1, 0, "ten")
                .containsCell(4, 2, "forty-two");
    }
    

    If I find some spare time, I might create a PR to https://github.com/acebaggins/guava-gson-serializers with properly tested changes for Table (de)serialization.