Gson custom deserializing logic based on field name - android

My class is like:
class Foo {
public String duration;
public String height;
}
And my json data looks like
{"duration":"12200000", "height":"162"}
Now I want to deserialize it by
Foo foo = gson.fromJson(jsonStr, Foo.class);
So that,
foo.duration is "20 mins" (number of minutes),
foo.height is "162cm"
Is this possible to do using Gson?
Thanks!

GSON allows creation of custom deserializers/serializers. Try to read here.
Sorry for without an example.
class FooDeserializer implements JsonDeserializer<Foo>{
#Override
public Foo deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
JsonObject jo = (JsonObject)json;
String a = jo.get("duration").getAsString()+" mins";
String b = jo.get("height").getAsString() + " cm";
//Should be an appropriate constructor
return new Foo(a,b);
}
}
then:
Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class, foo.new FooDeserializer()).create();
and you should receive result as you wish it to get using fromJson(...).

Related

Android kotlin object to json values order [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...

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...

Custom GSON parser exclude object instances based on property value

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.

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

Use GSON to map changing field name to POJO

I am writing an Android app, and using RetroFit to communicate with the REST API. I have all the classes/POJO's written, except one.
Here is the Java for the specific class.
This is the parent class, which is the root of the problem. I need to be able to map the userNameResponse variable to the users name in the JSON below
public class DiTopicStats {
private UserNameResponse userNameResponse;
public UserNameResponse getUserNameResponse() {
return userNameResponse;
}
public void setUserNameResponse(UserNameResponse userNameResponse) {
this.userNameResponse = userNameResponse;
}
}
and the child class, which should be fine as long as I can map to it from the above parent class:
public class UserNameResponse {
//Fields
//Getters and Setters
}
The JSON that is returned contains a field which changes per response. The field is the users name. For example:
{
"sMessage": "",
"diStatus": {
"diTopicWrongAns": {
//Some fields
},
"diOverallStats": {
//Some more fields
},
"diUserStats": {
"John Smith": { //PROBLEM: This name changes
//some other fields
}
}
}
}
So in this case, the name "John Smith" has a dynamic field name. Realistically, it could be any string with letters, numbers, a - or a . in it.
My question is, using RetroFit, how can I create a Java class that lets RetroFit map a field to a variable?
I'm aware you can specify a SerialisedName but can this be done programatically at runtime, because I will have the name or the user at this stage.
Thanks.
Create a pojo called DiUserStats and then define a custom GsonTypeAdapter e.g.
public class DiUserStatsTypeAdapter implements JsonDeserializer<DiUserStats>, JsonSerializer<DiUserStats> {
#Override
public DiUserStats deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
DiUserStats target = new DiUserStats();
JsonObject jsonObject = json.getAsJsonObject();
Map.Entry<String,JsonElement> object =(Map.Entry<String,JsonElement>)jsonObject.entrySet().toArray()[0];
target.setName(object.getKey());
JsonObject targetValues = object.getValue().getAsJsonObject();
/*parse values to your DiUserStats as documented using targetValues.get("fooProperty").getAsString(), etc */
return target;
}
#Override
public JsonElement serialize(DiUserStats src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject obj = new JsonObject();
JsonObject values = new JsonObject();
values.addProperty("foo", src.getFoo);
obj.addProperty(target.getName(), values);
return obj;
}
}
Then when you setup Gson use a builder and add your custom type adapter e.g.
Gson gson = new GsonBuilder().registerTypeAdapter(DiUserStats.class, new DiUserStatsTypeAdapter()).create();

Categories

Resources