Reputation: 349
I'm finding a little bit difficult to deserialize this class using GSON. Starting from the Resource ABSTRACT class here:
public abstract class Resource() {
String id;
Integer quantity;
}
public class Wood extends Resource {
}
public class Stone extends Resource{
}
Then I have a container class:
public class ResourceSet {
Map<String, Resource> resourcesMap;
}
In the map the key is the ID of the resource while the value is the resource itself. I don't know how to deserialize properly this class, letting know gson that when he parse a string "Wood" the resource is an instance of Wood. Otherwise gson uses the base constructor of the resource class that is an abstract class, and so it gives me an exception.
An example of a json string that i want to deserialize could be:
{
"resourcesMap":{
"Wood":{"quantity":4},
"Stone":{"quantity":2}
}
}
Upvotes: 0
Views: 433
Reputation: 3579
one option would be to create a custom deserializer for your Resource
types. in it, use a hint within your JSON message to decide what type Resource
should be.
i'm not sure what your JSON messages look like, but the solution would look something like this:
public class ResourceDeserializer implements JsonDeserializer<Resource> {
@Override
public Resource deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final JsonObject root = json.getAsJsonObject();
final Resource resource;
if("wood".equalsIgnoreCase(root.get("type"))) {
resource = new WoodResource();
} else {
resource = new StoneResource();
}
return resource;
}
}
UPDATED
abstract class Resource {
protected String id;
protected Integer quantity;
}
class Wood extends Resource {}
class Stone extends Resource {}
class ResourceMap {
protected Map<String,Resource> resources;
ResourceMap() {
this.resources = new HashMap<>();
}
}
class ResourceMapDeserializer implements JsonDeserializer<ResourceMap> {
@Override
public ResourceMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final JsonObject root = json.getAsJsonObject();
final ResourceMap instance = new ResourceMap();
instance.resources.put("Wood", parse(root, "Wood"));
instance.resources.put("Stone", parse(root, "Stone"));
return instance;
}
private Resource parse(JsonObject root, String fieldName) {
final JsonElement field = root.get(fieldName);
if(field != null) {
final Resource resource;
if("Wood".equalsIgnoreCase(fieldName)) {
resource = new Wood();
} else {
resource = new Stone();
}
resource.quantity = field.getAsJsonObject().get("quantity").getAsInt();
return resource;
} else {
return null;
}
}
}
Simple verification...
@RunWith(MockitoJUnitRunner.class)
public class ResourceDeserializerTest {
@Mock private JsonDeserializationContext mockContext;
private Gson gson;
@Before
public void setUp() {
gson = new GsonBuilder()
.registerTypeAdapter(ResourceMap.class, new ResourceMapDeserializer())
.setPrettyPrinting()
.create();
}
@Test
public void deserializes_resource_map() {
final JsonObject woodJson = new JsonObject();
woodJson.addProperty("quantity", 4);
final JsonObject stoneJson = new JsonObject();
stoneJson.addProperty("quantity", 2);
final JsonObject mapJson = new JsonObject();
mapJson.add("Wood", woodJson);
mapJson.add("Stone", stoneJson);
final ResourceMap deserialized = gson.fromJson(mapJson, ResourceMap.class);
assertThat(deserialized.resources.get("Wood").getClass()).isEqualTo(Wood.class);
assertThat(deserialized.resources.get("Stone").getClass()).isEqualTo(Stone.class);
}
}
Upvotes: 1