Gson parse class with generic item - java

I have base class WebAnswer,
public class WebAnswer<T> {
private int id;
private T result;
private ErrorModel error;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public T getResult() {
return result;
}
public void setResult(T result) {
this.result = result;
}
public ErrorModel getError() {
return error;
}
public void setError(ErrorModel error) {
this.error = error;
}
}
and inherited class RequestAnsModel
public class RequestAnsModel extends WebAnswer<RequestModel> {
}
I need to deserialize JSON object with Gson, in code I do it like this:
Gson gson = new Gson();
WebAnswer<RequestModel> data = new WebAnswer<RequestModel>();
data = gson.fromJson(response.toString(),data.getClass());
and in Field result I get array of objects com.google.gson.internal.LinkedTreeMap$Node#. But when I do something like this
WebAnswer<RequestModel> data = new RequestAnsModel();
I get correct model where result is object of RequestModel. Are there any ways to desirialize in gson somthing like this correct with Generic without create inherited class?

You should use TypeToken like in the code below:
Gson gson = new Gson();
Type type = new TypeToken<WebAnswer<RequestModel>>() {
}.getType()
WebAnswer<RequestModel> data = gson.fromJson(response.toString(), type);

Related

Deserialize a JSON payload to object base on JSON integer property

I have below classes:
public class Result<T> {
public int code;
public Object meta;
public T data;
}
public class User {
public int id;
public String name;
}
public class Error {
public String field;
public String message;
}
I want to deserialize a JSON payload based on code field. If code >= 10, return Result<ArrayList<Error>>, otherwise return Result<User>
Currently, I map JSON to Result<Object> first, then check the code field. Based on that value I make second map to desired object.
ObjectMapper mapper = new ObjectMapper();
Result<Object> tempResult = mapper.readValue(json, new TypeReference<Result<Object>>() {});
if (tempResult.code < 10) {
Result<User> result = mapper.readValue(json, new TypeReference<Result<User>>() {});
return result;
} else {
Result<ArrayList<Error>> result = mapper.readValue(json, new TypeReference<Result<ArrayList<Error>>>() {});
return result;
}
Is there an elegant way to do this without deserializing it 2 times?
You need to implement custom TypeIdResolver:
class UserTypeIdResolverBase extends TypeIdResolverBase {
#Override
public String idFromValue(Object value) {
throw new IllegalStateException("Not implemented!");
}
#Override
public String idFromValueAndType(Object value, Class<?> suggestedType) {
throw new IllegalStateException("Not implemented!");
}
#Override
public JsonTypeInfo.Id getMechanism() {
return JsonTypeInfo.Id.CUSTOM;
}
#Override
public JavaType typeFromId(DatabindContext context, String id) {
if (Integer.parseInt(id) < 10) {
return context.getTypeFactory().constructType(new TypeReference<Result<User>>() {});
}
return context.getTypeFactory().constructType(new TypeReference<Result<List<Error>>>() {});
}
}
and declare it for a Result class:
#JsonTypeInfo(property = "code", use = JsonTypeInfo.Id.CUSTOM, visible = true)
#JsonTypeIdResolver(UserTypeIdResolverBase.class)
class Result<T>

Gson: How do I parse polymorphic values that can be either lists or strings?

