Gson TypeAdapters cross-reference problem [duplicate] - java

I am trying to use google gson TypeAdapter for converting nested JSON into nested Java object having implementation of TypeAdapter for each class. But I don't want to write complete read() method logic in single adapter class. I have referred few questions and blog examples over net. But complete read logic is in single class.
For small nested object its fine to have logic in single Adapter but for big object (having more than 10-15 fields in each class) it's not good.
[Update]
For example json keys look same as of class attributes, but in actual I will be getting input as hyphen-separated-small-case keys instead of Camel case keys. So my json and java classes attribute names will not be same hence I have to write my custom logic for mapping.
E.g.
Sample Json input :
{
"id": 1,
"name": "Alex",
"emailId": "alex#gmail.com",
"address": {
"address": "21ST & FAIRVIEW AVE",
"district": "district",
"city": "EATON",
"region": "PA",
"postalCode": "18044",
"country": "US"
}
}
And Java beans are as below :
//Employee object class
public class Employee {
private int id;
private String name;
private String emailId;
private Address address;
..
}
//Address object class
public class Address {
private String address;
private String district;
private String city;
private String region;
private String postalCode;
private String country;
..
}
I want to have two different adapters and integrate multiple adapters in read() method.
public class EmployeeAdapter extends TypeAdapter<Employee> {
#Override
public void write(JsonWriter out, Employee employee) throws IOException {
//
}
#Override
public Employee read(JsonReader jsonReader) throws IOException {
//read logic for employee class using AddressAdapter for address json
}
}
public class AddressAdapter extends TypeAdapter<Address> {
#Override
public void write(JsonWriter out, Address address) throws IOException {
//
}
#Override
public Address read(JsonReader jsonReader) throws IOException {
//read logic for Address class
}
}
How can I use AddressAdapter inside EmployeeAdapter?

I use a TypeAdapterFactory for this kind of thing. It allows passing the gson instance to the TypeAdapter instance.
(In the example below, I left in passing "rawType" to the TypeAdapter instance, since its often useful. Pull that out if not needed.)
Example TypeAdapterFactory:
public class ContactTypeAdapterFactory implements TypeAdapterFactory {
// Add #SuppressWarnings("unchecked") as needed.
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
final Class<? super T> rawClass = typeToken.getRawType();
if (Employee.class.isAssignableFrom(rawClass)) {
// Return EmployeeAdapter for Employee class
return EmployeeAdapter.get(rawClass, gson);
}
if (Address.class.isAssignableFrom(rawClass)) {
// Return AddressAdapter for Address class
return AddressAdapter.get(rawClass, gson);
}
return null; // let Gson find somebody else
}
private static final class EmployeeAdapter<T> extends TypeAdapter<T> {
private final Gson gson;
private final Class<? super T> rawClass; // Not used in this example
private EmployeeAdapter(Class<? super T> rawClass, Gson gson) {
this.rawClass = rawClass;
this.gson = gson;
}
private static <T> TypeAdapter<T> get(Class<? super T> rawClass, Gson gson) {
// Wrap TypeAdapter in nullSafe so we don't need to do null checks
return new EmployeeAdapter<>(rawClass, gson).nullSafe();
}
#Override
public void write(JsonWriter out, T value)
throws IOException {
// We should only ever be here for Employee types
// Cast value to Employee
Employee record = (Employee)value;
// Output start of JSON object
out.beginObject();
// Output key / value pairs
out.name("name");
gson.getAdapter(String.class).write(out, record.getName());
// [...]
out.name("address");
gson.getAdapter(Address.class).write(out, record.getAddress());
// Output end of JSON object
out.endObject();
}
#Override
public T read(JsonReader in)
throws IOException {
String fieldName;
// Create an empty Employee object
Employee record = new Employee();
// Consume start of JSON object
in.beginObject();
// Iterate each key/value pair in the json object
while (in.hasNext()) {
fieldName = in.nextName();
switch (fieldName) {
case "name":
record.setName(gson.getAdapter(String.class).read(in));
break;
// [...]
case "address":
record.setAddress(gson.getAdapter(Address.class).read(in));
break;
default:
// Skip any values we don't support
in.skipValue();
}
}
// Consume end of JSON object
in.endObject();
// Return new Object
return (T)record;
}
}
private static final class AddressAdapter<T> extends TypeAdapter<T> {
private final Gson gson;
private final Class<? super T> rawClass; // Not used in this example
private AddressAdapter(Class<? super T> rawClass, Gson gson) {
this.rawClass = rawClass;
this.gson = gson;
}
private static <T> TypeAdapter<T> get(Class<? super T> rawClass, Gson gson) {
// Wrap TypeAdapter in nullSafe so we don't need to do null checks
return new AddressAdapter<>(rawClass, gson).nullSafe();
}
#Override
public void write(JsonWriter out, T value)
throws IOException {
// We should only ever be here for Address types
// Cast value to Address
Address record = (Address)value;
// Output start of JSON object
out.beginObject();
// Output key / value pairs
out.name("address");
gson.getAdapter(String.class).write(out, record.getName());
// [...]
out.name("country");
gson.getAdapter(String.class).write(out, record.getCountry());
// Output end of JSON object
out.endObject();
}
#Override
public T read(JsonReader in)
throws IOException {
String fieldName;
Address record = new Address();
in.beginObject();
// Iterate each parameter in the json object
while (in.hasNext()) {
fieldName = in.nextName();
switch (fieldName) {
case "address":
record.setAddress(gson.getAdapter(String.class).read(in));
break;
// [...]
case "country":
record.setCountry(gson.getAdapter(String.class).read(in));
break;
default:
in.skipValue();
}
}
in.endObject();
return (T)record;
}
}
}
Use:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new ContactTypeAdapterFactory())
.create();
Employee employee = gson.fromJson(jsonString, Employee.class);

