I am trying to serialize/deserialize JSON in Android using GSON. I have two classes that look like this:
public class Session {
#SerializedName("name")
private String _name;
#SerializedName("users")
private ArrayList<User> _users = new ArrayList<User>();
}
and:
public class User {
#SerializedName("name")
private String _name;
#SerializedName("role")
private int _role;
}
I am using GSON for serializing/deserializing the data. I serialize like so:
Gson gson = new Gson();
String sessionJson = gson.toJson(session);
This will produce JSON that looks like this:
{
"name":"hi",
"users":
[{"name":"John","role":2}]
}
And I deserialize like so:
Gson gson = new Gson();
Session session = gson.fromJson(jsonString, Session.class);
I'm getting an error when I make this call.
DEBUG/dalvikvm(739): wrong object type: Ljava/util/LinkedList; Ljava/util/ArrayList;
WARN/System.err(739): java.lang.IllegalArgumentException: invalid value for field
I don't know what this error means. I don't see myself doing anything gravely wrong. Any help? Thanks!
Change your code to this:
public class Session {
#SerializedName("name")
private String _name;
#SerializedName("users")
private List<User> _users = new ArrayList<User>();
}
It's a good practice use Interfaces, and GSON requires that (at least, without extra configuration).
Gson converts the array "[ ]" in javascript, to a LinkedList object.
In your code, GSON tries to inject a LinkedList in the _users field, thinking than that field its a List.
Related
I am using retrofit w/ gson annotations to bind my incoming JSON to a model and its member attributes to types like String, int, float, etc...
public class Location {
#SerializedName("user_id")
#Expose
private int userId;
But how do I bind to a member variable that's of type JSON? I want it to be unstructured which is why I can't just map it to a well-defined model.
#SerializedName("metadata")
#Expose
private JsonObject metadata;
How do I get the above to work?
Looks like I can set the member attribute to JsonElement
#SerializedName("metadata")
#Expose
private JsonElement metadata;
This resolves the issue of binding to JsonNull + JsonObject.
The JSON data returned from the server can either return an object, or if that object is null, it returns an empty string ("").
My problem is that my DTO expects an object, but it sees a string and crashes.
PersonDTO
data class PersonDto(
#SerializedName("firstName") val first: String,
#SerializedName("lastName") val last: String,
#SerializedName("favorites") val favorites: FavoriteDto,
)
FavoriteDto
class FavoriteDto(
#SerializedName("color") val color: String,
#SerializedName("number") val number: Int
)
Different responses from server
"person" : {
"firstName": "Steve",
"lastName" : "Johnson",
"favorites" : {
"color": "Purple",
"number": 25
}
}
...
"person" : {
"firstName": "Steve",
"lastName" : "Johnson",
"favorites" : ""
}
I've heard that I might need a custom GSON deserializer, but I've never done anything with GSON other than the out of the box stuff - so I was hoping for a nudge in the right direction.
Thanks!
Easiest hack is that you can add extra fields in the class with the same serialised name but with a String data type. Like this -
data class PersonDto(
#SerializedName("firstName") val first: String,
#SerializedName("lastName") val last: String,
#SerializedName("favorites") val favorites: FavoriteDto,
#SerializedName("favorites") val favoritesStr: String,
)
As there is nothing in Gson as "Required" field, you'll just get a null in your deserialized object if something is missing in the JSON. So, if there is an empty string the FavoriteDto object will be null and not null otherwise.
EDIT
I'm adding some Java code that I have written earlier. This might help:
public class PersonDto {
private FavoriteDto favorites;
private String favoritesStr;
public FavoriteDto getResponseDataObject() {
return favorites;
}
public void setResponseDataObject(FavoriteDto favorites) {
this.favorites = favorites;
}
public String getResponseDataString() {
return favoritesStr;
}
public void setResponseDataString(String favoritesStr) {
this.favoritesStr = favoritesStr;
}
Defining the Deserializar:
public static class ArrayObjectDualityDeserializer implements JsonDeserializer<PersonDto> {
public PersonDto deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
PersonDto response = new PersonDto();
JsonObject object = json.getAsJsonObject();
if(object.get("favorites").isJsonArray()) {
} else if(object.get("favorites").isJsonObject()) {
try {
FavoriteDto dtoObject = gson.fromJson(object.get("favorites"), FavoriteDto.class);
response.setResponseDataObject(dtoObject);
} catch (JsonSyntaxException e) {
DebugLogger.e("Error " + e);
}
} else if (object.get("favorites").isJsonNull()) {
} else {
response.setResponseDataString(object.get("favorites").getAsString());
}
}
}
And:
public static Gson gson = new GsonBuilder()
.registerTypeAdapter(PersonDto.class, new ArrayObjectDualityDeserializer())
.create();
Lastly:
private static Retrofit retrofit = null;
private static OkHttpClient.Builder httpClient = null;
private static OkHttpClient session_client = null;
httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new SessionOkHttpInterceptor());
session_client = httpClient.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(session_client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
The answer of #Subrato M. is partially correct. In order to work, you should remove #SerializedName("favorites") annotation from both fields in order to work. In this case, Gson won't throw an error when deserialize multiple json fields named favorites because you don't have any field annotated with that name (also don't name fileds the same as expected field name beucase Gson will try to deserialize). Use the custom deserializer to check if is an object or a string.
Nowadays isn't necessary the #Subrato M's hack, you don't get the right json cause you are missing the data keyword before class FavoriteDto and that is why doesn't exists the get and set methods
I have an Api which the api looks like this:
If the response is ok the structure will begin with data keyword for example:
{
"data": {
"name": "Rogelio Volkman",
"address": "27299 Will Bridge Suite 058\nWest Reubenhaven, MI 00736",
"lat": 54.65,
"lng": 111.75,
"phone": "+26(4)5015498663"
}
}
And if response is not Ok(status is not 200) response will not container data keyword so response will look like:
{
"message": "404 Not Found",
"status_code": 404
}
Since this structure is a common structure for all models I intended to create a Generic Deserializer for all models.
This looked like:
public class DataObjectDeserializer implements JsonDeserializer<Object> {
#Override
public Objectdeserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException {
// Get the "data" element from the parsed JSON
JsonElement data = je.getAsJsonObject().get("data");
// Deserialize it. You use a new instance of Gson to avoid infinite recursion
// to this deserializer
return new Gson().fromJson(data, Object.class);
}
}
then registered it as typeAdapter Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Object.class, new DataObjectDeserializer()).create();
Since every object is an instance of Object I supposed this would work (deserialize my Museum model) but it didn't.
In my second attempt I created an abstract class called DataObjectModel and class Museum extended DataObjectModel. then created anoteher deserailizer like:
public class DataObjectDeserializer implements JsonDeserializer {
#Override
public DataObjectModel deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException {
// Get the "data" element from the parsed JSON
JsonElement data = je.getAsJsonObject().get("data");
// Deserialize it. You use a new instance of Gson to avoid infinite recursion
// to this deserializer
return new Gson().fromJson(data, DataObjectModel.class);
}
}
And registered it with Gson like Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(DataObjectModel.class, new DataObjectDeserializer()).create();. But this didn't deserialize Museum model either.
The question is: how can I create a deserializer for parent class which would deserialize child class as well (as my second approach) Or How can I create a generic class with Gson so that every tyoe that is wrapped aroung data can be used with it like:
public class DataObjectDeserializer implements JsonDeserializer {
#Override
public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException {
// Get the "data" element from the parsed JSON
JsonElement data = je.getAsJsonObject().get("data");
// Deserialize it. You use a new instance of Gson to avoid infinite recursion
// to this deserializer
return new Gson().fromJson(data, /* This part not working due to type erasing in java*/ T.calss);
}
}
If you create your response and data structure like this
private class Data {
#SerializedName("name")
private String name;
#SerializedName("address")
private String address;
#SerializedName("lat")
private String lat;
#SerializedName("lng")
private String lng;
#SerializedName("phone")
private String phone;
}
private class Response {
#SerializedName("message")
private String message;
#SerializedName("status_code")
private int statusCode;
#SerializedName("data")
private Data data;
}
you can then do
Response response = new Gson().fromJson(json, Response.class);
if(response.statusCode == 0) {
//everything ok, do something with response.data
} else {
//display response.message, data will be null
}
I'm working on API Requests with Retrofit(1.9.0) and gson library (1.7.0 I have compatibility issues with version 2.3.1 of gson) on Android, I make some request to an API which have same format of response but different content following the url call, but I encounter a problem for a deserialization of one answer which there is array inside. This is an example of the json I want to deserialize :
{
"http_code":200,
"content":[
{
"name":"Groult Christian",
"location":[
48.897655,
2.252462
],
"website":null,
"address":{
"street_address":"XXXXXX",
"city":"XXXXXX",
"state":null,
"postal_code":"XXXXXX",
"country":"XXXXXX"
},
"global_score":0,
"popularity_score":0,
"quality_score":0,
"createdAt":"2015-02-18T02:13:05.068Z",
"updatedAt":"2015-02-18T02:13:05.068Z",
"id":"54e3f531775288ca572872ac"
},
...
]
}
My DeserializerJson and how I call it for retrofit:
public class DeserializerJson<T> implements JsonDeserializer<T> {
#Override
public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException
{
// Get the "content" element from the parsed JSON
JsonElement content = je.getAsJsonObject().get("content");
// Deserialize it. You use a new instance of Gson to avoid infinite recursion
// to this deserializer
return new Gson().fromJson(content, type);
}
}
...
Gson gson = new GsonBuilder()
.registerTypeAdapter(ContentUser.class, new DeserializerJson<ContentUser>())
.registerTypeAdapter(DeviceInfo.class, new DeserializerJson<DeviceInfo>())
.registerTypeAdapter(PlacesModel.class, new DeserializerJson<PlacesModel>())
.create();
RestAdapter restAdapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.BASIC)
.setConverter(new GsonConverter(gson))
...
...
and my different models:
public class PlacesModel {
#SerializedName("name")
#Expose
private String name;
#SerializedName("location")
#Expose
private List<Double> location = new ArrayList<Double>();
#SerializedName("website")
#Expose
private Object website;
#SerializedName("address")
#Expose
private AddressModel address;
#SerializedName("global_score")
#Expose
private Integer globalScore;
#SerializedName("popularity_score")
#Expose
private Integer popularityScore;
#SerializedName("quality_score")
#Expose
private Integer qualityScore;
#SerializedName("createdAt")
#Expose
private String createdAt;
#SerializedName("updatedAt")
#Expose
private String updatedAt;
#Expose
#SerializedName("id")
private String id;
/* Getters and Setters... */
}
public class AddressModel {
#SerializedName("street_address")
#Expose
private String streetAddress;
#SerializedName("city")
#Expose
private String city;
#SerializedName("state")
#Expose
private Object state;
#SerializedName("postal_code")
private String postalCode;
#SerializedName("country")
#Expose
private String country;
/* Getters and Setters... */
}
url call in Api Manager is like this:
#GET("/places")
public void getPlaces(RestCallback<List<PlacesModel>> callback);
But when I do the call I get this error : com.google.gson.JsonParseException: The JsonDeserializer com.google.gson.DefaultTypeAdapters$CollectionTypeAdapter#3b8aa06 failed to deserialize json object
Everything is fine for other call I get all content and so with no problem but one where there is array inside content I got an error and I don't understand why I believed if I just put a list of my model it will be fine but it doesn't work.
I think I miss something so if someone can help me
Thanks in advance
The problem of your issue is that you register DeserializerJson for PlacesModel class and in getPlaces method your response is List<PlacesModel> class. List<PlacesModel> is a different class as PlacesModelso Gson doesn't know how to deserialise List<PlacesModel>. What you have to do is register one more Deserialiser by this method:
.registerTypeAdapter(List.class, new DeserializerJson<List<PlacesModel>>())
If you use more than one type of List (I mean List<PlacesModel> and List< DeviceInfo >) You can define your own TypeAdapter or you cN change list to array and register deserialiser for them as is shown below
.registerTypeAdapter(PlacesModel[].class, new DeserializerJson<PlacesModel[]>())
Now everything works fine for yours json.
I need to create the following JSON Object and than convert it to string using the GSON library (toJson(Object)). However, GSON appends the nameValuePair with each JSON Object, what do I need to do?
"Param_1":
{
"SubParam_1": { type: String, required: true }
}
I'm putting these JSONObjects into a ParameterMap (android HTTP Client) and using GSON to convert the map to JSON String.
First of all the JSON "object" you've written is not even valid JSON!
If anything, you could create a JSON like this:
{
"Param_1": {
"SubParam_1": { "type": "String", "required": true }
}
}
And that's very easy, you just need to have the correct class model, something like this:
public class YourClass {
private Param Param_1;
}
public class Param {
private SubParam SubParam_1;
}
public class SubParam {
private String type;
private boolean required;
}
And then you can use the method .toJson(), like this:
Gson gson = new Gson();
String jsonString = gson.toJson(yourObject);