Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mixed type/ type wildcard/ polymorphism #78

Closed
ericntd opened this issue Oct 5, 2016 · 9 comments
Closed

Mixed type/ type wildcard/ polymorphism #78

ericntd opened this issue Oct 5, 2016 · 9 comments

Comments

@ericntd
Copy link

ericntd commented Oct 5, 2016

My server search API returns a list of object of various types e.g. "products", "brands" etc.

I have Product and Brand model both extending BaseModel:

public class BaseModel {
    @Id
    protected String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

I've tried the following:

public interface SearchService {
    SearchService serviceJasminb = MyRESTClient.retrofitJasminb.create(SearchService.class);

    @GET("search")
    Call<JSONAPIDocument<ArrayList<? extends BaseModel>>> search(@Query("query") String query);

but I got the following exception:

java.lang.IllegalArgumentException: Method return type must not include a type variable or wildcard: retrofit2.Call<com.github.jasminb.jsonapi.JSONAPIDocument<java.util.ArrayList<? extends com.xxx.yyy.models.BaseModel>>>
                                                     for method SearchService.retrieveSearch
                                                     at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:720)
                                                     at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:711)
                                                     at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:224)
                                                     at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:160)
                                                     at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:166)
                                                     at retrofit2.Retrofit$1.invoke(Retrofit.java:145)
                                                     at java.lang.reflect.Proxy.invoke(Proxy.java:393)
                                                     at $Proxy16.retrieveSearch(Unknown Source)
                                                     at com.xxx.yyy.caches.SearchCache.fetchSearch(SearchCache.java:187)
                                                     at com.xxx.yyy.views.activities.SearchActivity.run(SearchActivity.java:352)
                                                     at android.os.Handler.handleCallback(Handler.java:739)
                                                     at android.os.Handler.dispatchMessage(Handler.java:95)
                                                     at android.os.Looper.loop(Looper.java:148)
                                                     at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                     at java.lang.reflect.Method.invoke(Native Method)
                                                     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
@jasminb
Copy link
Owner

jasminb commented Oct 6, 2016

Hey @ericn37, error you got is related to retrofit. Why not use BaseType as type instead of extends?

You will get a collection of different objects depending on the actual type attribute in the parsed response.

@emetsger
Copy link
Contributor

emetsger commented Oct 6, 2016

I don't have much to add to this conversation, except to add a "me too".
I'm working with a JSON API that has relationships that can point to
objects of different types.

For example, an object may have a relationship named somerel with a
related link to http://example.org/resource/1, and the JSON
representation at http://example.org/resource/1 may have a type of Baz.

A different object may express a relationship named somerel with a
related link to http://example.org/resource/2, and the JSON
representation http://example.org/resource/2 may have a type of Biz.

The semantics of the relationship named somerel hold for these two
example objects, even though dereferencing the link returns objects of
different types.

This becomes problematic when you are dereferencing somerel: you may
retrieve objects typed as Biz or objects typed as Baz:

@Type("foo")
public class Foo {
  @Relationship(value = "somerel", resolve = true)
  private Biz field;

  // ....
}

This class can't be used to deserialize somerel where the type is Baz;
it only works for type Biz.

One option is to have Baz and Biz share a super-type, and update the
java class as follows:
Baz -- extends --> BaseObject
Biz -- extends --> BaseObject

@Type("foo")
public class Foo {
  @Relationship(value = "somerel", resolve = true)
  private BaseObject field;

  // ....
}

However, that does not work either. You must deserialize a concrete type
(either Baz or Biz) which fails, before assigning to a base class.

I am not sure if this is a Retrofit problem or a jsonapi-converter problem,
or what. But currently the semantics of a relationship are coupled to the
type of object returned by that relationship, which is unfortunate.

On Thu, Oct 6, 2016 at 11:51 AM, Jasmin Begic notifications@github.com
wrote:

Hey @ericn37 https://github.com/ericn37, error you got is related to
retrofit. Why not use BaseType only?


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#78 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAI-Grlnc5zANcn_1V2FrhIPfAFQx9w2ks5qxRj0gaJpZM4KOYpL
.

@Servus7
Copy link

Servus7 commented Oct 14, 2016

