Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jasminb/jsonapi-converter
JSONAPI-Converter is a Java/Android library that provides support for working with JSONAPI spec
https://github.com/jasminb/jsonapi-converter
Last synced: 2 months ago
JSON representation
JSONAPI-Converter is a Java/Android library that provides support for working with JSONAPI spec
- Host: GitHub
- URL: https://github.com/jasminb/jsonapi-converter
- Owner: jasminb
- License: apache-2.0
- Created: 2016-02-18T17:03:38.000Z (almost 9 years ago)
- Default Branch: develop
- Last Pushed: 2024-07-31T21:12:18.000Z (6 months ago)
- Last Synced: 2024-08-01T21:47:24.570Z (5 months ago)
- Language: Java
- Homepage:
- Size: 374 KB
- Stars: 273
- Watchers: 20
- Forks: 81
- Open Issues: 59
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-android - JSONAPI-Converter - JSONAPI-Converter is a Java/Android library that provides support for working with JSONAPI spec. (Uncategorized / Uncategorized)
README
### jsonapi-converter
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/jbegic)
JSONAPI-Converter is a library that provides means for integrating with services using JSON API specification.
For information on JSON API specification please see: http://jsonapi.org/format/
Besides providing support for request/response parsing, library provides a retrofit plugin.
Library is using Jackson (https://github.com/FasterXML/jackson-databind) for JSON data parsing.
#### Including the library in your project
Maven:
```xml
com.github.jasminb
jsonapi-converter
0.14```
Gradle:
```groovy
implementation 'com.github.jasminb:jsonapi-converter:0.14'
```SBT:
```groovy
libraryDependencies += "com.github.jasminb" % "jsonapi-converter" % "0.14"
```In case you want to use current `SNAPSHOT` version of the project, make sure to add sonatype repository to your pom:
```xml
oss-sonatype
oss-sonatype
https://oss.sonatype.org/content/repositories/snapshots/
true
```
Than to add dependency:
```xml
com.github.jasminb
jsonapi-converter
0.15-SNAPSHOT```
#### Important retrofit note
Retrofit is used as scoped dependency (`provided`) which means that if one needs retrofit capabilities, retrofit needs
to be included as a separate explicit dependency (it won't be pulled transitively).#### Writing your model classes
When writing models that will be used to represent requests and responses, one needs to pay attention to following:
- Each model class must be annotated with `com.github.jasminb.jsonapi.annotations.Type` annotation
- Each class must contain an `String` attribute annotated with `com.github.jasminb.jsonapi.annotations.Id` annotation
- All relationships must be annotated with `com.github.jasminb.jsonapi.annotations.Relationship` annotation
- Attributes in the API that are _not_ well-formed Java identifiers, must use `JsonProperty` annotation#### Type annotation
Type annotation is used to instruct the serialisation/deserialisation library on how to process the given model class.
Annotation has single property `value` which is required and it should be set to whatever is the designated JSON API SPEC name for that type.Example:
```java
@Type("book")
public class Book {
...
}
```Note that `@Type` annotation is not inherited from supperclasses.
#### Id annotation
Id annotation is used to flag an attribute of a class as an `id` attribute. Each resource class must have an id field.
In case field annotated by the `@Id` annotation is not a `String` field, `@Id` annotation needs to be configured with proper `ResourceIdHandler`.
Library provides handlers for `Long` and `Integer` types, in case types other than those mentioned are used, user must implement and provide proper id handler.Id is a special attribute that is, together with type, used to uniquely identify a resource.
Id annotation is inheritable, one can define a base model class that contains a field with `@Id` annotation and then extend it to create a new type.
Example:
```java
@Type("book")
public class Book {
@Id
private String isbn;
...
}
```Example with inheritance:
```java
public class BaseModel {
@Id
private String id;
}@Type("book")
public class Book extends BaseModel {
# Your custom member variables
}
```Example using `Long` as id
```java
@Type("book")
public class Book {
@Id(LongIdHandler.class)
private Long id;
...
}```
#### LocalId annotation
This annotation is used to mark a class attribute as a holder of the `local identifier`.
Local identifiers may be used in cases where requests are originating on the client side. This annotation is optional
and behaves in a similar way to the `@Id` annotation. It must be represented as a string in serialized form
which means that for non-string class attributes, handler must be registered to convert from and to string type on
serialization/deserialization.Example:
```java
@Type("book")
public class Book {
@Id
private String isbn;
@LocalId
private String localId;
}
```By default, library will not serialize local id attribute, its serialization needs to be enabled explicitly:
```java
SerializationSettings settings = new SerializationSettings.Builder()
.serializeLocalId(true)
.build();// Using the settings on serialization call
converter.writeDocument(document, settings);```
It can also be enabled globally using the `SerializationFeature` options:
```java
converter.enableSerializationOption(SerializationFeature.INCLUDE_LOCAL_ID);
```For deserialization, `lid` attribute is optional by default, it can be made required by `DeserializationFeature`
mechanism:```java
converter.enableDeserializationOption(DeserializationFeature.REQUIRE_LOCAL_RESOURCE_ID);
```Setting the option above means that deserialization will throw in case resource does not contain non-empty `lid` attribute.
###### Important note
By default `REQUIRE_RESOURCE_ID` deserialization feature is enabled, which means that for server-side usage,
where users want to use the `lid` mechanism, this option should be disabled:```java
converter.disableDeserializationOption(DeserializationFeature.REQUIRE_RESOURCE_ID);
```#### Relationship annotation
Relationship annotation is used to designate other resource types as a relationships.
Imagine modeling a simple library application, you would end up having a `Book` resource and another logical resource would be `Author`.
You can model this as two different classes where `Book` resource would have a relationship to an `Author`:
```java
@Type("book")
public class Book {
@Id
private String isbn;
private String title;
@Relationship("author")
private Author author;
}
```Relationship annotation has following attributes:
- value
- resolve
- serialise
- relType
Value attribute is required and each relationship must have it set (value attribute represents the 'name' of the relationship).Resolve attribute is used to instruct the library on how to handle server responses where resource relationships are not provided in `included` section but are rather returned as `type` and `id` combination.
Library has a support for registering global and typed relationship resolves which are used to resolve unresolved relationships.
Resolving a relationship means using provided `links` attribute to perform additional `HTTP` request and get the related object using the link provided.Relationship resolver interface has a single method:
```java
byte [] resolve(String relationshipURL);
````After implementing relationship resolver, in order to use it, one must register it with the instance of the `ResourceConverter`.
Example:
```java
ResourceConverter converter = new ResourceConverter(Book.class, Author.class);
converter.setGlobalResolver(new CustomRelationshipResolverInstance());
```Besides support for global resolvers, there is an option to have different resolvers for different resource types:
```java
ResourceConverter converter = new ResourceConverter(Book.class, Author.class);
converter.setTypeResolver(new CustomBooksResolver(), Book.class);
converter.setTypeResolver(new CustomAuthorResolver(), Author.class);```
Serialise attribute is used to instruct the serialisar whether to include or exclude given relationship when serialising resources.
I is enabled by default, if disabled relationship will not be serialised.Relationship type (`relType`) is used to instruct the library on how to resolve link data from raw server responses in order to
resolve given relationship.There two different relationship types:
- `SELF` (`self` link will be followed to resolve relationship
- `RELATED` (`related` link will be followed)
Have in mind that relationship (same as id) is inheritable and can be defined in a base class.#### Polymorphic Relationships
In order to support polymorphic relationships, an `interface` needs to be created and than implemented by all possible types relationship supports. Created interface is used as a relationship's type (see example below).
Example response containing multiple types in a relationship:
```json
{
"data": {
"type": "dealerships",
"id": "1",
"attributes": {
"name": "Dealership name"
},
"links": {
"self": "http://example.com/dealerships/1"
},
"relationships": {
"inventory": {
"links": {
"self": "http://example.com/dealerships/1/relationships/inventory",
"related": "http://example.com/dealerships/1/inventory"
},
"data": [
{ "type": "cars", "id": "2" },
{ "type": "trucks", "id": "1" }
]
}
}
},
"included": [{
"type": "cars",
"id": "2",
"attributes": {
"make": "BMW",
"model": "i8 Roadster"
},
"links": {
"self": "http://example.com/cars/2"
}
}, {
"type": "trucks",
"id": "1",
"attributes": {
"make": "Ford",
"model": "Semi"
},
"links": {
"self": "http://example.com/trucks/1"
}
}]
}
```Needed classes, and example usage:
```java
public interface Driveable {}@Type("cars")
public class Car implements Driveable {
@Id
private String id;
private String model;
private String make;
// Getters and setters...
}@Type("trucks")
public class Truck implements Driveable {@Id
private String id;
private String make;
private String model;
// Getters and setters...
}@Type("dealerships")
public class Dealership {
@Id
private String id;
private String name;
private String city;// Interface is used as relalationship type (instead of concrete resource type)
@Relationship("inventory")
private Collection automobiles;
}// Putting everything together
ResourceConverter converter = new ResourceConverter("https://api.example.com", Car.class, Dealership.class, Truck.class);
JSONAPIDocument document = converter.readDocument(apiResponse, Dealership.class);```
#### Relationship meta and links
jsonapi-spec allows for having relationship-level metadata and links.
In order to gain access to returned relationship meta and links or ability to serialize it, use following annotations:
- `RelationshipMeta`
- `RelationshipLinks`
Here is a version of the `Book` class with relationship meta/links added:
```java
@Type("book")
public class Book {
@Id
private String isbn;
private String title;
@Relationship("author")
private Author author;
@RelationshipMeta("author")
private Meta authorMeta
@RelationshipLinks("author")
private Links authorLinks
}
```Make sure not to confuse relationship meta and links with regular meta-data and link data explained below.
#### Meta annotation
By JSON API specification, each resource can hold `meta` attribute. Meta can be arbitrary object that is defined by the API implementation.
In order to map and make meta available trough resource conversion, one must create a model that coresponds to the meta object returned by the API, create a member variable in the resource class using created model and annotate it using the `@Meta` annotation.
Meta example:
```java
# Meta model classpublic class MyCustomMetaClass {
private String myAttribute;
public String getMyAttribute() {
return myAttribute;
}
public void setMyAttribute(String value) {
this.myAttribute = value;
}
}# Resource class with meta attribute
@Type("book")
public class Book {
@Id
private String isbn;
private String title;
@Relationship("author")
private Author author;
@Meta
private MyCustomMetaClass meta;
}```
Meta annotation/attriubutes are inheritable.
#### Links annotation
JSON API specification allows for `links` to be part of resources. Links usually cary information about the resource itself (eg. its URI on the server).
Liks are not arbitray objects, JSON API spec provides links structure therefore it is not required to create a new model to make links object available.
Library provides a `com.github.jasminb.jsonapi.Links` class that must be used in order to make links data available in resources.
Example:
```java
@Type("book")
public class Book {
@Id
private String isbn;
private String title;
@Relationship("author")
private Author author;
@Meta
private MyCustomMetaClass meta;
@Links
private com.github.jasminb.jsonapi.Links links;
}
```Links are inheritable.
#### Attribute annotations
If your JSON API endpoint returns attributes that do not map well to Java identifiers, you'll get a fatal error on deserialization. The log message will tell you
about an unrecognized field with that name. To fix it, you can use `com.fasterxml.jackson.annotation.JsonProperty`.Example: your JSON API endpoint returns something like this
```json
{
"data": [
{
"id": "1",
"type": "gears",
"attributes": {
"tooth-count": 13,
"tooth-depth": 21
}
}
]
}
```then your model must be:
```java
import com.fasterxml.jackson.annotation.JsonProperty;
@Type("gears")
public class Gear {
@Id
private String id;@JsonProperty("tooth-count")
private Long toothCount;
@JsonProperty("tooth-depth")
private Long toothDepth;
}
```This also lets you use different names for your fields than your API.
#### Full example
Define simple POJO, please pay attention to the added annotations:
```java
# Meta is optional, one does not have to define or use it
public class Meta {
private String myAttribute;
public String getMyAttribute() {
return myAttribute;
}
public void setMyAttribute(String value) {
this.myAttribute = value;
}
}# Creating base class is optional but allows for writing more compact model classes
public class BaseResource {
@Id
private String id;
@Meta
private Meta meta;
@Links
private Links links;
}@Type("book")
public class Book extends BaseResource {
private String title;
@Relationship("author")
private Author author;
@RelationshipMeta("author")
private Meta authorMeta
@RelationshipLinks("author")
private Links authorLinks
# getters and setters
}@Type("author")
public class Author extends BaseResource {
private String name;
@Relationship("books")
private List books;
# getters and setters
}
```Create a converter instance:
```java
ResourceConverter converter = new ResourceConverter(Book.class, Author.class);// Get response data
byte [] rawResponse = ...get data from the wire// To convert raw data into single POJO
JSONAPIDocument bookDocument = converter.readDocument(rawResponse, Book.class);
Book book = bookDocument.get();// To convert raw data into collection
JSONAPIDocument> bookDocumentCollection = converter.readDocumentCollection(rawResponse, Book.class);
List bookCollection = bookDocumentCollection.get();```
Note that calling `readDocument(...)` or `readDocumentCollection(...)` using content that contains `errors` (`{"errors" : [{...}]}`) attribute will produce `ResourceParseException`.
Thrown exception has a method (`getErrorResponse()`) that returns parsed `errors` content. Errors content is expected to comply to JSON API Spec.
#### Top level links and meta
Besides having links and meta information on resource level, by JSON API spec it is also possible to have meta, links or both as top level objects in server responses.
To gain access to top level meta/links, this library provides convenience methods available in `JSONAPIDocument`, namely:
- `getMeta()`
- `getLinks()`
#### Resource serializationBesides providing options to deserialize json-api spec complaint resource representation, library also includes support for serializing resources.
Following are available serialization options that can be enabled/disabled on `ResourceConverter` instance:
- `INCLUDE_META` enabled by default, if enabled, meta data will be serialized
- `INCLUDE_LINKS` enabled by default, if enabled links will be serialized
- `INCLUDE_RELATIONSHIP_ATTRIBUTES` disabled by default, if enabled, relationship objects will be serialized fully, this means that besides generating `relationship` objects for each relationship, `included` section will be created that contains actual relationship attributesTo enable or disable serialization options:
```java
ResourceConverter converter = ...
# Enable generating included section
converter.enableSerializationOption(SerializationFeature.INCLUDE_RELATIONSHIP_ATTRIBUTES);# Disable generating included section
converter.disableSerializationOption(SerializationFeature.INCLUDE_RELATIONSHIP_ATTRIBUTES);```
Example with `INCLUDE_RELATIONSHIP_ATTRIBUTES` disabled:
```json
{
"data": {
"type": "articles",
"id": "id",
"attributes": {
"title": "title"
},
"relationships": {
"author": {
"data": {
"type": "people",
"id": "id"
}
}
}
}
}
```Example with `INCLUDE_RELATIONSHIP_ATTRIBUTES` enabled:
```json
{
"data": {
"type": "articles",
"id": "id",
"attributes": {
"title": "title"
},
"relationships": {
"author": {
"data": {
"type": "people",
"id": "id"
}
}
}
},
"included": [
{
"type": "people",
"id": "id",
"attributes": {
"firstName": "John"
}
}
]
}
```#### Example usage with retrofit
As a first step, define your model classes and annotate them using annotations described above.
After defining models, define your service interfaces as you would usually do with 'standard' JSON/XML APIs.
To create retrofit instance:
```java
// Create object mapper
ObjectMapper objectMapper = new ObjectMapper();// Set serialisation/deserialisation options if needed (property naming strategy, etc...)
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://yourapi")
.addConverterFactory(new JSONAPIConverterFactory(objectMapper, Book.class, Author.class))
.build();
// Create service using service interfaceMyBooksService booksService = retrofit.create(MyBooksService.class);
```
###### Synchronous usage
```java
Response> bookResponse = booksService.find("123").execute();if (bookResponse.isSuccess()) {
// Consume response
} else {
ErrorResponse errorResponse = ErrorUtils.parseErrorResponse(bookResponse.errorBody());
// Handle error
}
```###### Asynchronous usage
```java
Call> bookServiceCall = service.getExampleResource();bookServiceCall.enqueue(new Callback() {
@Override
public void onResponse(Response> bookResponse, Retrofit retrofit) {
if (bookResponse.isSuccess()) {
// Consume response
} else {
ErrorResponse errorResponse = ErrorUtils.parseErrorResponse(bookResponse.errorBody());
// Handle error
}
}
@Override
public void onFailure(Throwable throwable) {
// Handle network errors/unexpected errors
}
});
```Notice that expected return types in `MyBookService` calls are all wrapped with `JSONAPIDocument`, this is intended way to use the library since it allows for gaining access to response level `meta` and `links` data.
Example service interface:
```java
public interface MyBooksService {
@GET("books")
Call> allBooks();
}```
#### Tips
If you need a `String` as an output when serializing objects, you can do the following:
```java
byte [] serializedObject = resourceConverter.writeObject(...);
String serializedAsString = new String(serializedObject);
```#### Note for kotlin users
Have in mind that using `open` classes as type parameters in relationship collections will not work, for instance:
```java
@Type("base")
open class MyClass {@Relationship("my-relationship")
var bases: List? = null
}
```Removing the `open` modifier will solve the issue.
#### Note for Proguard
This library use reflection at runtime for checking if all registred types have an ID-field.
You have to add this proguard rule:
```
# Keep jsonapi-converter relative fields
-keepclassmembers class * {
@com.github.jasminb.jsonapi.annotations.Id ;
}
```Proguard should remove all fields and methods that you are not accessing. If you would like to keep all deserialized fields, you can add a rule for unconditionally keep all your POJO classes.
```
# Keep all POJO classes
-keep class com.example.json.** {*;}
```
Replace `com.example.json` by the correct package.```
# Keep custom id handlers
-keep class * implements com.github.jasminb.jsonapi.ResourceIdHandler
```