Giter VIP home page Giter VIP logo

stag-java's Introduction

⚠️⚠️⚠️ This library has been deprecated and will be removed in the future. ⚠️⚠️⚠️

Stag

Stag improves Gson performance by automatically generating reflection-less TypeAdapters for your model objects.

Build Status codecov Download

Why Build Stag?

Gson is the essential JSON parsing library. It greatly simplifies what can be the verbose and boilerplate-ridden process of parsing JSON into model objects. It does this by leveraging reflection. Unfortunately, using reflection can be slow (particularly on the Android OS).

You can work around this by writing a custom TypeAdapter, a class that Gson uses to (de)serialize an object. The main use case for custom type adapters is for classes that you don't have control over (e.g. parsing a JSON string into a java.util.Date object). They are used to manually map JSON to fields in your model object. So, you can just write a custom TypeAdapter to tell Gson how to map data to fields and the performance will improve, since it won't have to use reflection.

But... if you have a lot of model objects, and you want to remove the use of reflection for (de)serialization of each one, suddenly you have to write many, many TypeAdapters. If you've ever written one or many of these type adapters, you will know that it is a tedious process. In fact, when writing your own TypeAdapter, you might ask what you are doing using Gson in the first place!!!

The Stag library solves this problem. It leverages annotations to automatically generate reflection-less TypeAdapters for your model objects at compile time. Instead of spending time writing your own custom TypeAdapters for each model object, or forgoing the performance gain of eliminating reflection, use Stag and apply the @UseStag to your model class declarations and all the work will be done for you.

Gradle Usages

1. Add the Stag dependencies

All jar dependencies are available on jcenter.

Java Gradle

buildscript {
    repositories {
        maven { url 'https://plugins.gradle.org/m2/' }
    }
    dependencies {
        classpath 'net.ltgt.gradle:gradle-apt-plugin:0.11'
    }
}

apply plugin: 'net.ltgt.apt'

dependencies {
    def stagVersion = '2.6.0'
    compile "com.vimeo.stag:stag-library:$stagVersion"
    apt "com.vimeo.stag:stag-library-compiler:$stagVersion"
}

// Optional annotation processor arguments (see below)
gradle.projectsEvaluated {
    tasks.withType(JavaCompile) {
        aptOptions.processorArgs = [
                "stagAssumeHungarianNotation": "true",
                "stagGeneratedPackageName"   : "com.vimeo.sample.stag.generated",
                "stagDebug "                 : "true",
                "stag.serializeNulls"        : "true",
        ]
    }
}

Kotlin Gradle

apply plugin: 'kotlin-kapt'

dependencies {
    def stagVersion = '2.6.0'
    compile "com.vimeo.stag:stag-library:$stagVersion"
    kapt "com.vimeo.stag:stag-library-compiler:$stagVersion"
}

kapt {
    correctErrorTypes = true
    // Optional annotation processor arguments (see below)
    arguments {
        arg("stagDebug", "true")
        arg("stagGeneratedPackageName", "com.vimeo.sample.stag.generated")
        arg("stagAssumeHungarianNotation", "true")
        arg("stag.serializeNulls", "true")
    }
}

Android Gradle (Java)

dependencies {
    def stagVersion = '2.6.0'
    compile "com.vimeo.stag:stag-library:$stagVersion"
    annotationProcessor "com.vimeo.stag:stag-library-compiler:$stagVersion"
}

android {
    ...
    defaultConfig {
        ...
        // Optional annotation processor arguments (see below)
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [
                    "stagAssumeHungarianNotation": 'true',
                    "stagGeneratedPackageName"   : 'com.vimeo.sample.stag.generated',
                    "stagDebug"                  : 'true',
                    "stag.serializeNulls"        : 'true'
                ]
            }
        }
    }
}

2. Provide optional compiler arguments to Stag

  • stagGeneratedPackageName: Pass package name as an argument for the generated files. By default, the files will be in generated in com.vimeo.stag.generated package. You can specify your own package for the generated files by passing it as an argument to the annotation processor.
  • stagDebug: Turn on debugging in Stag. This will cause Stag to spit out a lot of output into the gradle console. This can aid you in figuring out what class is giving you trouble, if the exception gradle prints out isn't sufficient. Default is false.
  • stagAssumeHungarianNotation: If your Java member variables are private and Stag needs to use setters and getters to access the field, Stag will look for members named set[variable_name] and get[variable_name]. If your member variables are named using Hungarian notation, then you will need to pass true to this parameter so that for a field named mField, Stag will look for setField and getField instead of setMField and getMField. Default is false.
  • stag.serializeNulls: By default this is set to false. If an object has a null field and you send it to be serialized by Gson, it is optional whether or not that field is serialized into the JSON. If this field is set to false null fields will not be serialized, and if set to true, they will be serialized. Prior to stag version 2.6.0, null fields were always serialized to JSON. This should not affect most models. However, if you have a model that has a nullable field that also has a non null default value, then it might be a good idea to turn this option on.

Features

1. Class Level Annotation

Stag supports class level annotation @UseStag which processes all the fields for a particular class, which makes it easy to use and integrate.

@UseStag has three different variants:

  • @UseStag(FieldOption.ALL): Will serialize/de-serialize all member variables which are not static or transient
  • @UseStag(FieldOption.NONE): Will skip serialization and deserialization for all member variables. Only member variables inherited from annotated classes will be included.
  • @UseStag(FieldOption.SERIALIZED_NAME): Will Serialize or Deserialize Fields only which are annotated with SerializedName.

2. @SerializedName("key") Support

Similar to GSON, you can use the@SerializedName annotation to provide a different JSON name to a member field. It also supports alternate name feature of the @SerializedName annotation. @SerializedName("name") or @SerializedName(value = "name", alternate = {"name1", "name2"}).

