How do I write a custom JSON deserializer for Gson?
@Override
public User deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
JsonObject jobject = json.getAsJsonObject();
return new User(
jobject.get("id").getAsInt(),
jobject.get("name").getAsString(),
new Timestamp(jobject.get("update_date").getAsLong()));
}
I'm assuming User class has the appropriate constructor.
Custom JSON deserializer using Gson
You have to write a custom deserializer. I'd do something like this:
First you need to include a new class, further than the 2 you already have:
public class Response {
public VkAudioAlbumsResponse response;
}
And then you need a custom deserializer, something similar to this:
private class VkAudioAlbumsResponseDeserializer
implements JsonDeserializer<VkAudioAlbumsResponse> {
@Override
public VkAudioAlbumsResponse deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
JsonArray jArray = (JsonArray) json;
int firstInteger = jArray.get(0); //ignore the 1st int
VkAudioAlbumsResponse vkAudioAlbumsResponse = new VkAudioAlbumsResponse();
for (int i=1; i<jArray.size(); i++) {
JsonObject jObject = (JsonObject) jArray.get(i);
//assuming you have the suitable constructor...
VkAudioAlbum album = new VkAudioAlbum(jObject.get("owner_id").getAsInt(),
jObject.get("album_id").getAsInt(),
jObject.get("title").getAsString());
vkAudioAlbumsResponse.getResponse().add(album);
}
return vkAudioAlbumsResponse;
}
}
Then you have to deserialize your JSON like:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(VkAudioAlbumsResponse.class, new VkAudioAlbumsResponseDeserializer());
Gson gson = gsonBuilder.create();
Response response = gson.fromJson(jsonString, Response.class);
With this approach, when Gson tries to deserialize the JSON into Response
class, it finds that there is an attribute response
in that class that matches the name in the JSON response, so it continues parsing.
Then it realises that this attribute is of type VkAudioAlbumsResponse
, so it uses the custom deserializer you have created to parse it, which process the remaining portion of the JSON response and returns an object of VkAudioAlbumsResponse
.
Note: The code into the deserializer is quite straightforward, so I guess you'll have no problem to understand it... For further info see Gson API Javadoc
Error deserializing JSON response with custom Gson deserializer
Oh, it's a very common mistake. You have to create parameterized types with TypeToken.getParameterized
. So you have to change object : TypeToken<List<Item>>() {}.type
to TypeToken.getParameterized(List::class.java, Item::class.java).type
class ItemsDeserializer : JsonDeserializer<List<Item>> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): List<Item> {
val items: JsonElement = json!!.asJsonObject.get("items")
val listType= TypeToken.getParameterized(List::class.java, Item::class.java).type
return Gson().fromJson(items, listType)
}
}
private val gson = GsonBuilder()
.registerTypeAdapter(TypeToken.getParameterized(List::class.java, Item::class.java).type, ItemsDeserializer())
.create()
Gson custom deserializer just on field
Here is the code for custom deserialization (for Gender) with sample invocation.
1) Gender deserializer is case insensitive
2) Invalid values handled (i.e. input json contains other than male and female)
Main method:-
public static void main(String[] args) {
String jsonString = "{'firstName' : 'john','lastName' : 'stones','gender' : 'male'}";
Gson gson = new GsonBuilder()
.registerTypeAdapter(Person.class, new PersonModelDeserializer())
.create();
Person person = gson.fromJson(jsonString, Person.class);
System.out.println(person.toString());
}
Gender Enum:-
public enum Gender {
MALE, FEMALE
}
Deserializer:-
If the input json doesn't have male or female, the valueOf
method throws IllegalArgumentException which has been handled as well.
The Gender deserialization is case insensitive as well.
public class PersonModelDeserializer implements JsonDeserializer<Person> {
@Override
public Person deserialize(JsonElement paramJsonElement, Type paramType,
JsonDeserializationContext paramJsonDeserializationContext) throws JsonParseException {
Person person = new Gson().fromJson(paramJsonElement.getAsJsonObject(), Person.class);
try {
Gender gender = null;
if (paramJsonElement.getAsJsonObject().get("gender") != null) {
gender = Gender.valueOf(paramJsonElement.getAsJsonObject().get("gender").getAsString().toUpperCase());
}
person.setGender(gender);
} catch (IllegalArgumentException ie) {
System.out.println(ie.getMessage());
System.out.println("Gender cannot be serialized ..");
}
return person;
}
}
Person class:-
public class Person implements Serializable{
private static final long serialVersionUID = 5447375194275313051L;
private String firstName;
private String lastName;
private Gender gender;
... getters and setters
}
Gson custom deserializer for String class
In your StringDeserializer
, this
json.toString()
is calling toString
on a JsonElement
that is specifically a JsonPrimitive
containing a value that is text. Its toString
implementation returns the JSON representation of its content, which is, literally, the String " one two "
. trim
doesn't do what you want it because that String is enclosed in "
.
What you really wanted to do was read the content of the JSON element. You can do that with one of JsonElement
's convenience method: getAsString()
.
So
String a = json.getAsString().trim();
would then print what you expected.
Deserializing json array using gson?
Changing the return type of deserialize()
from Category
to ArrayList<Category>
solved the issue. Rest of the code is correct.
Custom deserialization into a Map using Gson
If you really need to keep your Holder
class as is, the only option I can think of is to create a custom deserializer for your Holder
class.
By doing this, whenever you tell Gson to parse some JSON into a Holder
object, it will use your deserializer, instead of doing it 'the normal way' (i.e., mapping JSON keys to object properties).
Something like this:
private class HolderDeserializer implements JsonDeserializer<Holder> {
@Override
public Holder deserialize(JsonElement json, Type type, JsonDeserializationContext context)
throws JsonParseException {
Type mapType = new TypeToken<Map<String, String>>() {}.getType();
Map<String, String> data = context.deserialize(json, mapType);
return new Holder(data);
}
}
Then you register your deserializer when creating the Gson
object:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Holder.class, new HolderDeserializer());
Gson gson = gsonBuilder.create();
And finally, you parse your JSON like this:
Type responseType = new TypeToken<Map<String, Holder>>() {}.getType();
Map<String, Holder> response = gson.fromJson(jsonLine, responseType);
You may want to look at Gson's docs for custom (de)serialization and this other answer of mine to get more info...
Note: this answer has been edited to make it clearer after discussion in comments/chat.
Gson custom deserialization
You'll need a custom deserializer (http://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/JsonDeserializer.html), something like:
public static class MyJsonAdapter implements JsonDeserializer<List<Person>>
{
List<Person> people = new ArrayList<>();
public List<Person> deserialize( JsonElement jsonElement, Type type, JsonDeserializationContext context )
throws JsonParseException
{
for (each element in the json data array)
{
Person p = context.deserialize(jsonElementFromArray,Person.class );
people.add(p);
}
}
return people;
}
Can I apply a custom deserializer to within another custom deserializer for GSON
The method you're looking for is JsonDeserializationContext.deserialize()
. Per the warning about how to cause an infinite loop, this invokes any relevant custom deserializers you've set up.
I believe replacing the initialization of subItems
inside the loop with a one-liner MySubItems subItems = context.deserialize(element, MySubItems.class);
will do the trick, leaving only that and the check around list.add(subItems)
in the loop body.
Related Topics
Changing the Shapes of Points in Scatter Plot
Difference in System. Exit(0) , System.Exit(-1), System.Exit(1 ) in Java
Singleton with Arguments in Java
Parsing JSON Array into Java.Util.List with Gson
Convert a String (Like Testing123) to Binary in Java
How Is Hashcode() Calculated in Java
How to Reference Another Property in Java.Util.Properties
How to Avoid Constructor Code Redundancy in Java
Using Itextpdf to Trim a Page's Whitespace
Difference Between Break and Continue Statement
Leiningen - How to Add Dependencies for Local Jars
Run Single Test from a Junit Class Using Command-Line
Java: Method to Get Position of a Match in a String
Remove() on List Created by Arrays.Aslist() Throws Unsupportedoperationexception
Java - Best Way to Grab All Strings Between Two Strings? (Regex)
Should I Use Java's String.Format() If Performance Is Important