I had the same issue and found a suitable solution for me.
You can get a new TypeAdapter<T> instance with help of a Gson object and its method getAdapter(Class<T> type).
So your provided example would look like this:
Java Beans:
//Employee object class
#JsonAdapter(EmployeeAdapter.class)
public class Employee {
private int id;
private String name;
private String emailId;
private Address address;
..
}
//Address object class
#JsonAdapter(AddressAdapter.class)
public class Address {
private String address;
private String district;
private String city;
private String region;
private String postalCode;
private String country;
..
}
Type Adapters:
public class EmployeeAdapter extends TypeAdapter<Employee> {
#Override
public void write(JsonWriter out, Employee employee) throws IOException {
//
}
#Override
public Employee read(JsonReader jsonReader) throws IOException {
Employee employee = new Employee();
jsonReader.beginObject();
//read your Employee fields
TypeAdapter<Address> addressAdapter = new Gson().getAdapter(Address.class);
employee.setAddress(addressAdapter.read(jsonReader);
return employee;
}
}
public class AddressAdapter extends TypeAdapter<Address> {
#Override
public void write(JsonWriter out, Address address) throws IOException {
//
}
#Override
public Address read(JsonReader jsonReader) throws IOException {
Address address = new Address();
//read your Address fields
return address;
}
}
With this solution you have the benefits of a loosely coupled code, because of the only dependency in the Beans JsonAdapter annotation.
Addtional you split the read / write logic for each Bean to its own TypeAdapter.

You can create a new instance of AddressAdapter encapsulated in EmployeeAdapter. Please go through following example.
public class EmployeeAdapter extends TypeAdapter<Employee> {
//private instance of address adapter
private AddressAdapter addressAdapter = new AddressAdapter();
#Override
public void write(JsonWriter out, Employee employee) throws IOException {
//TODO: do your stuff to Employee class
//manually do it to Address class
addressAdapter.write(out, employee.getAddress());
}
#Override
public Employee read(JsonReader jsonReader) throws IOException {
//your new instance of employee
Employee employee = new Employee();
//TODO: read logic for employee class using AddressAdapter for address json
//read from Address class
Address address = addressAdapter.read(jsonReader);//you may need only portion of address available, simply grab that string as same as other properties if needed
employee.setAddress(address);
}
}

Related

Jackson unwrapping of null value objects

Let's say I have Description value object:
#JsonInclude(value = JsonInclude.Include.ALWAYS)
public class Description {
#Column(name = "DESCRIPTION")
private final String description;
}
and Product entity:
#Entity
public class Product extends AbstractEntity<Long> {
#JsonUnwrapped
private final Description description;
}
I created custom serializer for Description:
static class DescriptionSerializer extends StdSerializer<Description> {
DescriptionSerializer() {
super(Description.class);
}
#Override
public void serialize(Description value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
if (value != null) {
jgen.writeString(value.getDescription());
} else {
jgen.writeNull();
}
}
}
When I create:
Product product = new Product(new Description("description"));
and serialize it:
String result = mapper.writeValueAsString(spec);
it returns JSON: {"description":"description"}
When I create:
Product product = new Product(null);
it returns {},
but I would expect {"description":null}
If I remove #JsonUnwrapped, it works as I'd expect, but for non-null Description, it would create nested object
Is there a way to keep unwrapping for fields with null value objects in a similar way how it is done for built-in Java types?
To have what you want you should try with:
Product p = new Product(new Description(null));
In the json
{"description":"description"}
the Description, as a container of other fields exists, what is null is the value of the description field inside the Description object.
The two kind of serializations have different meaning.
To easily manage it in your code you can have a class like:
public class Product {
private Description description;
public Product(Description d) {
this.description = d;
}
public Product() {
this(new Description()); //all fields are null
}
public Product(String description) {
return new Product(new Description(description));
}
}
The last two constructor I would prefer as a factory methods, but it is just matter of code style.

Convert nested JSON to nested Java object using gson TypeAdapter

I am trying to use google gson TypeAdapter for converting nested JSON into nested Java object having implementation of TypeAdapter for each class. But I don't want to write complete read() method logic in single adapter class. I have referred few questions and blog examples over net. But complete read logic is in single class.
For small nested object its fine to have logic in single Adapter but for big object (having more than 10-15 fields in each class) it's not good.
[Update]
For example json keys look same as of class attributes, but in actual I will be getting input as hyphen-separated-small-case keys instead of Camel case keys. So my json and java classes attribute names will not be same hence I have to write my custom logic for mapping.
E.g.
Sample Json input :
{
"id": 1,
"name": "Alex",
"emailId": "alex#gmail.com",
"address": {
"address": "21ST & FAIRVIEW AVE",
"district": "district",
"city": "EATON",
"region": "PA",
"postalCode": "18044",
"country": "US"
}
}
And Java beans are as below :
//Employee object class
public class Employee {
private int id;
private String name;
private String emailId;
private Address address;
..
}
//Address object class
public class Address {
private String address;
private String district;
private String city;
private String region;
private String postalCode;
private String country;
..
}
I want to have two different adapters and integrate multiple adapters in read() method.
public class EmployeeAdapter extends TypeAdapter<Employee> {
#Override
public void write(JsonWriter out, Employee employee) throws IOException {
//
}
#Override
public Employee read(JsonReader jsonReader) throws IOException {
//read logic for employee class using AddressAdapter for address json
}
}
public class AddressAdapter extends TypeAdapter<Address> {
#Override
public void write(JsonWriter out, Address address) throws IOException {
//
}
#Override
public Address read(JsonReader jsonReader) throws IOException {
//read logic for Address class
}
}
How can I use AddressAdapter inside EmployeeAdapter?
I use a TypeAdapterFactory for this kind of thing. It allows passing the gson instance to the TypeAdapter instance.
(In the example below, I left in passing "rawType" to the TypeAdapter instance, since its often useful. Pull that out if not needed.)
Example TypeAdapterFactory:
public class ContactTypeAdapterFactory implements TypeAdapterFactory {
// Add #SuppressWarnings("unchecked") as needed.
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
final Class<? super T> rawClass = typeToken.getRawType();
if (Employee.class.isAssignableFrom(rawClass)) {
// Return EmployeeAdapter for Employee class
return EmployeeAdapter.get(rawClass, gson);
}
if (Address.class.isAssignableFrom(rawClass)) {
// Return AddressAdapter for Address class
return AddressAdapter.get(rawClass, gson);
}
return null; // let Gson find somebody else
}
private static final class EmployeeAdapter<T> extends TypeAdapter<T> {
private final Gson gson;
private final Class<? super T> rawClass; // Not used in this example
private EmployeeAdapter(Class<? super T> rawClass, Gson gson) {
this.rawClass = rawClass;
this.gson = gson;
}
private static <T> TypeAdapter<T> get(Class<? super T> rawClass, Gson gson) {
// Wrap TypeAdapter in nullSafe so we don't need to do null checks
return new EmployeeAdapter<>(rawClass, gson).nullSafe();
}
#Override
public void write(JsonWriter out, T value)
throws IOException {
// We should only ever be here for Employee types
// Cast value to Employee
Employee record = (Employee)value;
// Output start of JSON object
out.beginObject();
// Output key / value pairs
out.name("name");
gson.getAdapter(String.class).write(out, record.getName());
// [...]
out.name("address");
gson.getAdapter(Address.class).write(out, record.getAddress());
// Output end of JSON object
out.endObject();
}
#Override
public T read(JsonReader in)
throws IOException {
String fieldName;
// Create an empty Employee object
Employee record = new Employee();
// Consume start of JSON object
in.beginObject();
// Iterate each key/value pair in the json object
while (in.hasNext()) {
fieldName = in.nextName();
switch (fieldName) {
case "name":
record.setName(gson.getAdapter(String.class).read(in));
break;
// [...]
case "address":
record.setAddress(gson.getAdapter(Address.class).read(in));
break;
default:
// Skip any values we don't support
in.skipValue();
}
}
// Consume end of JSON object
in.endObject();
// Return new Object
return (T)record;
}
}
private static final class AddressAdapter<T> extends TypeAdapter<T> {
private final Gson gson;
private final Class<? super T> rawClass; // Not used in this example
private AddressAdapter(Class<? super T> rawClass, Gson gson) {
this.rawClass = rawClass;
this.gson = gson;
}
private static <T> TypeAdapter<T> get(Class<? super T> rawClass, Gson gson) {
// Wrap TypeAdapter in nullSafe so we don't need to do null checks
return new AddressAdapter<>(rawClass, gson).nullSafe();
}
#Override
public void write(JsonWriter out, T value)
throws IOException {
// We should only ever be here for Address types
// Cast value to Address
Address record = (Address)value;
// Output start of JSON object
out.beginObject();
// Output key / value pairs
out.name("address");
gson.getAdapter(String.class).write(out, record.getName());
// [...]
out.name("country");
gson.getAdapter(String.class).write(out, record.getCountry());
// Output end of JSON object
out.endObject();
}
#Override
public T read(JsonReader in)
throws IOException {
String fieldName;
Address record = new Address();
in.beginObject();
// Iterate each parameter in the json object
while (in.hasNext()) {
fieldName = in.nextName();
switch (fieldName) {
case "address":
record.setAddress(gson.getAdapter(String.class).read(in));
break;
// [...]
case "country":
record.setCountry(gson.getAdapter(String.class).read(in));
break;
default:
in.skipValue();
}
}
in.endObject();
return (T)record;
}
}
}
Use:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new ContactTypeAdapterFactory())
.create();
Employee employee = gson.fromJson(jsonString, Employee.class);
I had the same issue and found a suitable solution for me.
You can get a new TypeAdapter<T> instance with help of a Gson object and its method getAdapter(Class<T> type).
So your provided example would look like this:
Java Beans:
//Employee object class
#JsonAdapter(EmployeeAdapter.class)
public class Employee {
private int id;
private String name;
private String emailId;
private Address address;
..
}
//Address object class
#JsonAdapter(AddressAdapter.class)
public class Address {
private String address;
private String district;
private String city;
private String region;
private String postalCode;
private String country;
..
}
Type Adapters:
public class EmployeeAdapter extends TypeAdapter<Employee> {
#Override
public void write(JsonWriter out, Employee employee) throws IOException {
//
}
#Override
public Employee read(JsonReader jsonReader) throws IOException {
Employee employee = new Employee();
jsonReader.beginObject();
//read your Employee fields
TypeAdapter<Address> addressAdapter = new Gson().getAdapter(Address.class);
employee.setAddress(addressAdapter.read(jsonReader);
return employee;
}
}
public class AddressAdapter extends TypeAdapter<Address> {
#Override
public void write(JsonWriter out, Address address) throws IOException {
//
}
#Override
public Address read(JsonReader jsonReader) throws IOException {
Address address = new Address();
//read your Address fields
return address;
}
}
With this solution you have the benefits of a loosely coupled code, because of the only dependency in the Beans JsonAdapter annotation.
Addtional you split the read / write logic for each Bean to its own TypeAdapter.
You can create a new instance of AddressAdapter encapsulated in EmployeeAdapter. Please go through following example.
public class EmployeeAdapter extends TypeAdapter<Employee> {
//private instance of address adapter
private AddressAdapter addressAdapter = new AddressAdapter();
#Override
public void write(JsonWriter out, Employee employee) throws IOException {
//TODO: do your stuff to Employee class
//manually do it to Address class
addressAdapter.write(out, employee.getAddress());
}
#Override
public Employee read(JsonReader jsonReader) throws IOException {
//your new instance of employee
Employee employee = new Employee();
//TODO: read logic for employee class using AddressAdapter for address json
//read from Address class
Address address = addressAdapter.read(jsonReader);//you may need only portion of address available, simply grab that string as same as other properties if needed
employee.setAddress(address);
}
}

Convert Java interface to JSON String using GSON without encapsulating serialization in a JSON object?

Background
I want to serialize an interface into a JSON object. For instance, say I have the following interface:
public interface Person {
String getName();
int getAge();
}
Which is implemented by the class PersonImpl:
public class PersonImpl implements Person {
private String name;
private int age;
public PersonImpl(String name, int age) {
this.name = name;
this.age = age;
}
#Override
public String getName() {
return name;
}
#Override
public int getAge() {
return age;
}
}
PersonSerializer.java is used to serialize the interface using GSON:
public class PersonSerializer implements JsonSerializer<Person> {
#Override
public JsonElement serialize(Person src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject person = new JsonObject();
person.addProperty("name", src.getName());
person.addProperty("age", src.getAge());
return person; // Breakpoint BP1
}
}
I then use a GsonBuilder to serialize the interface:
Person person = new PersonImpl("Bob", 42);
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Person.class, new PersonSerializer());
Gson gson = builder.create();
String personAsJsonString = gson.toJson(person); // Breakpoint BP2
Issue
The problem is the Person class is being serialized as follows:
// Not what I want
{
"person": {
"name": "Bob",
"age": 42
}
}
However, I only want the data within person:
// What I want
{
"name": "Bob",
"age": 42
}
Troubleshooting & Debugging
At Breakpoint BP1 (noted via the comment), the String value of person is exactly what I want. However, by Breakpoint BP2, after GSON completes serialization of the interface (i.e. personAsJsonString), it is the undesired result.
How can I get GSON to serialize a custom interface without encapsulating the result in a JSON Object?
Edit (root cause found)
I completely failed to mention that I was using the Decorator Pattern, which happened to be the root cause of the issue.
public abstract class PersonDecorator implements Person {
protected Person person;
public PersonDecorator(Person person) {
this.person = person;
}
#Override
public String getName() {
return person.getName();
}
#Override
public int getAge() {
return person.getAge();
}
}
Below is an example Decorator:
public class MrPersonDecorator extends PersonDecorator {
public MrPersonDecorator(Person person) {
super(person);
}
#Override
public String getName() {
return "Mr. " + person.getName();
}
}
So the issue happened when creating a Person instance via a decorator:
Person person = new MrPersonDecorator(new PersonImpl("Bob", 42));
In this case, I was not getting a reference to the interface itself, but rather the decorator, which also contains an instance of the interface along with implementing it.
Gson doesn't provide such mechanics (note, that it's not a principal constraint, e.g. Jackson provides it via #JsonUnwrapped annotation or SerializationFeature.UNWRAP_ROOT_VALUE on mapper).
What you can do is to either provide custom serializer for adapter like this:
public class PersonDecoratorSerializer implements JsonSerializer<PersonDecorator> {
#Override
public JsonElement serialize(PersonDecorator src, Type typeOfSrc, JsonSerializationContext context) {
return context.serialize(src.getOriginalPerson());
}
}
To avoid violating encapsulation you can add package-private getOriginalPerson() method to PersonDecorator and create serializer as inner static class so it can access this method.
Or wrap GSON calls to extract your person value: replace gson.toJson(person) with gson.toJsonTree(person).getAsJsonObject().get("person").toString():
public static String toJson(Person person, Gson gson) {
JsonObject object = gson.toJsonTree(person).getAsJsonObject();
// Checks whether Person was PersonDecorator or not
if (object.has("person")) {
return object.get("person").toString();
} else {
return object.toString();
}
}

