stag-java icon indicating copy to clipboard operation
stag-java copied to clipboard

Type being known at runtime.

Open anirudhramanan opened this issue 9 years ago • 11 comments

Problem Statement : While parsing the model object, if any class which has subclasses is being used as a field, and its type is known at runtime, stag will not be able to get the corresponding type adapter, and hence it will not able to serialize the fields of the subtypes/subclasses.

Solution : Since the type is known at runtime, we have to fallback to gson to parse that particular field.

Proposed Fix : We can have another annotation named @WriteRuntimeType, and the if the classes which has subclasses or subtypes is being used as a field in another model should be annotated with this. This will enable stag to dynamically fallback to gson for parsing such fields.

@anthonycr Will this work ?

anirudhramanan avatar Dec 06 '16 17:12 anirudhramanan

We came across this while one of our model class that had few subclasses was being used as a field in another class. While debugging we found out that although the type of field was one of the subclasses, the adapter that was being used to parse the same was of the super class type, hence not able to serialize the fields of subclasses

anirudhramanan avatar Dec 06 '16 18:12 anirudhramanan

I'm not sure I completely understand the situation.

Is this the sort of structure you're referring to?

class ClassA<T> {
    T unknownField;
}

class ClassB extends ClassA<ClassA<String>> {
    // some more fields
}

We have a similar use case, which isn't experiencing any problems (see https://github.com/vimeo/vimeo-networking-java/blob/v2.0.0/vimeo-networking/src/main/java/com/vimeo/networking/model/Category.java). But our example uses a Category class which itself contains a field of type ArrayList<Category>, rather than inheriting from a parameterized class and using that class as the type.

anthonycr avatar Dec 06 '16 18:12 anthonycr

Lets assume we have subclasses of class User, say UserWithAddress, and UserWithoutAddress

public class User {
   public String name;
}

public class UserWithAddress extends User {
   public String address;
}

public class UserWithoutAddress extends User {
   public String location;
}

Now, in Video class, we have User class as a field

public class Video {
   public User user;
}

Suppose the type of User field is known at runtime ie, it may be UserWithAddress or UserWithoutAddress or can be User simply. If it is of type User, everything will be work fine. But if its one of UserWithAddress or UserWithoutAddress, since stag does not know the actual type, and assumes it to be of type User, it will use the typeadapter of User to serialize it which will result in fields of the actual type not getting serialized.

Let me know if this helps

anirudhramanan avatar Dec 06 '16 21:12 anirudhramanan

Okay that's a much simpler scenario than I thought, thanks for the clarification.

This is definitely an important use case, i'll think on it.

anthonycr avatar Dec 06 '16 21:12 anthonycr

Sure. We fixed this using the annotation, looking forward for an alternative solution :)

Thank you

anirudhramanan avatar Dec 06 '16 21:12 anirudhramanan

@anthonycr Any updates on this ? Would be great if this gets resolved in the upcoming release.

anirudhramanan avatar Dec 16 '16 08:12 anirudhramanan

Okay, so if we introduce a WriteRuntimeType annotation, then the generated writing code could look like the following:

class Parent {
    String name;
}

class Child extends Parent {
    String name1;
}

class Other {
    Parent parent;
}

// generated
class Other$TypeAdapter {
    void write() {
        // other stuff

        // if (parent field annotated with RuntimeType)
	        mGson.getAdapter(object.mParentType.getClass()).write();
        // else
                mStagFactory.getParentType$TypeAdapter(mGson).write();
    }
}

This doesn't solve the whole problem, because the type isn't known when parsing the json, so then when you parse the correct json it will just lose the field because it will try to parse it as a parent type. Of course, if you don't need to deserialize it once it's been serialized then it will work fine.

anthonycr avatar Dec 19 '16 16:12 anthonycr

Yes. You're right. I tried the same thing with Gson as well, it does not deserialize it with the correct type.

anirudhramanan avatar Dec 19 '16 16:12 anirudhramanan

Do you think it's worth implementing the serialization half of this anyway? That would at least mirror gson's behavior, but at the same time I feel like it's not worth adding in new stuff for a half solve

anthonycr avatar Dec 19 '16 19:12 anthonycr

Ideal case would be that people use

class Other<T extends Parent> { T parent; }

This is a better way to represent it anyways where there are no run time checks for the type.

I guess a more generic requirement that might exists (for certain other corner/unknown/unhandled/stag-being-buggy scenario) is to allow certain fields of a class to be defaulted to Gson parsing.

Since this would be an exception we can call this annotation as @UseGson which is a field level annotation only.

Thoughts?

yasirmhd avatar Jan 06 '17 16:01 yasirmhd

I guess this is in a way the same as putting a wrapper adapter which is achieved in gson by TypeAdapterRuntimeTypeWrapper.java

If you look at the implementation of this function it does not do any specific logic in the read part. In the write part it does look at the type of the object and then makes an appropriate decision.

@anthonycr , do you think we should add a field level support for something like this?

The implementation in #56 is not entirely going to solve this case.

yasirmhd avatar Feb 07 '17 19:02 yasirmhd