GithubHelp home page GithubHelp logo

stantanasi / retrofit-jsonapi-converter Goto Github PK

View Code? Open in Web Editor NEW
2.0 2.0 0.0 227 KB

Retrofit JsonApi Converter is an Android library for converting JSON:API response to model and model to JSON:API format

Home Page: https://jitpack.io/#stantanasi/retrofit-jsonapi-converter

License: Apache License 2.0

Kotlin 100.00%
android android-library json-api retrofit retrofit2 converter json-api-serializer json-api-client retrofit-jsonapi-converter kotlin

retrofit-jsonapi-converter's Introduction

Retrofit JSON:API Converter


A Retrofit converter for JSON:API specification.
Implement library »

Report Bug · Request Feature

Table of Contents

About the project

Retrofit JSON:API Converter is a Retrofit converter for JSON:API specification

JSON:API is a specification for how a client should request that resources be fetched or modified, and how a server should respond to those requests.

JSON:API is designed to minimize both the number of requests and the amount of data transmitted between clients and servers. This efficiency is achieved without compromising readability, flexibility, or discoverability.

This is not an official Square product.

Built with

Getting started

Prerequisites

Inside your root build.gradle, add the JitPack maven repository to the list of repositories:

allprojects {
  repositories {
    ...
    maven { url 'https://jitpack.io' }
  }
}

Inside your module build.gradle, implement library latest version:

dependencies {
  ...
  implementation 'com.github.stantanasi:retrofit-jsonapi-converter:LAST_VERSION'
}

Setup

Add the following lines when creating the retrofit instance:

  • addCallAdapterFactory(JsonApiCallAdapterFactory.create())
  • addConverterFactory(JsonApiConverterFactory.create())
val retrofit = Retrofit.Builder()
  .baseUrl("http://example.com/")
  .addCallAdapterFactory(JsonApiCallAdapterFactory.create())
  .addConverterFactory(JsonApiConverterFactory.create())
  .build()

Usage

JSON:API response object

Let's suppose you have an API that returns the following response:

{
  "data": {
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "JSON:API paints my bikeshed!"
    },
    "links": {
      "self": "http://example.com/articles/1"
    },
    "relationships": {
      "author": {
        "links": {
          "self": "http://example.com/articles/1/relationships/author",
          "related": "http://example.com/articles/1/author"
        },
        "data": {
          "type": "people",
          "id": "9"
        }
      },
      "comments": {
        "links": {
          "self": "http://example.com/articles/1/relationships/comments",
          "related": "http://example.com/articles/1/comments"
        },
        "data": [
          {
            "type": "comments",
            "id": "5"
          },
          {
            "type": "comments",
            "id": "12"
          }
        ]
      }
    }
  },
  "included": [
    {
      "type": "people",
      "id": "9",
      "attributes": {
        "first-name": "Dan",
        "last-name": "Gebhardt",
        "twitter": "dgeb"
      },
      "links": {
        "self": "http://example.com/people/9"
      }
    },
    {
      "type": "comments",
      "id": "5",
      "attributes": {
        "body": "First!"
      },
      "relationships": {
        "author": {
          "data": {
            "type": "people",
            "id": "2"
          }
        }
      },
      "links": {
        "self": "http://example.com/comments/5"
      }
    },
    {
      "type": "comments",
      "id": "12",
      "attributes": {
        "body": "I like XML better"
      },
      "relationships": {
        "author": {
          "data": {
            "type": "people",
            "id": "9"
          }
        }
      },
      "links": {
        "self": "http://example.com/comments/12"
      }
    }
  ]
}

Setting the models

You could create the models like this:

@JsonApiType("articles")
data class Article(
    var id: String? = null,
    var title: String = "",
    var author: People? = null,
    var comments: List<Comment> = listOf(),
)

@JsonApiType("people")
data class People(
    @JsonApiId var id: String,
    @JsonApiAttribute("first-name") val firstName: String,
    @JsonApiAttribute("last-name") val lastName: String,
    @JsonApiAttribute("twitter") val twitter: String = "",
)

@JsonApiType("comments")
data class Comment(
    @JsonApiId val id: String? = null,
    var body: String = "",
    var author: People? = null,
)
  • Use class or data class, whichever you prefer.
  • Use val or var, whichever you prefer.

To have custom property name, you must add @JsonApiAttribute and/or @JsonApiRelationship annotations.

Property with default value is recommended, in case attribute is not present inside json response.

Annotations @JsonApiAttribute and @JsonApiRelationship contains an "ignore" property wich ignore fields in request body

Dynamic updates on request body

If you send your model inside a request, your model will be converted to JSON:API specification with ALL attributes and relationships.