3. Cross Module Support

Stag has the ability to reference TypeAdapters across modules.

4. Parity with GSON

Last but not the least, Stag is almost in parity with GSON.

Stag Rules

  1. Make sure that any private member variables have setters/getters following these naming rules:
    private String myString;
    
    public String getMyString() { ... }
    
    public void setMyString(String parameter) { ... }
    Java setters and getters must have protected, public, or package local visibility. If you don't want to use setters and getters, make sure your member variables have protected, public, or package local visibility. If working with Kotlin, currently, you must make sure your getters all have public visibility. Because stag generates Java code, the only way it knows how to access the Kotlin fields is if the setters and getters are public. By default, the visibility set on a Kotlin member variable is also applied to its setters and getters.
  2. Make sure your model class is not private and has a zero argument non-private constructor
  3. Annotate the classes with @UseStag annotation. This will process all the member variables of the class, which makes it easy to use.
  4. Use the @SerializedName("key") annotation to give the variables a different JSON name. (same as GSON)
  5. Use your favorite @NonNull annotation to tell Stag to throw an exception if the field is null while deserializing or while serializing the object.
  6. Register the Stag.Factory with Gson when you create your Gson instance: Gson gson = new GsonBuilder().registerTypeAdapterFactory(new Stag.Factory()).create();
  7. Make sure that you are not reusing the Stag.Factory instance between Gson instances. The factory is stateful and must be recreated when creating a new Gson instance. If you try to reuse the instance, an UnsupportedOperationException will be thrown.
  8. You're done!
  9. [Optional] By default, stag will drop a file called StagTypeAdapterFactory.list into your build folder which contains the plaintext names of all your models. It is used by the compiler to generate the adapters. It's a very small file and will compress down to a few bytes in size, but if you don't want it in your compiled apk, you can exclude it using the following code (if you supply a custom package name as a compiler argument, use that in place of com/vimeo/stag/generated/ below):
packagingOptions {
    exclude 'com/vimeo/stag/generated/StagTypeAdapterFactory.list'
}

See the example below or the sample app to get more info on how to use Stag.

Example

Java

@UseStag
public class Deer {

    // Private fields require getters and setters
    @SerializedName("name")
    private String name;    // name = json value with key "name"
    
    @SerializedName("species")
    String species; // species = json value with key "species"
    
    @SerializedName("age")
    int age;        // age = json value with key "age"
    
    @SerializedName("points")
    int points;     // points = json value with key "points"
    
    @SerializedName("weight")
    float weight;   // weight = json value with key "weight"
    
    public String getName() { return name; }
    
    public void setName(String name) { this.name = name; }
}

@UseStag
public class Herd {

    @NonNull                     // add NonNull annotation to throw an exception if the field is null
    @SerializedName("data_list")
    ArrayList<Deer> data;        // data = json value with key "data_list"
    
    List<Deer> data_list_copy;   // data_list_copy = json value with key "data_list_copy"
    
    Map<String, Deer> data_map;  // data_map = json value with key "data_map"
}

Kotlin

@UseStag
class Deer {

    @SerializedName("name")
    var name: String? = null    // name = json value with key "name"

    @SerializedName("species")
    var species: String? = null // species = json value with key "species"

    @SerializedName("age")
    var age: Int = 0        // age = json value with key "age"

    @SerializedName("points")
    var points: Int = 0     // points = json value with key "points"

    @SerializedName("weight")
    var weight: Float = 0.toFloat()   // weight = json value with key "weight"
}

@UseStag
class Herd {

    // non null fields will be honored buy throwing an exception if the field is null
    @SerializedName("data_list")
    var data: ArrayList<Deer> = ArrayList()     // data = json value with key "data_list"

    var data_list_copy: List<Deer>? = null   // data_list_copy = json value with key "data_list_copy"

    var data_map: Map<String, Deer>? = null  // data_map = json value with key "data_map"
}

Consuming Model in Java

/**
 * The class where you receive JSON 
 * containing a list of Deer objects.
 * You parse the list from JSON using
 * Gson.
 */
class MyParsingClass {
    private Gson gson = new GsonBuilder()
                                 .registerTypeAdapterFactory(new Stag.Factory())
                                 .create();

    public Herd fromJson(String json) {
        return gson.fromJson(json, Herd.class);
    }
}

Future Enhancements

  • Add an option to absorb parsing errors rather than crashing and halting parsing (default gson behavior)
  • Support internal visibility in Kotlin code
  • Generate Kotlin code for Kotlin models

Development

git clone [email protected]:vimeo/stag-java.git
cd stag-java
bundle install
# dev like a boss
bundle exec fastlane test
# commit and push like a boss

Manage build dependencies

Aside from specifying Java dependencies in the .gradle files, you can use the .travis.yml file to specify external build depencies such as the Android SDK to compile against (see the android.components section).

License

stag-java is available under the MIT license. See the LICENSE file for more information.

Questions

Post on Stack Overflow with the tag vimeo-android.

stag-java's People

Contributors

alfiehanssen avatar anirudhramanan avatar anthonycr avatar brentwatson avatar kevinzetterstrom avatar mathiasgr avatar mcumings avatar mikew-personal avatar penkzhou avatar sof4real avatar wilrnh avatar yasirmhd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stag-java's Issues

Fields which use raw types emit compilation warnings

Issue Summary

Model classes which refer to generically typed objects cause TypeAdapter references to be generated which cause "rawtypes" warnings to be generated within the generated TypeAdapter.

We have currently have suppressions for "rawtypes" and "unchecked" on the generated TypeAdapter's constructor but the failing point is in the generated TypeAdapter's mTypeAdapterN field references.

Reproduction Steps

Will include a test case in the PR.

Expected Behavior

We suppress the issues for field definitions as well.

Actual Behavior