How to deserialize JSON Array contained an abstract class without modifying a parent class?

I'm trying to deserialize JSON Array, which is persisted into my MongoDB, to a Java object by using Jackson. I found many tutorials mentioned to handle this polymorphism by adding:
#JsonTypeInfo(use=Id.CLASS,property="_class")
to a Super-class. However, in my case, I can't be able to modify the Super-class. So, are there some solutions to solve it without modifying the Super-class? Here is my code:
public class User {
#JsonProperty("_id")
private String id;
private List<Identity> identities; // <-- My List contains objects of an abstract class; Identity
public User(){
identities = new ArrayList<Identity>();
}
public static Iterable<User> findAllUsers(){
return users().find().as(User.class); // Always give me the errors
}
/*More code*/
}
It always give me the error - Can not construct instance of securesocial.core.Identity, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information.
You can use #JsonDeserilize annotation to bind a concrete implementation class to an abstract class. If you cannot modify your abstract class you can use the Jackson Mix-in annotations to tell Jackson how to find the implementation class.
Here is an example:
public class JacksonAbstract {
public static class User {
private final String id;
private final List<Identity> identities;
#JsonCreator
public User(#JsonProperty("_id") String id, #JsonProperty("identities") List<Identity> identities) {
this.id = id;
this.identities = identities;
}
#JsonProperty("_id")
public String getId() {
return id;
}
public List<Identity> getIdentities() {
return identities;
}
}
public static abstract class Identity {
public abstract String getField();
}
#JsonDeserialize(as = IdentityImpl.class)
public static abstract class IdentityMixIn {
}
public static class IdentityImpl extends Identity {
private final String field;
public IdentityImpl(#JsonProperty("field") String field) {
this.field = field;
}
#Override
public String getField() {
return field;
}
}
public static void main(String[] args) throws IOException {
User u = new User("myId", Collections.<Identity>singletonList(new IdentityImpl("myField")));
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(Identity.class, IdentityMixIn.class);
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(u);
System.out.println(json);
System.out.println(mapper.readValue(json, User.class));
}
}