If you only need to send specific attributes/relationships inside your request body, you have to:

  • implements JsonApiResource to your model
  • Add updated properties inside dirtyProperties

I recommend using delegate class JsonApiProperty. Using this, only properties updated after instancing will be sent into request body.

@JsonApiType("articles")
class Article(
    var id: String? = null,
    title: String = "",
    author: People? = null,
    comments: List<Comment> = listOf(),
) : JsonApiResource {

    var title: String by JsonApiProperty(title)
    var author: People? by JsonApiProperty(author)
    var comments: List<Comment> by JsonApiProperty(comments)

    override val dirtyProperties: MutableList<KProperty<*>> = mutableListOf()
}
Article().also {
    it.title = "test"
    it.author = People(
        id = "2"
    )
}
{
  "type": "articles",
  "attributes": {
    "title": "test"
  },
  "relationships": {
    "author": {
      "data": {
        "type": "people",
        "id": "2"
      }
    }
  }
}

Multi-type relationship

@JsonApiType("people")
data class People(
    ...
    val books: List<Book> = listOf()
)

sealed class Book {
    @JsonApiType("dictionaries")
    data class Dictionaries(val id: String, val title: String) : Book()

    @JsonApiType("graphic-novels")
    data class GraphicNovel(val id: String, val name: String) : Book()
}

Define the endpoints

With Retrofit 2, endpoints are defined inside of an interface using special retrofit annotations to encode details about the parameters and request method.

@GET("articles")
suspend fun getArticles(@QueryMap params: JsonApiParams = JsonApiParams()): JsonApiResponse<List<Article>>

@GET("articles/{id}")
suspend fun getArticle(@Path("id") id: String, @QueryMap params: JsonApiParams = JsonApiParams()): JsonApiResponse<Article>

@POST("articles")
suspend fun createArticle(@Body article: Article): JsonApiResponse<Article>

@DELETE("articles/{id}")
suspend fun deleteArticle(@Path("id") id: String): JsonApiResponse<Unit>

JsonApiParams

JsonApiParams(
    include = listOf<String>(),
    fields = mapOf<String, List<String>>(),
    sort = listOf<String>(),
    limit = 10,
    offset = 0,
    filter = mapOf<String, List<String>>()
)

JsonApiResponse

when (response) {
    is JsonApiResponse.Success -> {
        response.headers // okhttp3.Headers
        response.code // Int (e.g., 2xx)

        response.body.jsonApi?.version // String (e.g., "1.0")
        response.body.included // JSONArray
        response.body.links?.first // String (e.g., "http://example.com/...")
        response.body.meta // JSONObject

        response.body.raw // String (e.g., " {"data":{"type":"articles", ... ")
    }
    is JsonApiResponse.Error.ServerError -> {
        response.body.errors.forEach {
            it.id // String
            it.links?.about // String
            it.status // String
            it.code // String
            it.title // String
            it.detail // String
            it.source?.pointer // String
            it.source?.parameter // String
            it.meta // String
        }
    }
    is JsonApiResponse.Error.NetworkError -> {
        Log.e(
            TAG,
            "Network error: ",
            response.error // IOException
        )
    }
    is JsonApiResponse.Error.UnknownError -> {
        Log.e(
            TAG,
            "Unknown error: ",
            response.error // Throwable
        )
    }
}

Make the request

Fetch a collection

val response = MainService.build().getArticles(
    params = JsonApiParams(
        include = listOf("author")
    )
)
when (response) {
    is JsonApiResponse.Success -> {
        response.body.data?.forEach {
            it.title // String (e.g., JSON:API paints my bikeshed!)
        }
    }
    else -> TODO()
}

Fetch a resource

val response = MainService.build().getArticle(
    id = id,
    params = JsonApiParams(
        include = listOf("author")
    )
)
when (response) {
    is JsonApiResponse.Success -> {
        response.body.data // Article
        response.body.data?.title // String (e.g., JSON:API paints my bikeshed!)
        response.body.data?.author // People
    }
    else -> TODO()
}

Create a resource

val response = TestApiService.build().createArticle(
    article = Article().also {
        it.title = "test"
        it.author = People(
            id = "2"
        )
    }
)
when (response) {
    is JsonApiResponse.Success -> {
        response.body.data // Article created
    }
    else -> TODO()
}

Contributing

Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.

  1. Fork the project
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a pull request

Author

License

This project is licensed under the Apache-2.0 License - see the LICENSE file for details


© 2021 Lory-Stan TANASI. All rights reserved

retrofit-jsonapi-converter's People

Contributors

stantanasi avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

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.