The generated code causes javac lint "rawtypes" warnings.

version 2.6.0 creates a conflict with dagger

Issue Summary

the moment i update from 2.5.0 to 2.6.0 dagger stops working, i get an error on kapt

Reproduction Steps

create a project that contains kotlin classes that use dagger and try to add classes that use stag.

Expected Behavior

should compile with both libraries as in version 2.5.0

Actual Behavior

breaks with error: "[ComponentProcessor:MiscError] dagger.internal.codegen.ComponentProcessor was unable to process this class because not all of its dependencies could be resolved. Check for compilation errors or a circular dependency with generated code."

Generated TypeAdapter writes out an empty object for null values

Issue Summary

When the generated TypeAdapter is instructed to write a null value it always writes an empty object. This is due to the following code at the beginning of the write(JsonWriter, T) method:

    writer.beginObject();
    if (object == null) {
      writer.endObject();
      return;
    }

Reproduction Steps

Create a type adapter and use it to write a null value.

Expected Behavior

The generated TypeAdapter should wither write out a JSON null in this scenario or should be configurable to allow either behavior at the discretion of the user.

Actual Behavior

A JSON Object with no fields is written.

HashMap support

Issue Summary

Support HashMap

Reproduction Steps

Use @GsonAdapterKey on a HashMap field.

Expected Behavior

When generating parser for hashmap fields, use TypeToken from GSON instead of HashMap<String, String>.class.

Actual Behavior

stag generate a compilation error in ParseUtils :

if (object.urls!= null) { 
            writer.name("urls");
            Stag.writeToAdapter(gson, java.util.HashMap<java.lang.String,java.lang.String>.class, writer, object.urls);
}
ParseUtils.java:73: error: illegal start of expression Stag.writeToAdapter(gson, java.util.HashMap<java.lang.String,java.lang.String>.class, writer, object.urls);

....
try {
        object.urls= Stag.readFromAdapter(gson, java.util.HashMap<java.lang.String,java.lang.String>.class, reader);
} catch(Exception e) {
            throw new IOException("Error parsing Operation.urls JSON!");
}
ParseUtils.java:198: error: illegal start of expression object.urls= Stag.readFromAdapter(gson, java.util.HashMap<java.lang.String,java.lang.String>.class, reader);

Custom TypeAdapterFactory not called when using Stag.Factory

Hi,
I have a custom PostParseTypeAdapterFactory , which just calls onParseCompleted method on my objects after parsing is finished, to allow for some post processing of values, etc.

public class PostParseTypeAdapterFactory implements TypeAdapterFactory {
    public interface PostProcessable {
        void onParseCompleted();
    }

    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        return new TypeAdapter<T>() {
            public void write(JsonWriter out, T value) throws IOException {
                delegate.write(out, value);
            }

            public T read(JsonReader in) throws IOException {
                T obj = delegate.read(in);
                if (obj instanceof PostProcessable) {
                    ((PostProcessable) obj).onParseCompleted();
                }
                return obj;
            }
        };
    }
}

When not using Stag factory, it works as expected, however with it, the onParseCompleted doesnt get called

Convert java file to Kotlin data class not generate Stag.Factory

Issue Summary

I have java pojo class after i convert it to kotlin data class the build fail and Stag factory not generated and it's wired because i have other data classes and it's work fine only this file that cause this bug

Reproduction Steps

Here is the java file

@UseStag
public class Article {
    @SerializedName("id")
    private Long id;

    @SerializedName("cell_style")
    private Integer cellStyle;

    @SerializedName("type")
    private String type;

    @SerializedName("ref_id")
    private String refId;

    @SerializedName("url")
    private String url;

    @SerializedName("author")
    private String author;

    @SerializedName("thumbnail")
    private String thumbnail;

    @SerializedName("title")
    private String title;

    @SerializedName("content")
    private List<Content> content = null;

    @SerializedName("comments_count")
    private Integer commentsCount;

    @SerializedName("published_at")
    private String publishedAt;

    @SerializedName("is_ltr")
    private Integer ltr;

    @SerializedName("reports")
    private Integer reports;

    @SerializedName("share_url")
    private String shareUrl;

    @SerializedName("tags")
    private List<NewsTag> newsTags = null;

    @SerializedName("source")
    private ChildSource childSource;

    @SerializedName("reactions")
    private List<Reaction> reactions = null;

    public Long getId() {
        return id;
    }

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

    public Integer getCellStyle() {
        return cellStyle;
    }