I need to parse a JSON file that contains long list of customers. In the JSON file each customer may have one id as a string:
{
"cust_id": "87655",
...
},
or a few ids as an array:
{
"cust_id": [
"12345",
"45678"
],
...
},
The Customer class is as below:
public class Customer {
#SerializedName("cust_id")
#Expose
private String custId;
public String getCustId() {
return custId;
}
public void setCustId(String custId) {
this.custId = custId;
}
}
I parse the JSON using Gson:
Gson gson = new Gson()
Customers customers1 = gson.fromJson(json, Customers.class)
and it fails with com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_ARRAY when it attempts to parse the array.
The reason of failure is clear.
My question: what is the best way to handle both cases (when id is a string and when it is an array of strings), given I can not change the json file structure?
If you want to handle both scenarios you can use a custom deserializer. Of course, you have to change the "cust_id" variable to be a list or an array.
Main:
String json1 = "{\"cust_id\": \"87655\"}";
String json2 = "{\"cust_id\": [\"12345\", \"45678\"]}";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Customer.class, new CustomerDeserializer());
Gson gson = gsonBuilder.create();
Customer customer1 = gson.fromJson(json1, Customer.class);
System.out.println(customer1);
Customer customer2 = gson.fromJson(json2, Customer.class);
System.out.println(customer2);
Customer
public class Customer {
#SerializedName("cust_id")
private List<String> custId;
public List<String> getCustId() {
return custId;
}
public void setCustId(List<String> custId) {
this.custId = custId;
}
}
CustomerDeserializer
public class CustomerDeserializer implements JsonDeserializer<Customer> {
#Override
public Customer deserialize(JsonElement jsonElement, Type typeOf, JsonDeserializationContext context) throws JsonParseException {
Customer result = null;
Gson gson = new Gson();
try {
// try to deserialize by assuming JSON has a list
result = gson.fromJson(jsonElement, Customer.class);
} catch (JsonSyntaxException jse) {
// error here means JSON has a single string instead of a list
try {
// get the single ID
String custId = jsonElement.getAsJsonObject().get("cust_id").getAsString();
result = new Customer();
result.setCustId(Arrays.asList(new String[] {custId}));
} catch (Exception e) {
// more error handling here
e.printStackTrace();
}
}
return result;
}
}
Output
Customer [custId=[87655]]
Customer [custId=[12345, 45678]]
Try method overriding:
public class Customer {
#SerializedName("cust_id")
#Expose
private String custId;
public void setCustId(String custId) {
this.custId = {custId};
}
public String[] getCustId() {
return custId;
}
#override
public void setCustId(String[] custId) {
this.custId = custId;
}
}
Now In the code all values of CUSTID will be arrays instead of strings
You can just simply specify all values as array, even if is just one value.
{
"cust_id": ["87655"],
...
},
UPDATE: If you cannot change the json, you can bind every field in Customer class except custId and set it manually.
public class Customer {
private String[] custId;
public String getCustId() {
return custId;
}
public void setCustId(String custId) {
custId = new String[] {custId};
}
public void setCustId(String[] custId) {
this.custId = custId;
}
}
And then parse manually:
Gson gson = new Gson();
Customers customers = gson.fromJson(json, Customers.class);
Object custId = new JSONObject(json).get("cust_id");
if (custId instanceof String) {
customers.setCustId((String) custId);
else if (custId instanceof JSONArray) {
customers.setCustId(convertToStringArray(new JSONArray(custId)));
}
Refer this.
Now the problem is that you will have to write your own code on the returned map to get the desired result.

Serialization error using Gson

I am using the following code for serialization:
public class JsonTransformer implements ResponseTransformer {
private Gson gson = new Gson();
#Override
public String render(Object model) {
GsonBuilder gsonBuilder = new GsonBuilder();
return gson.toJson(model);
}
}
I get error:
java.lang.UnsupportedOperationException: Attempted to serialize java.lang.Class: com.soul.seeker.models.Post. Forgot to register a type adapter?
How do I serialize a list a return it in the below code?
get("/data_on_page_load", "application/json", (Request request, Response response) -> {
List<Post> list = Post.findAll();
return list;
}, new JsonTransformer());
pojo class:
public class Post extends Model{
private String title;
private String details;
private String username;
private String userImage;
private String url;
private List categories;
//Getters and Setters removed for brevity
}
update:
When I tried to use it the below way:
public class JsonTransformer implements ResponseTransformer {
private Gson gson = new Gson();
#Override
public String render(Object model) {
GsonBuilder gsonBuilder = new GsonBuilder();
gson = gsonBuilder.registerTypeAdapterFactory(new ClassTypeAdapterFactory()).create();
return gson.toJson(model);
}
}
public class ClassTypeAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
if(!Class.class.isAssignableFrom(typeToken.getRawType())) {
return null;
}
return (TypeAdapter<T>) new ClassTypeAdapter();
}
}
it returns unnecessary information in json object when I pass it back to client side:
Array[2]
0:Object
attributes:Object
cachedChildren:Object
cachedParents:Object
compositeKeyPersisted:false
dirtyAttributeNames:Array[0]
errors:Object
frozen:false
manageTime:true
metaModelLocal:Object
modelRegistryLocal:Object
__proto__:
Object1:Object
length:2
__proto__:Array[0]
It makes sense that ClassTypeAdapter is where I have modify the code to return proper list object as json and exclude irrelevant information, but I am still new to serialization.

Gson property order in android

