Custom JSON Deserializer Using Gson

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



Leave a reply



Submit