    public void setCellStyle(Integer cellStyle) {
        this.cellStyle = cellStyle;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getRefId() {
        return refId;
    }

    void setRefId(String refId) {
        this.refId = refId;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getThumbnail() {
        return thumbnail;
    }

    void setThumbnail(String thumbnail) {
        this.thumbnail = thumbnail;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public List<Content> getContent() {
        return content;
    }

    public void setContent(List<Content> content) {
        this.content = content;
    }

    public Integer getCommentsCount() {
        return commentsCount;
    }

    void setCommentsCount(Integer commentsCount) {
        this.commentsCount = commentsCount;
    }

    public String getPublishedAt() {
        return publishedAt;
    }

    void setPublishedAt(String publishedAt) {
        this.publishedAt = publishedAt;
    }

    public Integer getLtr() {
        return ltr;
    }

    public void setLtr(Integer ltr) {
        this.ltr = ltr;
    }

    Integer getReports() {
        return reports;
    }

    void setReports(Integer reports) {
        this.reports = reports;
    }

    public String getShareUrl() {
        return shareUrl;
    }

    void setShareUrl(String shareUrl) {
        this.shareUrl = shareUrl;
    }

    public List<NewsTag> getNewsTags() {
        return newsTags;
    }

    void setNewsTags(List<NewsTag> newsTags) {
        this.newsTags = newsTags;
    }

    public ChildSource getChildSource() {
        return childSource;
    }

    void setChildSource(ChildSource childSource) {
        this.childSource = childSource;
    }

    public List<Reaction> getReactions() {
        return reactions;
    }

    public void setReactions(List<Reaction> reactions) {
        this.reactions = reactions;
    }

}

and the same file but in kotlin data class and that's not work

@UseStag
data class Article(
        @SerializedName("id")
        var id: Long? = null,

        @SerializedName("cell_style")
        var cellStyle: Int? = null,

        @SerializedName("type")
        var type: String? = null,

        @SerializedName("ref_id")
        var refId: String? = null,

        @SerializedName("url")
        var url: String? = null,

        @SerializedName("author")
        var author: String? = null,

        @SerializedName("thumbnail")
        var thumbnail: String? = null,

        @SerializedName("title")
        var title: String? = null,

        @SerializedName("content")
        var content: List<Content>? = null,

        @SerializedName("comments_count")
        var commentsCount: Int? = null,

        @SerializedName("published_at")
        var publishedAt: String? = null,

        @SerializedName("is_ltr")
        var ltr: Int? = null,

        @SerializedName("reports")
        var reports: Int? = null,

        @SerializedName("share_url")
        var shareUrl: String? = null,

        @SerializedName("tags")
        var newsTags: List<NewsTag>? = null,

        @SerializedName("source")
        var childSource: ChildSource? = null,

        @SerializedName("reactions")
        var reactions: List<Reaction>? = null
)

Expected Behavior

It should work fine as the rest data classes i have

Actual Behavior

The build failed and the factory not generated

Stag with Kotlin Data Classes

Issue Summary

In Kotlin, all data classes are marked as final classes when the app compiles. An error like this is thrown at compile time:

error: Unable to access field "whatever" in class com.example.stagtest.MyDataModel, field must not be final.

Reproduction Steps

@UseStag
data class ApiResponse(val dates: List<Day>, val totalGames: Int) {

    @UseStag
    data class Day(
        val totalGames: Int, // 14
        val games: List<Game>
    )
}

Attempt to compile.


Expected Behavior

App compiles to completion.

Actual Behavior

Throws an error:

/Users/me/StagDeserializationPrototype/app/build/tmp/kapt3/stubs/debug/com/example/stagdeserializationprototype/ApiResponse.java:92: error: Unable to access field "totalGames" in class com.example.stagdeserializationprototype.ApiResponse.Day, field must not be final.
private final int totalGames = 0;

java.util.concurrent.ExecutionException: com.android.dex.DexException

I successfully added the library with my android studio project and works fine but when i try to export the signed apk both debug and release build throws the following error.

com.android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: java.util.concurrent.ExecutionException: com.android.dex.DexException: Multiple dex files define Lorg/intellij/lang/annotations/Identifier;

Reproduction Steps

Add compile'com.vimeo.stag:stag-library:2.1.0' and apt 'com.vimeo.stag:stag-library-compiler:2.1.0' dependencies to your android project and sync, then try to export the signed apk

Warning:Supported source version 'RELEASE_7' from annotation processor

Hello, I use Android Studio 2.4 preview7 with During the compilation of Stag I obtain an alert:
Warning:Supported source version 'RELEASE_7' from annotation processor 'com.vimeo.stag.processor.StagProcessor' less than -source '1.8'

Actually, I work with jdk1.8

It can raise a problem of compilation ?
If yes, you propose a workaround solution?
It will be useful to plan a RELEASE_8 ?

Add support for top-level json lists

Issue Summary

A server response such as

[
  {},
  {}
]

maps to a model like:

public class Foo extends ArrayList<Bar> {}

stag fails generating code these top-level array objects.

Reproduction Steps

See above

Expected Behavior

Extending ArrayList should produce valid code

Actual Behavior

Fails to generate.

"Cannot find getter or setter" for private parameterized member variables

Issue Summary

Given the following code

private ArrayList<String> myList;

public void setMyList(ArrayList<String>) { ... }

public ArrayList<String> getMyList() { ... }

Stag will be unable to find the setters. Only parameterized fields are affected by this bug.

Reproduction Steps

See above

Expected Behavior

It should find the getter/setter.

Actual Behavior

The build fails.

The cause of this bug is incorrect comparison of TypeMirror objects, using the correct comparison will fix the problem.

Stag.Factory can only be used with a single Gson instance

Issue Summary

Stag.Factory is stateful which can cause problems when attempting to use a Stag.Factory
instance across multiple GsonBuilder invocations.

Reproduction Steps

Create a Stag.Factory instance A.
Create a TypeAdapter implementation B mapping java.util.Date.
Create a TypeAdapter implementation C mapping java.util.Date.
Create a type D to marshal that has a java.util.Date field in it.

Create a Gson instance using a builder configured with A and B.
Create a Gson instance using a builder configured with A and C.

The first Gson instance used to marshal/unmarshal type D will set a private field in A to cache the TypeAdapter instance. If the second Gson instance is then used then the type adapter lookup is skipped in favor of the previously cached instance coming from the alternate Gson instance.

Expected Behavior

The current behavior is an implicit result of performance optimizations. Given that reuse of a
Stag.Factory instance is a corner case the current behavior is probably reasonable. I'm raising this
as an issue to document and highlight this surprising result to see if we want to do anything
about it. For instance, it would be possible to capture the initial Gson instance used and then assert instance equality for subsequent uses rather than fail later in the process.

Actual Behavior

Described above.

Should support boolean "isXXXX()" getter naming convention

Issue Summary

JavaBean naming convention is to use isXXXX() for boolean fields. Stag currently only supports getXXXX().

Reproduction Steps

Attempt to generate code for a boolean field with a is getter. Results in a error.

Expected Behavior

Should support is getters

Actual Behavior

Fails to generate code. Outputs a "cannot find getter/setter" error message.

why not use a map for class->adapter mapping

Issue Summary

why not use a map for class->adapter mapping. currently is using a complicated package-> index-> subFactory-> if-else logic, which is neither efficient nor easy to understand.

Expected Behavior

just generate a map<Class, TypeAdapter> which will be extremely easy to debug and read

Publish snapshot versions between releases

Issue Summary

It would be helpful to publish snapshots of the current dev branch to a maven repo (maybe http://www.sonatype.org/nexus/) so that we could test/use the version in development before it gets released. This would help to iron out any bugs that are in a version before it is officially released. This matches what many other open source libraries do (e.g. https://github.com/square/okhttp) between versions.

Alternatively, we could just upload a beta version to bintray.

class generated between compiles is different

Issue Summary

generated classes depend on
Set<? extends Element> useStagElements = roundEnv.getElementsAnnotatedWith(UseStag.class);
can vary between compiles, making hotfix very hard.

Reproduction Steps

just compile several times

Expected Behavior

generated classes stay the same unless code changes.

Fix step

Set<? extends Element> useStagElements = roundEnv.getElementsAnnotatedWith(UseStag.class); List<? extends Element> sorted = new ArrayList<>(useStagElements); sorted.sort(new Comparator<Element>() { @Override public int compare(Element element, Element t1) { return element.toString().compareTo(t1.toString()); } });

issue with TypeAdpater generation having Object fieldType

Issue Summary

In case of object fieldType, typeadapter uses stagFactory but does not have stagFactory as a member variable.

Reproduction Steps

Create a class with having Object as one member variable, generated TypeAdapter will have compile time issue of mStagFactory not found.

Expected Behavior

mStagFactory is being used in adapter so it should be there as a member variable of class.

Actual Behavior

mStagFactory is being used but is not there as member variable.

Type being known at runtime.

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 ?

Stag generates type adapters for non-annotated nested classes

Issue Summary

Given the class:

@UseStag
public class NestedClassWithoutUseStag {

    String field;

    /**
     * No adapter should be created for this class.
     */
    public static class NestedWithoutUseStag {

    }
}

Stag is generating TypeAdapter implementations for the outer class (NestedClassWithoutUseStag) as expected but is also generating a TypeAdapter for the nested, unannotated class (NestedWithoutUseStag). This is unexpected as it makes a fairly broad assumption of the nature of the nested class in that it assumes it is structurally related to the model/entity class and - for instance - is not a nested JsonDeserializer / JsonSerializer / TypeAdapter implementation.

This additionally causes problems if some of the fields of the top level class are of types defined as nested inner classes but have a custom type adapter registered with Gson. Because Stag is automatically generating the type adapters for the nested class the nested class becomes known to Stag and the Gson type adapter lookup is bypassed.

Expected Behavior

Expected behavior would be that classes need to be explicitly annotated with @UseStag in order for a TypeAdapter to be generated.

Actual Behavior

Nested classes have TypeAdapters generated as with the FieldOption.ALL option. This looks to be explicitly added in 88ab69f with commit comment Fixed backwards incompatibility, fixed nested classes always defaulting to fieldoption.all.

Raising this as an issue so that we can discuss whether this makes sense and/or address the issue via other means.

TypeAdapters for cross referential generic classes throw StackOverflowExceptions

Issue Summary

If you have two generic classes (e.g. Model1<T> and Model2<T>) which each have a field that references the other, the TypeAdapters for these classes will not be able to be instantiated. This is because in the constructor of a generic type adapter, all the necessary type adapters are created using new TypeAdapter. This means that Model1 instantiates Model2 in its constructor, and Model2 instantiates Model1 in its constructor, creating an infinite chain of instantiation.

Reproduction Steps

Create two generic classes that reference each other, and try to instantiate the TypeAdapters created.

Expected Behavior

The TypeAdapter should be correctly instantiated.

Actual Behavior

The TypeAdapter constructor throws a StackOverflowException

Note: Non-generic cross referential models work as expected

Stag-java does not support Kotlin

Issue Summary

Kotlin makes all fields private and that raises the stag exception: Unable to access field "x" in class Y, field must not be private.

Expected Behavior

It would be nice if there was a workaround for Kotlin ;)

Create a wiki

Issue Summary

The readme is very long, and it would be better if the readme only showed simple set up and explained what the library did. Advanced examples could be moved to the wiki for better organization.

Raised by @msya on a PR.

PrimitiveCharArrayAdapter is broken

Issue Summary

The char array type adapter is broken. It writes an array of numbers to json since it writes the characters as numbers, but tries to read the array out of the json as a string. This is broken. See KnownTypeAdaptersTest.testForPrimitiveArrayCharacterTypeAdapter() for a test case that breaks the adapter.

Reproduction Steps

char[] value = new char[5];

value[0] = 'a';
value[1] = 'b';
value[2] = 'c';
value[3] = 'd';
value[4] = 'e';

StringWriter stringWriter = new StringWriter();
KnownTypeAdapters.PrimitiveCharArrayAdapter.write(new JsonWriter(stringWriter), value);
String jsonString = stringWriter.toString();

char[] readValue = KnownTypeAdapters.PrimitiveCharArrayAdapter.read(new JsonReader(new StringReader(jsonString)));

// This is broken
Assert.assertArrayEquals(value, readValue);

Expected Behavior

The char array should be read from json correctly.

Actual Behavior

An exception is thrown:

java.lang.IllegalStateException: Expected a string but was BEGIN_ARRAY at line 1 column 2 path $
	at com.google.gson.stream.JsonReader.nextString(JsonReader.java:825)
	at com.google.gson.internal.bind.TypeAdapters$16.read(TypeAdapters.java:418)
	at com.google.gson.internal.bind.TypeAdapters$16.read(TypeAdapters.java:406)
	at com.google.gson.TypeAdapter$1.read(TypeAdapter.java:199)
	at com.vimeo.stag.KnownTypeAdapters$PrimitiveCharArrayAdapter.read(KnownTypeAdapters.java:688)

Unable to access field for data class

Issue Summary

When trying to use a kotlin data class, I am unable to generate the convertors.

For example using the class

@UseStag
data class Foo(
    val name: String,
    val occupation: String
)

Gives the error

error: Unable to access field "name" in class com.company.Foo, field must not be final.
   private final java.lang.String name = null;

The generated class for this data class looks like

import com.vimeo.stag.UseStag;

@kotlin.Metadata(mv = {1, 1, 15}, bv = {1, 0, 3}, k = 1, d1 = {"\u0000\"\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u000e\n\u0002\b\t\n\u0002\u0010\u000b\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0002\b\u0087\b\u0018\u00002\u00020\u0001B\u0015\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\u0006\u0010\u0004\u001a\u00020\u0003\u00a2\u0006\u0002\u0010\u0005J\t\u0010\t\u001a\u00020\u0003H\u00c6\u0003J\t\u0010\n\u001a\u00020\u0003H\u00c6\u0003J\u001d\u0010\u000b\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u00032\b\b\u0002\u0010\u0004\u001a\u00020\u0003H\u00c6\u0001J\u0013\u0010\f\u001a\u00020\r2\b\u0010\u000e\u001a\u0004\u0018\u00010\u0001H\u00d6\u0003J\t\u0010\u000f\u001a\u00020\u0010H\u00d6\u0001J\t\u0010\u0011\u001a\u00020\u0003H\u00d6\u0001R\u0011\u0010\u0002\u001a\u00020\u0003\u00a2\u0006\b\n\u0000\u001a\u0004\b\u0006\u0010\u0007R\u0011\u0010\u0004\u001a\u00020\u0003\u00a2\u0006\b\n\u0000\u001a\u0004\b\b\u0010\u0007\u00a8\u0006\u0012"}, d2 = {"Lcom/company/Foo;", "", "name", "", "occupation", "(Ljava/lang/String;Ljava/lang/String;)V", "getName", "()Ljava/lang/String;", "getOccupation", "component1", "component2", "copy", "equals", "", "other", "hashCode", "", "toString", "app_staging"})
@com.vimeo.stag.UseStag()
public final class Foo {
    @org.jetbrains.annotations.NotNull()
    private final java.lang.String name = null;
    @org.jetbrains.annotations.NotNull()
    private final java.lang.String occupation = null;
    
    @org.jetbrains.annotations.NotNull()
    public final java.lang.String getName() {
        return null;
    }
    
    @org.jetbrains.annotations.NotNull()
    public final java.lang.String getOccupation() {
        return null;
    }
    
    public Foo(@org.jetbrains.annotations.NotNull()
    java.lang.String name, @org.jetbrains.annotations.NotNull()
    java.lang.String occupation) {
        super();
    }
    
    @org.jetbrains.annotations.NotNull()
    public final java.lang.String component1() {
        return null;
    }
    
    @org.jetbrains.annotations.NotNull()
    public final java.lang.String component2() {
        return null;
    }
    
    @org.jetbrains.annotations.NotNull()
    public final com.company.Foo copy(@org.jetbrains.annotations.NotNull()
    java.lang.String name, @org.jetbrains.annotations.NotNull()
    java.lang.String occupation) {
        return null;
    }
    
    @org.jetbrains.annotations.NotNull()
    @java.lang.Override()
    public java.lang.String toString() {
        return null;
    }
    
    @java.lang.Override()
    public int hashCode() {
        return 0;
    }
    
    @java.lang.Override()
    public boolean equals(@org.jetbrains.annotations.Nullable()
    java.lang.Object p0) {
        return false;
    }
}

Even with default values I still have this issue

Support JsonAdapter Annotation

Support JsonAdapter annotation which is also available in Gson. The complete documentation about JsonAdapter is available here : https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/annotations/JsonAdapter.java

There are some adapters which are very custom and need to be hand-written. Given this support the adapter for these classes will also be recognised by STAG while generating the code.

This will also solve the scenario mentioned in issue #25

Kotlin models should be able to utilize internal visibility

Summary

Currently, Kotlin support in stag requires public visibility of setters and getters. This is because internal, private, and protected all prevent stag from seeing the setters and getters. internal should be supported, but cannot until there is a standalone kotlin stag compiler due to the way setter and getter names are created in java code for kotlin.

Proposed Spec

  • Kotlin fields should be able to reduce visibility of setters and getters to internal.

Stag should generate Kotlin code for Kotlin models

Summary

The only code generated by Stag is java code. This is all fine and good, but it prevents us from leveraging special features of the kotlin language such as internal visibility. We should be able to generate

Proposed Spec

  • Split up stag-library-compiler into stag-compiler, stag-java-compiler, and stag-kotlin-compiler.
  • stag-compiler will be an internal use only module and will house common logic. It will perform code generation via an interface or will provide data to an upper level which will perform the code generation instead
  • The language specific modules will perform the code generation and will be what consumers of the library rely on.

Debug logs show up during compilation

Issue Summary

Debug logs such as Looking for getter and Found getter show up while compiling even with the stagDebug flag not being set.

Reproduction Steps

Compile code with private members.

Expected Behavior

No logging unless the stagDebug argument is true.

Actual Behavior

Logging occurs.

Originally reported by @Ahmed-Abdelmeged

ExternalModelExample2 Type Adapter is broken

Issue Summary

Instantiating the Type Adapter generated for the ExternalModelExample2 class causes a stack overflow. The cause is that the model class is generic and references itself. Note that other self referential classes such as RecursiveClass are generated and instantiated without error.

Reproduction Steps

Instantiate the type adapter with the following code and observe the stack overflow:

ExternalModelExample2$TypeAdapter typeAdapter = new ExternalModelExample2$TypeAdapter(gson, factory, typeAdapter1);

For simplicity, uncomment the test in ExternalModelExample2Test and observe the problem.

Expected Behavior

It should properly instantiate the type adapter.

Actual Behavior

StackOverflowException occurs.

Stag should not require compiler argument for hungarian notation

Issue Summary

Currently, it is required to pass an argument to the annotation processor if you wish to use hungarian notation when naming your java getters an setters.

Reproduction Steps

Create the following model

@UseStag
class Test {
    private String mField;

    public void setField(String field) { ... }
    public String getField() { ... }
}

and do NOT pass the hungarian naming parameter to the annotation processor.

Expected Behavior

Ideally, you should be able to name your setters as above

Actual Behavior

You are warned that setters and getters are not found and compilation fails.

Potential solutions

  • Check for hungarian and non hungarian named methods
    • downside: namespace collisions
    • downside: potential non-determinism when both hungarian and non-hungarian names exist
    • upside: consumer doesn't have to provide compiler argument
  • Option in class level @UseStag annotation
    • downside: verbose, consumer has to provide option in every class using the naming scheme
    • upside: localized, easier to consume than compiler argument
    • upside: very deterministic

cc @kvenn

How to upload video to Vimeo from Android app using this Library?

Hello.

I would like to upload video to vimeo from my android app.
Can I upload video from my android app using your github repository?
Or is there any other published github source code that can upload video?

Thanks,
Look forward to hearing from you.

Best Regards.

StagTypeAdapterFactory.list included in APK

Issue Summary

StagTypeAdapterFactory.list, which is used by stag during compilation, is included in the compiled APK.

Expected Behavior

This file should not be included as it is unnecessary for any runtime functionality.

Code breaking for subclass which has nested parametrized member variables

Issue Summary

public class DummyGenericClass {
HashMap<String, List> testArrayMap;
ArrayList<Map<String, T>> testListMap;
}

public class DummyInheritedClass extends DummyGenericClass {

}

Generation of TypeAdapter for DummyInheritedClass fails.

Reproduction Steps

Added unit tests. Use the above as an example

Expected Behavior

ArrayList<Map<String, T>> should resolve to ArrayList<Map<String, String>>

Actual Behavior

ArrayList<Map<String, T>> should resolve to ArrayList

Allow 1's and 0's to be parsed as booleans

Issue Summary

Currently, binary numbers are not supported by Stag for parsing into boolean fields. This is not something that Gson supports. Proposed spec:

  • for field boolean
  • numbers 1 and 0 will be properly parsed into true and false.
  • other numbers will throw a JsonSyntaxException

Reproduction Steps

create a field

@SerializedName("boolean_field")
boolean myField;

parse the following data into that field

{
"boolean_field":"1"
}

Expected Behavior

We would expect that binary numbers would be accepted as true and false.

Actual Behavior

Observe a JsonSyntaxException being thrown and parsing halted.

Fields referencing wildcarded generics cause compilation failure

Issue Summary

Defining models with wild-carded generics such as the following result in invalid Java sources being generated for the TypeAdapter.

This scenario is useful when consuming models of varying types into a single collection. For example, the code may leverage a provided type adapter factory to dynamically determine how to unmarshal the a JSON object payload, dynamically resolving the correct TypeAdapter to use for the object type being unmarshaled.

Reproduction Steps

Define a model which uses wildcarded generics such as the following:

@UseStag
public class DynamicallyTypedWildcardReadModel {
    public List<DynamicallyTypedModel<?>> models;
}

The generated code for the failing constructor looks like this:

  public DynamicallyTypedWildcardReadModel$TypeAdapter(Gson gson) {
    this.mGson = gson;
    com.google.gson.reflect.TypeToken<com.vimeo.sample_java_model.DynamicallyTypedModel<?>> typeToken0 = (com.google.gson.reflect.TypeToken<com.vimeo.sample_java_model.DynamicallyTypedModel<?>>)com.google.gson.reflect.TypeToken.getParameterized(com.vimeo.sample_java_model.DynamicallyTypedModel.class, ?.class);
    this.mTypeAdapter0 = gson.getAdapter(typeToken0);
    this.mTypeAdapter1 = new com.vimeo.stag.KnownTypeAdapters.ListTypeAdapter<com.vimeo.sample_java_model.DynamicallyTypedModel<?>,java.util.List<com.vimeo.sample_java_model.DynamicallyTypedModel<?>>>(mTypeAdapter0, new com.vimeo.stag.KnownTypeAdapters.ListInstantiator<com.vimeo.sample_java_model.DynamicallyTypedModel<?>>());
  }

Which results in compilation failure:

/Users/mcumings/src/github/stag-java/integration-test-java/build/generated/source/apt/main/com/vimeo/sample_java_model/DynamicallyTypedWildcardReadModel$TypeAdapter.java:28: error: illegal start of expression
    com.google.gson.reflect.TypeToken<com.vimeo.sample_java_model.DynamicallyTypedModel<?>> typeToken0 = (com.google.gson.reflect.TypeToken<com.vimeo.sample_java_model.DynamicallyTypedModel<?>>)com.google.gson.reflect.TypeToken.getParameterized(com.vimeo.sample_java_model.DynamicallyTypedModel.class, ?.class);

Expected Behavior

Stag generated code compiles and the behavior at runtime is identical to the behavior of Gson's ReflectiveTypeAdapter.

Actual Behavior

Compilation failure.

Workaround

Simply remove the @UseStag annotation for the model which contains the generics references to revert back to the built-in ReflectiveTypeAdapter (which works).

Support for a class level annotation

Issue Summary

Now that Stag has support for Generics, Enums, Maps etc it can generate a type adapter for pretty much any class. With the current approach of having to provide GsonAdapterKey is a huge friction for people to move to this library from Gson.

We can have another annotation maybe at a class level called @UseStag. We have some sort of implementation for this already in place.

Stag cannot access inherited members if the classes are in different packages

Issue Summary

If ClassA extends ClassB and inherits fields, stag will collect all the inherited fields and use them in the creation of an object. If ClassA extends ClassB, which is in a different package than ClassA, and the members of ClassB are protected or package local (legal according to stag rules), then the type adapter for ClassA will not be able to access them.

Reproduction Steps

Take the following two classes. The generated ClassA$TypeAdapter will not be able to access field2.

package sample.a;

@UseStag
class ClassA extends ClassB {
    protected String field1;
}

package sample.b;

@UseStag
class ClassB {
    protected String field2;
}

Expected Behavior

Stag should either be able to access the fields, or should enforce a rule requiring classes that are extended to make their members public.

Actual Behavior

Given the example above, the generated ClassA$TypeAdapter will not be able to access field2.

Data binding files with errors

Issue Summary

I just integrated the file into android gradle and after syncing it just went to errors of data binding files. I have implemented MVVM in my android project.

Unable to find getter for List<Map<String, String>>

Issue Summary

Stag fails to find the getter for a list of maps,

Reproduction Steps

@UseStag
@Generated("com.robohorse.robopojogenerator")
data class RequestBodyTransaction(
        @field:SerializedName("restrictions")
        var listOfMaps: List<Map<String, String>> = emptyList()
)

Expected Behavior

To work and parse a list of maps

Actual Behavior

A Compile error as below

RequestBodyTransaction.java:16: error: Unable to find getter for variable: restrictions
    private java.util.List<? extends java.util.Map<java.lang.String, java.lang.String>> restrictions;
                                                                                        ^

Stag does resolve

Issue Summary

Stag class is generated but is not resolved by the IDE

Reproduction Steps

  1. Create a kotlin module
  2. Add @UseStag annotation on a data class
  3. Try to call Stag().Factory()

Expected Behavior

Stag class is generated under the specified package, should resolve by IDE

Actual Behavior

The Stag class is generated, however, the IDE cannot resolve it

val gson = GsonBuilder()
                .registerTypeAdapterFactory(com.mrezanasirloo.domain.networkimpl.Stag.Factory())
                .create()

The above code compiles, but as I stated before the Stag class is hinted by IDE as an unresolved refrence.

Found the solution while opening this issue, so the solution is to add this block to include the generated source to the classpath

sourceSets {
    main.java.srcDirs += "build/generated/source/kapt/main"
}

Duplicate Classes generated

Issue Summary

Duplicate classes are being generated when both the base class and derived class use @UseStag annotation and the base class exists in another module.

Reproduction Steps

Define a class in sample_model library

package com.vimeo.sample_model;

import com.vimeo.stag.UseStag;

@UseStag
public class BaseExternalModel {
    public String type;

    public int baseValue;

}

In sample app module add ExternalModelDerivedExample class

package com.vimeo.sample.model;

import com.vimeo.sample_model.BaseExternalModel;
import com.vimeo.stag.UseStag;

@UseStag
public class ExternalModelDerivedExample extends BaseExternalModel {

    public Boolean derivedBoolean;
}

Expected Behavior

It should create only one TypeAdapter for BaseExternalModel which should exist in the sample_module module only.

Actual Behavior

It generates two classes by BaseExternalModel$TypeAdapter in both the modules. Please check the screen shot below

What currently happens as a result of the reproduction steps?
image

Enum fields can't be private or final

Issue Summary

In the 2.1.0 release, which more explicitly messages the consumer on when fields are private and/or final, a bug was introduced which prevents enum fields from being private or final. The cause of this bug was a global check for private and final fields on any class annotated with @UseStag. The correct solution would be to not impose those checks on enums, since enums are constant and don't need to serialize the fields.

Reproduction Steps

Create an enum that takes a parameter in its constructor. Make the field that it maps to private and final.

Expected Behavior

The Stag class to compile successfully.

Actual Behavior

The compilation fails due to the private and final fields.

Add compiler configuration for writing null values to JSON

Issue Summary

Currently, if the fields of an object are null, stag adapters skip writing those fields to JSON. This is valid behavior, but if you have default values which your fields are assigned to, then writing to JSON and then reading back out the object will result in unequal object.

Reproduction Steps

given the following object:

class Test {
    @Nullable public String field;
    @Nullable public String field2 = "default";

    public Test(String field, String field2) { ... }
}

with values

Test test = new Test("test", null);

the following JSON will be created

{
"field":"test",
}

and when read from JSON, will result in the Test object having the following properties

{
"field":"test",
"field2":"default"
}

which results in inequality. This is expected behavior and is not a bug, but we should provide a way to force the writing of null to the JSON so that the entire object state is preserved and default values are not used. This will still allow empty JSON to result in default assignment, but it will fix a problem where nullable fields with default values can cause inequality problems.

We should provide a compiler option that allows stag to force the writing of null fields to JSON.

related to this bug: #132

Upgrade com.intellij:annotations to avoid Dex Compiler D8 conflict issues

Issue Summary

Same behaviour as vimeo/vimeo-networking-java#285

Reproduction Steps

With Android Studio 3.2 Beta 5, create a simple (Empty Activity) Android project with Kotlin support
Add app stag dependency with version 2.5.1
Run: ./gradlew assembleDebug

Workarround

Same workarround as vimeo/vimeo-networking-java#285

implementation ("com.vimeo.stag:stag-library:${stag_version}", {
        exclude group: "com.intellij", module: "annotations"
    })

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.