@emetsger It's an jsonapi-converter problem. Other libraries like moshi-jsonapi are able to convert different types.

@ericntd
Copy link
Author

ericntd commented Oct 15, 2016

https://github.com/faogustavo/JSONApi is also able to parse multiple types.
It used to be my favourite.
Their drawback is that they are are not be to handle circular reference properly 😭

However, is it a tradeoff?
Multiple types/ polymorphism ==> no circular reference
Circular reference (this libary) ==> no multiple types/ polymorphism

@ericntd
Copy link
Author

ericntd commented Oct 17, 2016

@jasminb I've tried what you suggested, however, I only got BaseModel object, not the subclass.
When I try to cast, I got ClassCastException

Process: com.xxx.yyy, PID: 10629
                                                 java.lang.ClassCastException: com.xxx.yyy.models.BaseModel cannot be cast to com.xxx.yyy.models.LandingPagePromoList
                                                     at com.xxx.yyy.tasks.LandingPageTask$1.onResponse(LandingPageTask.java:35)
                                                     at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.run(ExecutorCallAdapterFactory.java:68)
                                                     at android.os.Handler.handleCallback(Handler.java:739)
                                                     at android.os.Handler.dispatchMessage(Handler.java:95)
                                                     at android.os.Looper.loop(Looper.java:148)
                                                     at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                     at java.lang.reflect.Method.invoke(Native Method)
                                                     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

My Retrofit code:

Call<JSONAPIDocument<BaseModel>> obj;
        obj = MyService.service.fetchLandingPage(objectId);
        obj.enqueue(new Callback<JSONAPIDocument<BaseModel>>() {
            @Override
            public void onResponse(Call<JSONAPIDocument<BaseModel>> call,
                                   Response<JSONAPIDocument<BaseModel>> response) {
                if (response.isSuccessful()) {
                    MyModel tmp = (MyModel) response.body().get();
                    listener.handleLandingPage(tmp);
                } else {
                    listener.handleError(response.errorBody().toString());
                }
            }

            @Override
            public void onFailure(Call<JSONAPIDocument<BaseModel>> call, Throwable t) {
                Log.e(TAG, "onFailure", t);
                listener.handleError(null);
            }
        });

BaseModel:

@JsonIgnoreProperties(ignoreUnknown = true)
@Type("base")
public class BaseModel {
    @Id
    protected String id;

    public BaseModel() {
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

MyModel:

@JsonIgnoreProperties(ignoreUnknown = true)
@Type("mymodel")
public class MyModel extends BaseModel implements Parcelable {
    @Relationship("objectbs")
    private ArrayList<ObjectB> promos;

    @JsonProperty("slug-url")
    private String slug;

    public MyModel() {
    }

    public ArrayList<ObjectB> getPromos() {
        return promos;
    }

    public void setPromos(ArrayList<ObjectB> promos) {
        this.promos = promos;
    }

    public String getSlug() {
        return slug;
    }

    public void setSlug(String slug) {
        this.slug = slug;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeTypedList(this.promos);
        dest.writeString(this.slug);
    }

    protected MyModel(Parcel in) {
        this.promos = in.createTypedArrayList(ObjectB.CREATOR);
        this.slug = in.readString();
    }

    public static final Creator<MyModel> CREATOR = new Creator<MyModel>() {
        @Override
        public MyModel createFromParcel(Parcel source) {
            return new MyModel(source);
        }

        @Override
        public MyModel[] newArray(int size) {
            return new MyModel[size];
        }
    };
}

@jasminb
Copy link
Owner

jasminb commented Oct 17, 2016

Hey,

What version of the lib you are using. Support for providing base type is SNAPSHOT only and not yet part of any release. Until new release is out, please use latest snapshot (example in the README).

@ericntd
Copy link
Author

ericntd commented Oct 17, 2016

Hi @jasminb , I'm using 0.4. Will try the snapshot. Thanks :)

@jasminb
Copy link
Owner

jasminb commented Oct 24, 2016

Hey @ericn37, did 0.5 solve the issue for you?

@ericntd
Copy link
Author

ericntd commented Oct 24, 2016

Yes, indeed 0.5 did solve the problem, thanks @jasminb

@ericntd ericntd closed this as completed Oct 24, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants