Written purely in kotlin 😍❤️
retrofit-jsonapi-converter is a Kotlin library for Android that adapts with retrofit and moshi to be able to map http responses with jsonapi.
see jsonapi specification
Support
- RxJava Observables
- Observable
- Single
- Coroutine Flow
In recent years, the Rest architecture has been widely adopted for the exchange of information between web services and clients (client-server). JsonApi is a standard that works over HTTP; it was created to help define a more consistent response standard within the development team to increase productivity and efficiency; Thus reducing the number of requests and the size of the data packets transported between the client and the server.
Let's look at the difference between a list of articles with their respective authors
RESTful
[
{
"id": "1",
"title": "JSON:API paints my bikeshed!",
"body": "The shortest article. Ever.",
"created": "2015-05-22T14:56:29.000Z",
"updated": "2015-05-22T14:56:28.000Z",
"author": {
"id": "42",
"name": "John",
"age": 80,
"gender": "male"
}
}
]
JsonApi (see Json Api specification)
{
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API paints my bikeshed!",
"body": "The shortest article. Ever.",
"created_date": "2015-05-22T14:56:29.000Z",
"updated_date": "2015-05-22T14:56:28.000Z"
},
"relationships": {
"author": {
"data": {"id": "42", "type": "people"}
}
}
}],
"included": [
{
"type": "people",
"id": "42",
"attributes": {
"name": "John",
"age": 80,
"gender": "male"
}
}
]
}
Add retrofit-jsonapi-converter as Gradle build dependency.
repositories {
mavenCentral()
google()
}
dependencies {
implementation "com.squareup.moshi:moshi:LAST_VERSION"
implementation 'tech.jorgecastro:retrofit-jsonapi-converter:LAST_VERSION'
}
add the following lines when creating the retrofit instance:
- addConverterFactory(JsonApiConverterFactory())
- addCallAdapterFactory(JsonApiCallAdapterFactory.create())
Retrofit.Builder()
.baseUrl(baseUrl)
.client(httpClient)
.addConverterFactory(JsonApiConverterFactory())
.addCallAdapterFactory(JsonApiCallAdapterFactory.create())
.build()
Adding MoshiConverterFactory and RxJava2CallAdapterFactory.
val moshi =
Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
Retrofit.Builder()
.baseUrl(baseUrl)
.client(httpClient)
.addConverterFactory(JsonApiConverterFactory())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(JsonApiCallAdapterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
@JsonApiMethod
annotation to each method that responds with jsonapi format
interface TestApi {
@JsonApiMethod
@GET("PATH_URL")
fun getArticles(): Single<List<Article>>
@JsonApiMethod
@GET("PATH_URL/{id}")
fun getArticle(@Path("id") id: Int): Single<Article>
}
@JsonApiResource(type = "people")
data class People(
@field:Json(name = "id") var id: String = "", // required
var name: String = "",
var age: String = "",
var gender: String = ""
)
@JsonApiResource(type = "article")
data class Article(
@field:Json(name = "id") val id: String = "", // required
val title: String = "",
val body: String = "",
@Json(name = "created_date") val createdDate: String = "",
@Json(name = "updated_date") val updatedDate: String = "",
@JsonApiRelationship(jsonApiResourceName = "people", jsonAttrName = "author")
var authors: List<People>? = listOf() // name is JsonApiResource of people
)
val dataApi = getRetrofitApi()
dataApi.getArticles()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// List<Article>
}, {
// Code for Error
})
dataApi.getArticle(1)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// Article
}, {
// Code for Error
})
When you work with JsonApi you can find multiple problems, these are represented in an array of errors. JsonApi returns a JsonApiException when http 4xx codes are processed
{
"jsonapi": { "version": "1.0" },
"errors": [
{
"code": "400",
"source": { "pointer": "/data/attributes/firstName" },
"title": "Value is too short",
"detail": "First name must contain at least three characters."
},
{
"code": "400",
"source": { "pointer": "/data/attributes/password" },
"title": "Passwords must contain a letter, number, and punctuation character.",
"detail": "The password provided is missing a punctuation character."
},
{
"code": "400",
"source": { "pointer": "/data/attributes/password" },
"title": "Password and password confirmation do not match."
}
]
}
val dataApi = getRetrofitApi()
dataApi.getDataWithObservableError()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// Success
}, {
if (it is JsonApiResponseException) { // Exception type for JsonApi errors
val errorData = it.data // List of errors with data attribute.
}
})
JsonApiResponseException is thrown when the JsonApi response is a set of system-managed errors. The error payload can be accessed by calling through the data attribute
- Retrofit JsonApi Converter does not support mixed data in the current version.
- In the current version there is no support for the links attribute in errors
Jorge Castro - @devjorgecastro on GitHub, @devjcastro on Twitter
This is not an official Square product.
Copyright (c) 2020, Jorge Castro.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.