I have the following class hierarchy
public abstract class SyncModel {
@Expose
@SerializedName("id")
private Long globalId;
@Expose
protected DateTime lastModified;
/* Constructor, methods... */
}
public class Event extends SyncModel {
@Expose
private String title;
/* Other fields, constructor, methods... */
}
I need to send an Event instance to backend.
@Body
When I post the Event instance in a request body, it is serialized fine.
RetroFit Java interface:
public interface EventAPI {
@POST("/event/create")
void sendEvent(@Body Event event, Callback<Long> cbEventId);
}
RetroFit log:
D Retrofit ---> HTTP POST http://hostname:8080/event/create
D Retrofit Content-Type: application/json; charset=UTF-8
D Retrofit Content-Length: 297
D Retrofit {"title":"Test Event 01",...,"id":null,"lastModified":"2015-07-09T14:17:08.860+03:00"}
D Retrofit ---> END HTTP (297-byte body)
@Field
But when I post the Event instance in a request parameter, only abstract class is serialized.
RetroFit Java interface:
@FormUrlEncoded
@POST("/event/create")
void sendEvent(@Field("event") Event event, Callback<Long> cbEventId);
RetroFit log:
D Retrofit ---> HTTP POST http://hostname:8080/event/create D Retrofit Content-Type: application/x-www-form-urlencoded; charset=UTF-8 D Retrofit Content-Length: 101 D Retrofit event=SyncModel%28globalId%3Dnull%2C+lastModified%3D2015-07-09T13%3A36%3A33.510%2B03%3A00%29 D Retrofit ---> END HTTP (101-byte body)
Notice the difference.
Why?
How can I send a serialized Event instance to backend in a request parameter?
Do I need to write a custom JSON serializer for abstract class? (example: Polymorphism with JSON)
Or is it a RetroFit specific feature (to ignore child classes)?
I've also noticed that in the 2nd case globalId field serialized name is globalId, but it should be id! It makes me think that RetroFit uses a different GsonConverter for @Field than for @Body parameters...
Gradle dependencies
compile 'com.squareup.retrofit:retrofit:1.9.+'
compile 'com.squareup.okhttp:okhttp:2.3.+'
compile 'net.danlew:android.joda:2.8.+'
compile ('com.fatboyindustrial.gson-jodatime-serialisers:gson-jodatime-serialisers:1.1.0') { // GSON + Joda DateTime
exclude group: 'joda-time', module: 'joda-time'
}
REST client
public final class RESTClient {
// Not a real server URL
public static final String SERVER_URL = "http://hostname:8080";
// one-time initialization
private static GsonBuilder builder = new GsonBuilder()
.serializeNulls()
.excludeFieldsWithoutExposeAnnotation()
.setDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z'");
// Joda DateTime type support
private static Gson gson = Converters.registerDateTime(builder).create();
private static RestAdapter restAdapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL) // for development
.setEndpoint(SERVER_URL)
.setConverter(new GsonConverter(gson)) // custom converter
.build();
private static final EventAPI eventService = restAdapter.create(EventAPI.class);
/* + Getter for eventService */
static {
// forget them
restAdapter = null;
gson = null;
builder = null;
}
}
Call
RESTClient.getEventService().sendEvent(event, new Callback<Long>() {/* ... */});
Take a look at @Field's documentation. It says:
Values are converted to strings using
String#valueOf(Object)and then form URL encoded.
String#valueOf(Object) makes a call to Object#toString() inside. I suppose your SyncModel has a toString() method and Event does not. When Retrofit calls String.valueOf(event), SyncModel#toString() is called instead of Event#toString(). That's why you don't see title in the Retrofit logs.
Gson doesn't play any role at all when converting @Field parameters. It can be though - you can make your toString() method look something like this:
@Override
public String toString() {
return GsonProvider.getInstance().toJson(this);
}
Put this inside your abstract SyncModel class and it should work for Event as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With