I have a class Shop
public class Shop {
private String name;
private ArrayList<Fruitable> fruits;
public String toJson() {
Gson gson = new Gson();
return gson.toJson(this, Shop.class)
}
}
Apple and Orange are the two classes that implements Fruitable interface.
Fruitable:
public interface Fruitable {
String getColor();
}
Apple:
public class Apple implements Fruitable {
private int Id;
#Override
public String getColor() {
return "red";
}
}
Orange:
public class Orange implements Fruitable {
private int Id;
#Override
public String getColor() {
return "orange";
}
}
I can serialize Shop using GSON, however, I cannot deserialize it properly.
When I deserialize, the ArrayList<Fruitable> has objects, but the objects are null.
This is because GSON requires a concrete instance to deserialze into.
I know I can implement JsonDeserializer in Shop.
However, can I implement some deserializing mechanism, inside Apple and Orange so they will deserialize themselves?
If that is not possible, and I have to write a deserializer in Shop, can I write code just to deserialize the fruits property and not the other properties,i.e. GSON will deserialize the remaining properties assuming they are concrete instances?
Ok so you should use GSON annotations they will make your life a whole lot easier.
An example:
public class Deal{
#SerializedName("name")
String name;
#SerializedName("times")
List<DealTime> times;
}
The json coming back will roughly look this:
{
name:dave,
times[
{
end:400,
start:200
},
{
end:30,
start:500
}
]
}
Then your DealTime class will have the following:
public class DealTime{
#SerializedName("end")
int end;
#SerializedName("start")
int start;
}
All you have to do is cast the json coming back with GSON using the Deal object.
Hope this helps.
Related
I am trying to serialize an object. I have the following structure:
Class A{
String aField1;
String aField2;
B bObj;
}
Class B{
String bField1;
String bField2;
String bField3;
}
I am trying to serialze class A and B objects to send them to server.
When I am serializing Class A object, it gives me
{
aField1: "abc",
aField2: "def",
B: {
bField1: "mnp",
bField2: "qrt",
bField3: "xyz",
}
}
And serializing Class B obj:
{
bField1: "mnp",
bField2: "qrt",
bField3: "xyz",
}
But I want Class A object like this:
{
aField1: "abc",
aField2: "def",
B: {
bField1: "mnp"
}
}
I am currently using GSON library to accomplish this.
I want to remove extra key value pairs when interacting with server. How can I do this?
Change your Serialize class like this way.
public class A implements Serializable{
String aField1;
String aField2;
B bObj;
class B{
String bField1;
String bField2;
String bField3;
}
}
Just remove the extra fields. It will not make any problem.
You can mark bField2 and bField3 as transient or use the annotation #Expose(serialize = false).
Or you can customize your serialization exclusion strategy.
Sample code:
GsonBuilder builder = new GsonBuilder();
Type type = new TypeToken <A>(){}.getType();
builder.addSerializationExclusionStrategy(
new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return fieldAttributes.getName().equals("bField2") ||
fieldAttributes.getName().equals("bField3");
}
#Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
}
);
I have the following Object and i want to use RxJava in order to create a new object. The logic behind this is that each article has a lot of comments. And it finds the correct comments using the ArticleData.commentId and the Comment.id.
public class ArticlesResponse {
public List<ArticleData> articles;
public List<Data> comments;
}
public class Data {
public int id;
public String link;
public String title;
public String author
public String body;
}
public class ArticleData extends Data {
public List<int> commentId;
}
So how can i use Rxjava in order to create the following object
public class Article extends Data {
public List<Comments> comments;
}
public class Comments extends Data {
// comments will have more attributes in the feature
// so use a seperate object
}
I know that i have to use the flapMap and the filter in order to parse the "ArticleResponse" but i don't know how to put all this together.
Furthermore the "ArticleResponse" is being generated from a json which i got from Retrofit, so i guess it will be better to use RxJava since i already have the Observable instead of putting nested for's inside my Callback.
I assume you means that articlesResponse.comments is a list contains all Comments of these all ArticleData, although I don't think wrap these data together and do the map operation in client is a good idea, this job should be done at server.
And I think maybe your ArticlesResponse's comments field should be a List<Comments>.
With these assumption, the code below may do the job you want (I put them in a TempTest class, and define an interface you described, and mock it to pass javac compile, and I also use Java 8 lambda grammar for code simplicity).
public class TempTest {
public static class Data {
public int id;
public String link;
public String title;
public String author;
public String body;
}
public static class ArticleData extends Data {
public List<Integer> commentId;
}
public static class Comments extends Data {
// comments will have more attributes in the feature
// so use a seperate object
}
public static class ArticlesResponse {
public List<ArticleData> articles;
public List<Comments> comments;
}
public class Article extends Data {
public List<Comments> comments;
}
public interface TestInterface {
Observable<ArticlesResponse> getArticle();
}
public static Comments findCommentWithId(int commentId, List<Comments> comments) {
for (Comments comment : comments) {
if (comment.id == commentId) {
return comment;
}
}
return null;
}
#Test
public void simpleTestcase() {
// assume you means that articlesResponse.comments is a list contains all Comments of these
// all ArticleData, although I don't think wrap these data together and do the map operation
// in client is a good idea, this job should be done at server
TestInterface testInterface = mock(TestInterface.class);
testInterface.getArticle().map(articlesResponse -> {
List<Article> result = new ArrayList<>();
// for each ArticleData in articlesResponse.articles
for (ArticleData articleData : articlesResponse.articles) {
// get all Comments from articlesResponse.comments
Article article = new Article();
// ... copy Data field from articleData to article
article.comments = new ArrayList<>();
for (Integer id : articleData.commentId) {
Comments comment = findCommentWithId(id, articlesResponse.comments);
if (comment != null) {
article.comments.add(comment);
}
}
result.add(article);
}
return result;
}).subscribe(articles -> {
for (Article article : articles) {
System.out.println(article);
}
});
}
}
Kind of confused at what your actual question is, so hopefully this helps. Retrofit can return an Observable for you, which should make RxJava integration easy. For example, in your service you could make:
#GET(<your endpoint>)
Observable<ArticlesResponse> getArticles();
And call it like:
<yourService>.getArticles()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedules.mainThread())
.subscribe() // manipulate how you want
Imagine the following request:
#POST("/recipes/create")
void createRecipe(#Query("recipe") Recipe recipe, Callback<String> callback);
I would like to have toJson(recipe) but unfortunately my request is just calling toString() for my recipe which does not work at all.
I could override the toString inside of Recipe but I'd rather have a general solution.
I cannot use #Body as I need to specify, what I'm sending (i need to have "recipe=json(theRecipe)".
I also cannot change the serialization to add "recipe=" as I'm not in charge of the server.
At the moment I'm using a QueryMap Map where I put in a serialized object. Although this works, it's not a very nice solution in my opinion.
Can I somehow intercept the retrofit-adapter?
This is now possible when registering a custom Converter.Factory that overrides the stringConverter method, which is called when resolving parameters. The Github issue that #William referred to 2 years ago doesn't seem to be updated since support was added.
Method Javadoc:
Returns a Converter for converting type to a String, or null if type cannot be handled by this factory. This is used to create converters for types specified by #Field, #FieldMap values, #Header, #HeaderMap, #Path, #Query, and #QueryMap values.
The example below delegates to Gson, but in the same way any type of conversion can be applied to the parameters.
Example: GsonStringConverterFactory
class GsonStringConverterFactory
extends Converter.Factory {
private final transient Gson gson;
GsonStringConverterFactory(final Gson gson) {
this.gson = gson;
}
#Override
public Converter<?, String> stringConverter(final Type type, final Annotation[] annotations, final Retrofit retrofit) {
final TypeAdapter typeAdapter;
typeAdapter = gson.getAdapter(TypeToken.get(type));
return new StringConverter<>(typeAdapter);
}
private static class StringConverter<T>
implements Converter<T, String> {
private final TypeAdapter<T> typeAdapter;
private StringConverter(final TypeAdapter<T> typeAdapter) {
this.typeAdapter = typeAdapter;
}
#Override
public String convert(final T value)
throws IOException {
/* This works in our case because parameters in this REST api are always some kind of scalar
* and the toJson method converts these to simple json types. */
final String jsonValue;
jsonValue = typeAdapter.toJson(value));
if (jsonValue.startsWith("\"") && jsonValue.endsWith("\"") {
/* Strip enclosing quotes for json String types */
return jsonValue.substring(1, jsonValue.length() - 1);
} else {
return jsonValue;
}
}
}
}
Registering the converter:
To register the custom converter, your Retrofit builder could look something like this:
new Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addConverterFactory(new GsonStringConverterFactory(gson))
.build();
I don't think it supports this right now in some nice way. Check this answer by one of the authors: https://github.com/square/retrofit/issues/291
The suggested method from that answer is to create a custom type that overrides the toString() method, because Retrofit internally uses String.valueOf(value) to convert query or path parameters to strings.
So, you could have something like this:
class Recipe {
public int id;
public String title;
#Override
public String toString() {
// You can use a GSON serialization here if you want
// This is not a robust implementation
return Integer.toString(id) + "-" + title;
}
}
As #Rolf mentioned, there is a way to set customConverter.Factory
Example:
public class QueryConverterFactory extends Converter.Factory {
public static QueryConverterFactory create() {
return new QueryConverterFactory();
}
private QueryConverterFactory() {
}
#Nullable
#Override
public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if (type == Date.class) {
return DateQueryConverter.INSTANCE;
}
return null;
}
private static final class DateQueryConverter implements Converter<Date, String> {
static final DateQueryConverter INSTANCE = new DateQueryConverter();
private static final ThreadLocal<DateFormat> DF = new ThreadLocal<DateFormat>() {
#Override
public DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
#Override
public String convert(Date date) {
return DF.get().format(date);
}
}
}
You can add converters for your own types.
You can use retrofit.RestAdapter.Builder().setConverter(...) to pass a custom json converter.
I started using Retrofit recently. I don't know much about it. I have googled this issue and no answers suite my problem.
This is JSON response
{
"results": [
{
"description_eng": "This is second time testing",
"img_url": "-",
"title_eng": "Second test"
},
{
"description_eng": "Hello 1 2 3, I am testing.",
"img_url": "https://fbcdn-sphotos-f-a.akamaihd.net/hphotos-ak-xpa1/t31.0-8/s720x720/10838273_816509855058935_6428556113200361121_o.jpg",
"title_eng": "Test"
}
]
}
This is Feed Class
public class Feed {
public List<Results> results;
class Results{
String description_eng,img_url,title_eng;
}
}
This is the interface
public interface GetApi {
#GET("/api.json")
public void getData(Callback<List<Feed>> response);
}
I got json_illegal_syntax Exception.
This is how I solved this problem, by creating empty constructors.
Feed.class
public class Feed{
private List<Result> results;
public Feed(){}
public List<Result> getFeed(){
return this.results;
}
public void setFeed(List<Result> results) {
this.results = results;
}
}
Result.class
public class Result{
private String description_eng;
private String img_url;
private String title_eng;
public Result(){}
//getters and setters
}
GetApi.class
public interface GetApi {
#GET("/api.json")
public void getData(Callback<Feed> response);
}
Retrofit uses Gson by default to convert HTTP bodies to and from JSON. If you want to specify behavior that is different from Gson's defaults (e.g. naming policies, date formats, custom types), provide a new Gson instance with your desired behavior when building a RestAdapter.
Gson can not automatically deserialize the pure inner classes since their no-args constructor also need a reference to the containing Object which is not available at the time of deserialization. You can address this problem by either making the inner class static or by providing a custom InstanceCreator for it. Here is an example:
public class A {
public String a;
class B {
public String b;
public B() {
// No args constructor for B
}
}
}
NOTE: The above class B can not (by default) be serialized with Gson.
You should read more about GSON library
#Ye Min Htut Actually, even better is to write Feed class with generics.
public class FeedList<T>{
private List<T> feeds;
public Feed() {
}
public List<T> getFeed(){
return this.feeds;
}
public void setFeed(List<T> results) {
this.feeds = feeds;
}
}
and if there is something similar with only one object, than you can remove List part and get/set single object.
Now you can call FeedList<Result> or with whatever object you want.
GetApi.class
public interface GetApi {
#GET("/api.json")
public void getData(Callback<FeedList<Result>> response);
}
#Ye Min Htut I will suggest you to make Model/POJO classes using ROBOPOJO Generator It will generate All model classes for you, you don't need to make it by your self, Will also help you in future while creating model class. It just need JSON string and click will make your job done
I'm working on an android application using robospice, spring and jackson. Everything works fine except that cache results don't have relations of POJO classes.
For instance;
public class User extends SampleBase {
public int id;
public String name;
public Address address;
}
public class Address extends SampleBase {
public int id;
public String street;
public String city;
}
public class SampleBase {
// Base class of all POJO classes
}
When I send a request to get a user, I get all the values properly. However, when I try to get a user from cache, the address field returns null. If there's a relation between two classes, those relation fields are null but other fields are OK.
The result of request:
User:
id: 1
name: "Test User"
address: Address Object
The result of cache:
User:
id: 1
name: "Test User"
address: null
In my design, all of the POJO classes extend SampleBase. And there is only one RequestListener:
public class SampleRequestListener implements RequestListener<SampleBase> {
#Override
public void onRequestSuccess(SampleBase result) {
// Some operations
}
#Override
public void onRequestFailure(SpiceException e) {
// Some operations
}
}
I don't know if it's about my request listener but this works fine while sending requests. Is there something else that I should do to get the related objects of a cached object?
It is strange, we often do this and it works fine. Try to find some solution on jackson forum, RS is just a wrapper here.