Java implementation of JSON API specification v1.0 for moshi.
JsonAdapter.FactoryjsonApiAdapterFactory = ResourceAdapterFactory.builder()
.add(Article.class)
.add(Person.class)
.add(Comment.class)
// ...
.build();
Moshimoshi = newMoshi.Builder()
.add(jsonApiAdapterFactory)
// ...
.build();You're now ready to serialize/deserialize JSON API objects with cool Moshi interface!
Stringjson = "...";
ArrayDocument<Article> articles = moshi.adapter(Document.class).fromJson(json).asArrayDocument();
for (Articlearticle : articles) {
System.out.println(article.title);
}Extend a Resource class to create a model for resource object.
@JsonApi(type = "people")
classPersonextendsResource {
@Json(name="first-name") StringfirstName;
@Json(name="last-name") StringlastName;
Stringtwitter;
}@JsonApi(type = ...) annotation identifies each model by type as is mentioned in specification.
There are two kinds of relationship defined in JSON API specification. Defining these relationship in resource object is quite simple:
@JsonApi(type = "articles")
publicclassArticleextendsResource {
publicStringtitle;
publicHasOne<Person> author;
publicHasMany<Comment> comments;
}Relationships can be resolved to resource object in a Document:
Personauthor = article.author.get(article.getDocument());You can use Resource.getDocument() to access the Document object the Resource be added/included in.
Further more, with a little bit encapsulation:
@JsonApi(type = "articles")
publicclassArticleextendsResource {
privateStringtitle;
privateHasOne<Person> author;
privateHasMany<Comment> comments;
publicStringgetTitle() {
returntitle;
}
publicvoidsetTitle(Stringtitle) {
this.title = title;
}
publicPersongetAuthor() {
returnauthor.get(getDocument());
}
publicList<Comment> getComments() {
returncomments.get(getDocument());
}
}Document interfaces denotes a JSON API document, document object contains one of the following attributes:
datathe primary data, can be null, resource object or array of resource objecterrorerror objectmeta
To keep consistency with the specification, moshi-jsonapi implements ArrayDocument<T> and ObjectDocument<T>.
Document object can be converted with Document.<T>asXDocument() function.
ObjectDocument<Article> document = newObjectDocument<>();
document.set(article);
document.addInclude(author);
// SerializeSystem.out.println(moshi.adapter(Document.class).toJson(document));
// => {// data: { "type": "articles", "relationships": { "author": { "data": "type": "people", id: "1" } } },// included: [// { "type": "people", "attributes": { "first-name": "Yuki", "last-name": "Kiriyama", "twitter": "kamikat_bot" } }// ]// }// DeserializeDocumentdocument2 = adapter.fromJson(...);
ObjectDocument<Article> document3 = document2.asObjectDocument();
assertdocument3.get() instanceofArticleassertdocument3.get().getDocument() == document3The linkage (relationship) of a resource object is resolved in document of the resource object (check Resource.getDocument()).
Create a default typed class to have all unknown type parsed in the class to avoid deserialization error processing unknown type of resource.
@JsonApi(type = "default")
classUnknownextendsResource {
// nothing...
}You'd like to access meta/links/jsonapi value on Document for example.
Documentdocument = ...;
document.getMeta() // => JsonBufferAs meta and links can contain a variant of objects, they are not been parsed when access with getMeta and getLinks.
You will get a JsonBuffer and you're expected to implement your JsonAdapter<T> to read/write these objects.
Retrofit extension library (see following section) provides JsonApiConverterFactory to get integrate with Retrofit 2.
Here's an example:
Retrofitretrofit = newRetrofit.Builder()
// ...
.addConverterFactory(JsonApiConverterFactory.create(moshi))
.build()
retrofit.create(MyAPI.class);And MyAPI interface:
publicinterfaceMyAPI {
@GET("posts")
Call<Post[]> listPosts();
@GET("posts/{id}")
Call<Post> getPost(@Path("id") Stringid);
@GET("posts/{id}/comments")
Call<List<Comment>> getComments(@Path("id") Stringid);
@POST("posts/{id}/comments")
Call<Document> addComment(@Path("id") Stringid, @BodyCommentcomment);
@GET("posts/{id}/relationships/comments")
Call<ResourceIdentifier[]> getCommentRels(@Path("id") Stringid);
}Note that the body can either be serialized/deserialized to resource object or document object with additional information.
In gradle build script:
repositories {
jcenter()
}
dependencies {
implementation 'com.squareup.moshi:moshi:1.4.0'// required, peer dependency to moshi
implementation 'moe.banana:moshi-jsonapi:<version>'// required, core library
implementation 'moe.banana:moshi-jsonapi-retrofit-converter:<version>'// optional, for retrofit
}For library version >= 3.5, moshi is removed from runtime dependencies of the library to become a peer dependency.
Use snapshot version:
repositories {
maven { url "https://jitpack.io" }
}
dependencies {
implementation 'com.squareup.moshi:moshi:1.4.0'
implementation 'moe.banana:moshi-jsonapi:master-SNAPSHOT'
}NOTE: It's necessary clean gradle library cache to access the latest snapshot version.
For moshi-jsonapi:
-keepattributes Signature
-keepclassmembers class moe.banana.jsonapi2.** {
*;
}
Additional fix for not obfuscating any jsonapi objects implemented in DTOs as fields:
-keepclassmembers class * extends moe.banana.jsonapi2.** {
*;
}
For moshi, if you use a custom JSON adapter (e.g. for Enum types):
-keepclassmembers class ** {
@com.squareup.moshi.FromJson *;
@com.squareup.moshi.ToJson *;
}
| Feature | Supported | Note |
|---|---|---|
| Serialization | Yes | |
| Deserialization | Yes | |
| Custom-named fields | Yes | With @Json |
| Top level errors | Yes | |
| Top level metadata | Yes | |
| Top level links | Yes | |
| Top level JSON API Object | Yes | |
| Resource metadata | Yes | |
| Resource links | Yes | |
| Relationships | Yes | HasOne and HasMany |
| Inclusion of related resources | Yes | |
| Resource IDs | Yes |
Release 3.4 removed type parameter from Document object which can break your code. Please replace the type declaration with
ObjectDocument<T> or ArrayDocument<T> if you insist that.
Release 3.5 changes the dependency to moshi from runtime dependency to compile-only dependency, which means moshi-jsonapi does no longer includes moshi as a dependency for your project. And you need to add moshi to the dependencies of the project manually.
3.x supports all features supported by JSON API specification. And the interface changed a lot especially in serialization/deserialization.
More object oriented features are added to new API. If you're using the library with Retrofit, migration should be a lot easier by using a
special Converter adapts Document<Article> to Article[] and backward as well (see retrofit section). Migration should be
easy if you use latest 2.x API with some OO features already available. Otherwise, it can take hours to migrate to new API.
2.x abandoned much of seldomly used features of JSON API specification and re-implement the core of JSON API without AutoValue since AutoValue is considered too verbose to implement a clean model.
And the new API no longer requires a verbose null check since you should take all control over the POJO model's nullability check.
Another major change is that the new API is not compatible with AutoValue any more. Means that one have to choose 1.x implementation if AutoValue is vital to bussiness logic.
(The MIT License)