How to grab JSON Array and use gson to parse each json object? (Retrofit)

I am returning an array of results with my json Objects, and I am trying to use my customObjectResponse class to pull out each of the fields within each of the objects... the problem it is expecting an object so how do I edit my class to allow it to take in an array of object to be able to then call the fields of each object... I am confused as to what needs to be added:
Here is a response example of what is being passed to be used:
[
{
itemId: 'dfsdfsdf343434',
name: 'tests',
picture: '6976-7jv8h5.jpg',
description: 'testy.',
dateUpdated: 1395101819,
}
]
Here is my response Object Class:
public class ObjResponse{
private String itemId;
private String name;
private String picture;
private String description;
private String location;
private int dateUpdated;
private String msg;
//gridview constructor
public ObjResponse(String picture) {
this.picture = picture;
}
//public constructor
public ObjResponse() {
}
public String getItemId() {
return itemId;
}
public void setItemId(String itemId) {
this.itemId = itemId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPicture() {
return picture;
}
public void setPicture(String picture) {
this.picture = picture;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getDateUpdated() {
return dateUpdated;
}
public void setDateUpdated(int dateUpdated) {
this.dateUpdated = dateUpdated;
}
public String getMsg() {
return msg;
}
}
what I am trying, but is not working, even if I separate the classes into their own files:
Data passed in:
items: [{obj1: "A", obj2: ["c", "d"]}, {etc...}]
public class Response {
public class List<Custom> {
private List<Custom> items;
}
public class Custom {
private String obj1;
private List<Obj2> obj2;
}
public Class Obj2 {
private String letters;
}
}
I ended up just calling in the callback a list of the customObject and it did the job...
new Callback<List<ObjResponse>>() {
I originally had trouble getting an idea of how the OP solved his problem but, after days of debugging I have finally figured out how to solve this issue.
So you essentially have data in the format like so (JSON Array of JSON Objects):
[
{
...
}
]
Your class that models the data and contains the getter and setter methods are nothing more than your typical POJO.
public class Person implements Serializable {
#SerializedName("Exact format of your json field name goes here")
private String firstName;
// Getters and Setters....
}
In your interface that contains your RESTful annotations you want to convert your call from:
Before:
public interface APInterface {
#GET("SOME URL TO YOUR JSON ARRAY")
Call<Person>(...)
}
After:
public interface APInterface {
#GET("SOME URL TO YOUR JSON ARRAY")
Call<List<Person>>(...)
}
In your android activity you want to convert all calls in the form of Call<Person> to Call<List<Person>>
Finally when making the initial asynchronous request call, you will want to convert your callbacks like so.
call.enqueue(new Callback<List<Person>>() {
#Override
public void onResponse(Call<List<Person>> call, Response<List<Person>> response) {
if(response.isSuccessful()){
List<Person> person = response.body();
// Can iterate through list and grab Getters from POJO
for(Person p: person){...}
} else {
// Error response...
}
}
#Override
public void onFailure(Call<List<Person>> call, Throwable t) {...}
});
Hope this helps others whom are lost from the accepted answer above.
This can also work by just passing an array of response objects. So if this is your response object:
public class CustomUserResponse {
public String firstName;
public String lastName;
...
}
You can use related syntax, depending on how you use the callbacks. Such as:
new Callback<CustomUserResponse[]>(){
#Override
public void success(CustomUserResponse[] customUserResponses, Response rawResponse) {
}
#Override
public void failure(RetrofitError error) {
}
};
OR
public class GetUserCommand implements Callback<CustomUserResponse[]> { ...
Put simply, in every place where you normally replace T with a response class, replace it with an array, instead as in CustomUserResponse[].
NOTE: to avoid confusing errors, be sure to also use an array in the Retrofit interface definition:
#POST ( "/users" )
public void listUsers(#Body GetUsersRequest request, Callback<CustomUserResponse[]> callback);
You could try something like this
JSONObject jsonObject = new JSONObject(<your JSON string result>);
JSONArray jsonArray = jsonObject.getJSONArray();
//use GSON to parse
if (jsonArray != null) {
Gson gson = new Gson();
ObjResponse[] objResponse = gson.fromJson(jsonArray.toString(), ObjResponse[].class);
List<ObjResponse> objResponseList = Arrays.asList(objResponse);
}
This should definitely work.

Categories