Venom
Venom

Reputation: 1047

Deserialize a generic class instance accessible through an interface reference using Gson

I'm using the Universal Java Matrix Package and it's working really great but I can't get it to play nice with Gson - serialization seems to work fine but that is the easier part, of course.
Deserialization is producing some problems because I'm trying to deserialize JSON into an instance of a class that has a generic private member of type DefaultDenseGenericMatrix2D where Square is my own class. DefaultDenseGenericMatrix2D implements, among others, the Matrix interface which I'm using to reference the actual member. So, the interface itself is not generic but a class that implements it in fact is. I've tried using the RuntimeTypeAdapterFactory that isn't included in the Gson library itself but can be found in their GitHub repository.

I'm using this on Android, if that matters.

This is how it looks like currently:

// Grid
public class Grid implements Cloneable {
    private GridPosition _position; // works fine
    private GridSize _size; // this one also
    private int _dimensions; // like this one, as well
    private Matrix _squaresMatrix; // this little rascal refuses to play nice

    public Grid(GridPosition position, GridSize size) {
        _position = position;
        _size = size;

        Integer sizeValue = Converters.valueOf(size);
        _dimensions = sizeValue != null ? sizeValue : 0;

        // as you can see, it really is generic but the interface isn't
        _squaresMatrix = new DefaultDenseGenericMatrix2D<Square>(_dimensions, _dimensions); // can be either 10, 15 or 20 (100, 225 or 400 elements in total)

        // initializing the matrix, doesn't really matter
        for (long[] coordinates : _squaresMatrix.allCoordinates()) {
            Long x = coordinates[0];
            Long y = coordinates[1];

            _squaresMatrix.setAsObject(new Square(SquareState.VACANT, new Coordinates(x.intValue(), y.intValue())), coordinates);
        }
    }

    // ... (unimportant stuff)
}

/*********************************************/

// Square
public class Square {
    // all members are otherwise serialized correctly, on their own
    private SquareState _state;
    private Coordinates _coordinates;
    private Ship _owner;

    public Square(SquareState state, Coordinates coordinates) {
        _state = state;
        _coordinates = coordinates;
        _owner = null;
    }

    // ... (unimportant stuff)
}

/*********************************************/

// constructing the Gson object
Type genericType = new TypeToken<DefaultDenseGenericMatrix2D<Square>>(){}.getType(); // not used (where to put it?)
final RuntimeTypeAdapterFactory<Matrix> typeFactory = RuntimeTypeAdapterFactory
        .of(Matrix.class, "_squaresMatrix")
        // DefaultDenseGenericMatrix2D<Square>.class is illegal, of course (wouldn't make sense either way due to the infamous type erasure)
        .registerSubtype(DefaultDenseGenericMatrix2D.class);

GsonBuilder builder = new GsonBuilder().registerTypeAdapterFactory(typeFactory);
Gson gson = builder.create();

During deserialization, the "_squaresMatrix" field is indeed deserialized but instead of a DefaultDenseGenericMatrix2D of type Square I get one of type LinkedTreeMap because Gson didn't know which type to actually use and the DefaultDenseGenericMatrix2D class itself works fine with plain old Objects, of course.
I believe the solution might be in using the TypeToken class somewhere but I just don't where I would incorporate it.
As a last resort I could just map LinkedTreeMap instances to those of type Square because the data is there but I would like to try to avoid that, if possible.

Thank you in advance.

Upvotes: 0

Views: 125

Answers (1)

Joffrey
Joffrey

Reputation: 37680

Using an InstanceCreator allows you to tell Gson how to create instances of a specific type.

Here you can tell it to create a DefaultDenseGenericMatrix2D<Square> each time it finds a Matrix:

GsonBuilder builder = new GsonBuilder().registerTypeAdapter(Matrix.class, new MatrixInstanceCreator());
Gson gson = builder.create();

With MatrixInstanceCreator being:

public class MatrixInstanceCreator()  implements InstanceCreator<Matrix> {
    @Override
    public Matrix createInstance() {
        // the dimensions will be overridden by Gson anyway
        return new DefaultDenseGenericMatrix2D<Square>(0, 0);
    }
}

Warning: this is not very robust, though, as every Matrix in your application would be created as DefaultDenseGenericMatrix2D<Square>. However, if you only have this one, or if all your Matrix are of that concrete type, this will suit your needs anyway.

Upvotes: 1

Related Questions