Custom GSON parser exclude object instances based on property value - android

When parsing JSON in Android using the GSON parser, I'd like to implement a rule that will exclude any objects from being created based on property value. For example:
{"people": [
{"first_name": "Bob"},
{"first_name": "Bob", "last_name": "Loblaw"}]}
I want to exclude the first person object because it doesn't have a last name property.
Is this possible at parse time?

It is possible with JsonDeserializer.
Suppose you would have POJOs like
public class Response {
#Getter
private List<Person> people = new ArrayList<>();
}
and
public class Person {
#Getter #Setter
private String first_name, last_name;
}
Creating JsonDeserializer like
public class PersonResponseDeserializer implements JsonDeserializer<Response> {
// Create a new gson to make the default parsing for response object
private final Gson gson = new Gson();
#Override
public Response deserialize(JsonElement json, Type typeOfT
, JsonDeserializationContext context) throws JsonParseException {
Response r = gson.fromJson(json, typeOfT);
// Remove all persons from R that have last name null
r.getPeople().removeAll(
r.getPeople().stream().filter( p -> p.getLast_name() == null )
.collect(Collectors.toSet())
);
return r;
}
}
could then be used like
Gson gson = new GsonBuilder()
.registerTypeAdapter(Response.class, new PersonResponseDeserializer())
.create();
Response r = gson.fromJson(s, Response.class);
So this is if it is required to be done at the parse time. Maybe it is otherwise better to loop the People after parsing and exclude Persons without last name then.

Related

GSON sorting keys automatically in toJson() [duplicate]

Seems like Gson.toJson(Object object) generates JSON code with randomly spread fields of the object. Is there way to fix fields order somehow?
public class Foo {
public String bar;
public String baz;
public Foo( String bar, String baz ) {
this.bar = bar;
this.baz = baz;
}
}
Gson gson = new Gson();
String jsonRequest = gson.toJson(new Foo("bar","baz"));
The string jsonRequest can be:
{ "bar":"bar", "baz":"baz" } (correct)
{ "baz":"baz", "bar":"bar" } (wrong sequence)
You'd need to create a custom JSON serializer.
E.g.
public class FooJsonSerializer implements JsonSerializer<Foo> {
#Override
public JsonElement serialize(Foo foo, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
object.add("bar", context.serialize(foo.getBar());
object.add("baz", context.serialize(foo.getBaz());
// ...
return object;
}
}
and use it as follows:
Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class, new FooJsonSerializer()).create();
String json = gson.toJson(foo);
// ...
This maintains the order as you've specified in the serializer.
See also:
Gson User Guide - Custom serializers and deserializers
If GSON doesn't support definition of field order, there are other libraries that do. Jackson allows definining this with #JsonPropertyOrder, for example. Having to specify one's own custom serializer seems like awful lot of work to me.
And yes, I agree in that as per JSON specification, application should not expect specific ordering of fields.
Actually Gson.toJson(Object object) doesn't generate fields in random order. The order of resulted json depends on literal sequence of the fields' names.
I had the same problem and it was solved by literal order of properties' names in the class.
The example in the question will always return the following jsonRequest:
{ "bar":"bar", "baz":"baz" }
In order to have a specific order you should modify fields' names, ex: if you want baz to be first in order then comes bar:
public class Foo {
public String f1_baz;
public String f2_bar;
public Foo ( String f1_baz, String f2_bar ) {
this.f1_baz = f1_baz;
this.f2_bar = f2_bar;
}
}
jsonRequest will be { "f1_baz ":"baz", "f2_bar":"bar" }
Here's my solution for looping over json text files in a given directory and writing over the top of them with sorted versions:
private void standardizeFormat(File dir) throws IOException {
File[] directoryListing = dir.listFiles();
if (directoryListing != null) {
for (File child : directoryListing) {
String path = child.getPath();
JsonReader jsonReader = new JsonReader(new FileReader(path));
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(LinkedTreeMap.class, new SortedJsonSerializer()).create();
Object data = gson.fromJson(jsonReader, Object.class);
JsonWriter jsonWriter = new JsonWriter(new FileWriter(path));
jsonWriter.setIndent(" ");
gson.toJson(data, Object.class, jsonWriter);
jsonWriter.close();
}
}
}
private class SortedJsonSerializer implements JsonSerializer<LinkedTreeMap> {
#Override
public JsonElement serialize(LinkedTreeMap foo, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
TreeSet sorted = Sets.newTreeSet(foo.keySet());
for (Object key : sorted) {
object.add((String) key, context.serialize(foo.get(key)));
}
return object;
}
}
It's pretty hacky because it depends on the fact that Gson uses LinkedTreeMap when the Type is simply Object. This is an implementation details that is probably not guaranteed. Anyway, it's good enough for my short-lived purposes...

How Gson knows which json element is to use which Deserializer

I have the following json input. And know i am using Gson to parse.
{
“type”: “type1”,
“date”: “Tue, 16 May 2017 07:09:33 +0000”,
“body”:
{
“formatA_1”: “aaa”,
“formatA_2”: “bbbcccddd”
}
"other": "info"
}
public class Data {
private String type;
private Long date;
private Body body;
private String other;
...
}
As i want to convert the date to long, So i implement the custom DateDeserializer.
public class DateDeserializer implements JsonDeserializer<Long> {
#Override
public Long deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return DateConvertUtils.convertStringDatetoLong(json.getAsString(), DateConvertUtils.SERVER_DATE_FORMAT);
}
}
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Long.class, new DateDeserializer());
Gson gson = gsonBuilder.create();
Data data = gson.fromJson(json, Data.class);
This is working. But i wonder, how the Gson knows that only the "date" element needs to use the DateDeserializer? How it knows that other elements no need to use DateDeserializer?
If I put more other custom deserializers, how it would know which element is to use which deserializer?
Thanks a lot.
Gson uses the last added deserializer for given type and uses the deserializer for all cases. In your case, it will use your deserializer for any and all Longs

