Deserializing an abstract class in Gson
I'd suggest adding a custom JsonDeserializer for Node
s:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Node.class, new NodeDeserializer())
.create();
You will be able to access the JsonElement
representing the node in the deserializer's method, convert that to a JsonObject
, and retrieve the field that specifies the type. You can then create an instance of the correct type of Node
based on that.
Gson and abstract superclasses: deserialization issue
Following the Axxiss link, here follows the answer. A custom serializer/deserializer must be provided.
public class AClassAdapter implements JsonSerializer<A>, JsonDeserializer<A> {
@Override
public JsonElement serialize(A src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject result = new JsonObject();
result.add("type", new JsonPrimitive(src.getClass().getSimpleName()));
result.add("properties", context.serialize(src, src.getClass()));
return result;
}
@Override
public A deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String type = jsonObject.get("type").getAsString();
JsonElement element = jsonObject.get("properties");
try {
String fullName = typeOfT.getTypeName();
String packageText = fullName.substring(0, fullName.lastIndexOf(".") + 1);
return context.deserialize(element, Class.forName(packageText + type));
} catch (ClassNotFoundException cnfe) {
throw new JsonParseException("Unknown element type: " + type, cnfe);
}
}
}
Then the serialization is done like follows:
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(A.class, new ATypeAdapter());
String json = gson.create().toJson(list);
and given the json string, the deserialization is:
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(A.class, new ATypeAdapter());
return gson.create().fromJson(json, A[].class);
Abstract class with Gson serialization/deserialization
Thanks to @fluffy's suggestion, I managed to skip writing a custom deserialiser and making use of RuntimeTypeAdapterFactory
from gson-extras
library:
public static void writeObjectToFile(Object object, String filePath) {
RuntimeTypeAdapterFactory<Parameter> parameterAdapterFactory = RuntimeTypeAdapterFactory.of(Parameter.class, "type");
parameterAdapterFactory.registerSubtype(StringParameter.class, "StringParameter");
parameterAdapterFactory.registerSubtype(PasswordParameter.class, "PasswordParameter");
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapterFactory(parameterAdapterFactory).create();
try {
FileWriter fileWriter = new FileWriter(filePath);
gson.toJson(object, fileWriter);
fileWriter.flush();
fileWriter.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
public static LinkedList<Request> readObjectFromFile(String filePath) {
RuntimeTypeAdapterFactory<Parameter> parameterAdapterFactory = RuntimeTypeAdapterFactory.of(Parameter.class, "type");
parameterAdapterFactory.registerSubtype(StringParameter.class, "StringParameter");
parameterAdapterFactory.registerSubtype(PasswordParameter.class, "PasswordParameter");
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapterFactory(parameterAdapterFactory).create();
try {
BufferedReader jsonBuffer = new BufferedReader(new FileReader(filePath));
return gson.fromJson(jsonBuffer, new TypeToken<LinkedList<Request>>(){}.getType());
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
}
return null;
}
I also kept only the lombok
annotations and did not write constructors and getters/setters for my POJOs
Custom gson serialization for an abstract class
How can I read this JSON as List?
One possibility would be to create a custom deserializer that acts like a factory.
The first step would be to define this deserializer
class PersonJsonDeserializer implements JsonDeserializer<Person> {
@Override
public Person deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
String type = json.getAsJsonObject().get("type").getAsString();
switch(type) {
case "zombie":
return context.deserialize(json, Zombie.class);
case "hunter":
return context.deserialize(json, Hunter.class);
default:
throw new IllegalArgumentException("Neither zombie or hunter");
}
}
}
It fetches the value associated with the key "type" and choose the proper type to deserialize the object you're currently reading.
Then, you need to plug this deserializer within the parser.
public class GsonTest {
public static void main(String[] args) {
String json = "[\n" +
" {\"id\":1, \"type\":\"zombie\", \"name\":\"Ugly Tom\", \"uglyness\":42},\n" +
" {\"id\":2, \"type\":\"hunter\", \"name\":\"Shoot in leg Joe\", \"skill\":0}\n" +
"]";
Gson gson = new GsonBuilder().registerTypeAdapter(Person.class, new PersonJsonDeserializer()).create();
Type type = new TypeToken<List<Person>>(){}.getType();
List<Person> list = gson.fromJson(json, type);
for(Person p : list) {
System.out.println(p);
}
}
}
Running it with your example, I get:
Zombie{id=1; name=Ugly Tom; uglyness=42}
Hunter{id=2; name=Shoot in leg Joe; skill=0}
If the value of the type already corresponds to the class name, you might want to use Class.forName
also:
class PersonJsonDeserializer implements JsonDeserializer<Person> {
@Override
public Person deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
String className = json.getAsJsonObject().get("type").getAsString();
className = Character.toUpperCase(className.charAt(0)) + className.substring(1);
try {
return context.deserialize(json, Class.forName(className));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
Deserializing an abstract class in Gson
I'd suggest adding a custom JsonDeserializer for Node
s:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Node.class, new NodeDeserializer())
.create();
You will be able to access the JsonElement
representing the node in the deserializer's method, convert that to a JsonObject
, and retrieve the field that specifies the type. You can then create an instance of the correct type of Node
based on that.
Gson: Deserialize Json into Abstract Class
You'd need to create something like a Gson TypeAdapter
and register it with your Gson
instance.
I'm not sure how/whether this will work for your particular data format, but here's an example that I've used in my own projects:
public class TimeZoneAdapter extends TypeAdapter<TimeZone> {
@Override
public void write(JsonWriter out, TimeZone value) throws IOException {
out.value(value.getID());
}
@Override
public TimeZone read(JsonReader in) throws IOException {
return TimeZone.getTimeZone(in.nextString());
}
}
You would then register it when building a Gson
instance like so:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(TimeZone.class, new TimeZoneAdapter());
Gson gson = builder.create();
Hope this helps!
Gson deserialization of a multimap with abstract classes
I don't really know a way to do this with gson without it being manually or hacky. This is too big to put as a comment, so I'll leave it here as an answer as an idea to help you out.
First, you get a stack overflow because you are calling context.deserialize
on the same parameters which triggers gson
to call the same deserializer, which will call again context.deserialize
and so on, until the stack overflow.
You'll run into the same problem when serializing because you're also just doing context.serialize
.
To avoid this, you'll need to avoid that gson
recurses into calling the serializer's/deserializer's methods. This is very easy to achieve by creating another gson
instance without the adapters:
public class PetAdapter
implements JsonSerializer<Pet>, JsonDeserializer<Pet> {
private final Gson gson = new Gson();
@Override
public Pet deserialize(JsonElement jsonElement, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
EntityType entityType = EntityType.valueOf(jsonElement.getAsJsonObject().get("entityType").getAsString());
switch (entityType) {
case IRON_GOLEM:
return gson.fromJson(jsonElement, EcoPet.class);
case WOLF:
return gson.fromJson(jsonElement, BoostPet.class);
case MAGMA_CUBE:
return gson.fromJson(jsonElement, CombatPet.class);
default:
throw new JsonParseException("Invalid PetType");
}
}
@Override
public JsonElement serialize(Pet src, Type typeOfSrc, JsonSerializationContext context) {
return gson.toJson(src);
}
}
This works, but only if your Pet
implementations do not depend on other custom serializers/deserializers. So as you can imagine this is quite hacky.
Another approach is the manual deserialization. This means that you'd have to go through json element and read the properties like you are reading entityType
and manually build your objects.
Very similarly, I guess (I didn't check this), you could first use context
to deserialize each pet into a Map
of objects and let each pet implement a static method that creates an instance of the specific pet from this map. Something like:
public class IronGolem extends Pet {
public static IronGolem from(Map<String, Object> deserializedPet) {
// here check the map for each thing you need
return new IronGolem(/*pass in every attribute*/);
}
}
Hope this helps.
Related Topics
What Does an Integer That Has Zero in Front of It Mean and How to Print It
How to Get a Index Value from Foreach Loop in Jstl
Java JSON Serialization - Best Practice
How to Encode Url to Avoid Special Characters in Java
How to Upper Case Every First Letter of Word in a String
Java Regex - Overlapping Matches
How to Get a Client's MAC Address from Httpservlet
Should I Retrieve Database Record in Struts2 View Layer
Is the in Relation in Cassandra Bad for Queries
Eclipse/Maven Error: "No Compiler Is Provided in This Environment"
How to Escape Reserved Words in Hibernate's Hql
How to Make a List with Checkboxes in Java Swing
Maximum Size of Hashset, Vector, Linkedlist
Right Way to Write JSON Deserializer in Spring or Extend It