I have integrated Gson to create the json used in a request for an android application.
Here is my model class
public class TwitterUser {
#Expose
public String gid;
public String icon_url;
public Boolean is_app_user;
#Expose
public String displayName;
public TwitterUser(String l, String i, String url, Boolean app_user) {
gid = i;
displayName = l;
icon_url = url;
is_app_user = app_user;
}
public TwitterUser(String l, String i) {
gid = i;
displayName = l;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getIcon_url() {
return icon_url;
}
public void setIcon_url(String icon_url) {
this.icon_url = icon_url;
}
public Boolean getIs_app_user() {
return is_app_user;
}
public void setIs_app_user(Boolean is_app_user) {
this.is_app_user = is_app_user;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
Here is how i create the json request
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
gson.toJson(twitterUser));
But when I send the request to the server - the order will be rejected. I have to change the request's field order to stay:
gid
displayName
but gson creates other way around, is there any way to achieve this.
Gson doesn't support definition of property order out of the box, but there are other libraries that do. Jackson allows defining this with #JsonPropertyOrder, for example.
But of course Gson has it's way so you can do it by creating your very own Json serializer:
public class TwitterUserSerializer implements JsonSerializer<TwitterUser> {
#Override
public JsonElement serialize(TwitterUser twitterUser, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
object.add("gid", context.serialize(twitterUser.getGid());
object.add("displayName", context.serialize(twitterUser.getDisplayName());
// ...
return object;
}
}
Then of course you need to pass this serializer to Gson during Setup like this:
Gson gson = new GsonBuilder().registerTypeAdapter(TwitterUser.class, new TwitterUserSerializer()).excludeFieldsWithoutExposeAnnotation().create();
String json = gson.toJson(twitterUser);
See also:
Gson User Guide - Custom serializers and deserializers

Json to Object using Gson

I have a class DocumentBO which has the following attributes -
public class DocumentBO implements IStorageBO {
private String aId;
private String studyId;
private Map<AlgorithmsEnum, JobIOStatus> status;
private String text;
private Collection<Sentence> sentences;
public String getaId() {
return aId;
}
public void setaId(String aId) {
this.aId = aId;
}
public String getStudyId() {
return studyId;
}
public void setStudyId(String studyId) {
this.studyId = studyId;
}
public Map<AlgorithmsEnum, JobIOStatus> getStatus() {
return status;
}
public void setStatus(Map<AlgorithmsEnum, JobIOStatus> status) {
this.status = status;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Collection<Sentence> getSentences() {
return sentences;
}
public void setSentences(Collection<Sentence> sentences) {
this.sentences = sentences;
}
}
The AlgorithmsEnum is as follows -
public enum AlgorithmsEnum {
SENTIMENT("sentiment"),
INTENTION("intention"),
TOPIC("topic"),
NER("ner"),
UIMA("uima");
private final String value;
private AlgorithmsEnum(String value) {
this.value = value;
}
public String value() {
return value;
}
#Override
public String toString() {
return value;
}
public static AlgorithmsEnum fromValue(String value) {
if (value != null) {
for (AlgorithmsEnum aEnum : AlgorithmsEnum.values()) {
if (aEnum.value().equals(value)) {
return aEnum;
}
}
}
return null;
}
}
The JobIOStatus is also similar.
I am successfully able to create a JSON string of Collection using GSON using the following TypeToken
Type type = new TypeToken<Collection<DocumentBO>>() {}.getType();
But, when I try to recreate the Collection object using the JSON string returned by Gson and the same TypeToken, the key of the status hashmap is always returned as NULL whereas the value is successfully created. What do you think can be the issue?
The problem is that you have overridden toString() in your enum.
If you look at the JSON being produced, the keys to your Map<AlgorithmsEnum, JobIOStatus> are the lowercase names you're creating. That won't work. Gson has no idea how to recreate the enum from those when you attempt to deserialize the JSON.
If you remove your toString() method it will work just fine.
Alternatively you can use the .enableComplexMapKeySerialization() method in GsonBuilder when serializing which will ignore your toString() method and produce JSON using the default representations of your enum values which is what is required.
There are "well" known :) issues of Gson to serialize Map when the key is derived from object and its not a "native" data type.
Please use this
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.enableComplexMapKeySerialization().create();
Collection<DocumentBO> obj = gson.fromJson(str, type);

Categories