MappingFacebook JSON response to POJO

I am making an API call to Facebook and receiving the following Json object:
{"first_name":"FirstName",
"last_name":"LastName",
"email":"email#email.com",
"picture":{"data":{"is_silhouette":true,"url":"pictureUrl"}},"id":"12345"}
Instead of deserializing the object manually, I am currently using Gson for it, like this:
FacebookProfileModel facebookProfileModel = new Gson().fromJson(object.toString(), FacebookProfileModel.class);
Here's how my POJO looks like:
#SerializedName("first_name")
String mFirstName;
#SerializedName("last_name")
String mLastName;
#SerializedName("email")
String mEmail;
#SerializedName("url")
String mUrl;
Obviously, I am receiving all the values except for the url, since the value is in 2 Json objects: picture and data. I guess one possible solution but not the best would be to create the Picture object within the Facebook Model and then the Data object within the Picture object but feels bad creating 2 more pojos for a String. Any other solutions?
There is no annotation based solution for this. However, the custom de-serializer would resolve this problem.
Custom Deserializer:-
public class FacebookProfileModelDeserializer implements JsonDeserializer<FacebookProfileModel> {
#Override
public FacebookProfileModel deserialize(JsonElement paramJsonElement, Type paramType,
JsonDeserializationContext paramJsonDeserializationContext) throws JsonParseException {
String url = paramJsonElement.getAsJsonObject().get("picture").getAsJsonObject().get("data").getAsJsonObject()
.get("url").getAsString();
FacebookProfileModel facebookProfileModel = new Gson().fromJson(paramJsonElement.getAsJsonObject(),
FacebookProfileModel.class);
facebookProfileModel.setmUrl(url);
return facebookProfileModel;
}
}
Main method:-
public static void main(String[] args) {
String jsonString = "{\"first_name\":\"FirstName\",\"last_name\":\"LastName\",\"email\":\"email#email.com\",\"picture\":{\"data\":{\"is_silhouette\":true,\"url\":\"pictureUrl\"}},\"id\":\"12345\"}";
Gson gson = new GsonBuilder()
.registerTypeAdapter(FacebookProfileModel.class, new FacebookProfileModelDeserializer())
.create();
FacebookProfileModel faceBookProfileModel = gson.fromJson(jsonString, FacebookProfileModel.class);
System.out.println(faceBookProfileModel.toString());
}

Retrofit gson converter for nested Json Object with Date property

JSON structure looks like the following:
"blabla": 1234,
"blabla2": "1234",
"object": {
"property1": "1234",
"property2": "blablab",
"property3": "12345",
"property4": Date object,
}
}
Due to this structure, I have implemented a custom deserializer and passed it in the TypeAdapter:
.registerTypeAdapter(Date.class, new DateDeserializer())
.registerTypeAdapter(GenericNotificationResponse.class, new NotificationDeserializer())
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
.create();
public class NotificationDeserializer implements JsonDeserializer<GenericNotificationResponse> {
#Override
public GenericNotificationResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject content = json.getAsJsonObject();
GenericNotificationResponse message = new Gson().fromJson(json, typeOfT);
JsonElement notification = content.get("notification");
if (message.mType == 1)
message.mNotification = (new Gson().fromJson(notification, Model1.class));
else if (message.mType == 2)
message.mNotification = (new Gson().fromJson(notification, Model2.class));
return message;
}
}
And the deserialization of the inner object goes fine. Until recently, when I changed the model and started receiving a Date object as well, as shown in the JSON structure, the last property. For some reason, it cannot parse it and it throws an error, so it seems like the DateDeserializer that I'm passing in the TypeAdapter is not called due to the these lines:
message.mNotification = (new Gson().fromJson(notification, Model1.class));
message.mNotification = (new Gson().fromJson(notification, Model2.class));
The DateDeserializer works since I'm using it within other models and it does the trick. Is there any way I can make the deserialization of the date property in the inner json Object? Thank you!
When you do this:
message.mNotification = (new Gson().fromJson(notification, Model1.class));
You are deserializing with a new Gson() instance that does not have the DateDeserializer.
Try something like this:
new GsonBuilder().registerTypeAdapter(Date.class, new DateDeserializer())
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
.create().fromJson(notification, Model1.class));
Same thing for Model2 obviously.

Using GSON to deserialize nested array

Giving
JSON
// imagine this is JSON of a city
{
"title" : "Troy"
"people" : [
{
{
"title" : "Hector",
"status" : "Dead"
},
{
"title" : "Paris",
"status" : "Run Away"
}
},
...
],
"location" : "Mediteranian",
"era" : "Ancient",
...
}
City
public class City {
#SerializeName("title")
String title;
#SerializeName("people")
List<Person> people;
#SerializeName("location")
String location;
#SerializeName("era")
String era;
...
}
Person
public class Person {
#SerializeName("title")
private String title;
#SerializeName("status")
private String status;
}
If having string of JSON above, it is possible to create list of person
A. without having to deserialize City first like following
City city = new Gson().fromJson(json, City.class)
ArrayList<Person> people = city.people;
And
B. without having to convert string to JSONObject, get JSONArray and then convert back to string like following
String peopleJsonString = json.optJSONArray("people").toString
ArrayList<Person> people = new Gson().fromJSON(peopleJsonString, Person.class);
You can use a custom JsonDeserializer, which is part of Gson (com.google.gson.JsonDeserializer).
Simple example:
public class WhateverDeserializer implements JsonDeserializer<Whatever> {
#Override
public Whatever deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
Whatever whatever = new Whatever();
// Fetch the needed object here
// whatever.setNeededObject(neededObject);
return whatever;
}
}
You can then apply this deserializer like this:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Whatever.class, new WhateverDeserializer())
.create();
There is a full example of how to use a custom deserializer, including a super detailed explanation, on this page: http://www.javacreed.com/gson-deserialiser-example/
I don't think you can get the list directly without parsing the json array. You need to parse the array. And it would be faster via Gson;
If you strictly need (only array) and you won't be using any other json object . Simply delete them, so that gson won't parse them.

Categories

Resources