SkillAgentSearch skills...

Jsonapi

Library for streamlined use of JSON:API using Kotlin and Java built on top of a modern json library Moshi.

Install / Use

/learn @MarkoMilos/Jsonapi
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

JSON:API for Java & Kotlin

Maven Central

Library for streamlined use of JSON:API using Kotlin and Java built on top of a modern json library Moshi.

The library contains both models defined per JSON:API specification and adapters for converting these models to/from json.

About JSON:API

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.

Read more about JSON:API specification here.

Adapters

Usage

Define resources:

@Resource("people")
class Person(
  @Id val id: String?,
  val name: String
)
@Resource("comments")
class Comment(
  @Id val id: String?,
  val body: String,
  @ToOne("author") val author: Person?
)
@Resource("articles")
class Article(
  @Id val id: String?,
  val title: String,
  @ToOne("author") val author: Person?,
  @ToMany("comments") val comments: List<Comment>?
)

Create JsonApiFactory with registered resources:

val factory = JsonApiFactory.Builder()
  .addType(Person::class.java)
  .addType(Comment::class.java)
  .addType(Article::class.java)
  .build()

Alternatively you can use the annotation processor to avoid manually registering each resource (see the section below).

Add created JsonApiFactory as first in factory chain to Moshi:

val moshi = Moshi.Builder()
  .add(factory)
  .build()

Create adapter for Document type:

val adapter = moshi.adapter<Document<Article>>(type)

Deserialization:

// Deserialize document from json
val document = adapter.fromJson("{...}")

// Get primary resource(s) from document
val article = document.dataOrNull()

// Relationship values from included are bound to field
val author = article.author  

Serialization:

// Create resource
val article = Article("1", "They're taking the hobbits to Isengard!", author, comments)

// Create document with resource, also add links and meta
val document = Document.with(article)
  .links(Links("self" to "http://example.com/articles/1"))
  .meta(Meta("copyright" to "Copyright 2015 Example Corp."))
  .build()

// Serialize document to json string
val json = adapter.toJson(document)

Document

Document defines top level entity for JSON:API. It is a generic class accepting type of primary resource e.g. Document<Article>.

Create document with resource

Document.with(resource)
  .included(resource1, resource2)
  .links(links)
  .meta(meta)
  .build()

Configure how included are serialized (by default included are processed from primary resource relationships):

Document.with(resource)
  .includedSerialization(NONE) // Don't serialize included
  .build()

Create error document

Document.from(errors)

Document api

data: T?
included: List<Any>?
errors: List<Error>?
links: Links?
meta: Meta?
jsonapi: JsonApiObject?

hasData(): Boolean
hasErrors(): Boolean
hasMeta(): Boolean

dataOrNull(): T?
dataOrThrow(): T?
requireData(): T
dataOrDefault(data: T) : T
dataOrElse(block: (List<Error>?) -> T): T

errorsOrEmpty(): List<Error>
throwIfErrors()

Allowed document types are one of:

  • single resource (e.g. Article) or collection of resources (e.g. List<Article>)
  • single resource object (ResourceObject) or collection of resource objects (List<ResourceObject>)
  • single resource identifier (ResourceIdentifire) or collection of resource identifiers (List<ResourceIdentifier>)
  • Void/Nothing for empty documents without primary resource such are error documents or meta only documents

Resource relationships

To define related resource fields use ToOne and ToMany annotations.

  • ToOne - defines to-one relationship and should target field of resource type (e.g. Article)
  • ToMany - defines to-many relationship and should target field whose type is a List or Collection of resources ( e.g. List<Article>)

Example:

@Resource("articles")
class Article(
  @ToOne("author") val author: Person?,
  @ToMany("comments") val comments: List<Comment>?
)

Library uses this annotation info during serialization/deserialization to:

  • Deserialization - perform a lookup on resource relationships object for relationships with th given name and, if found, will bind matching resource from included (if any) to target field
  • Serialization - generate proper relationships member based on the value of the annotated field and add the value to included resources if not already there or within primary resources

Resource standard members

Library handles conversion for the following standard resource object members: type, id, lid, relationships, links, and meta.

Member attributes (containing resource data) is converted using a delegate adapter down the chain. Depending on your configuration of Moshi and definition of resource class these could be (in this order):

  • your custom adapter for the given type (if any)
  • codegen adapter (if kotlin codegen module is used)
  • reflection adapter (either for Java or Kotlin)

Resource object members can be bound to a resource with annotations as shown in the example below also showing the full api of this library for defining resources.

@Resource("articles")
class Article(
  // Standard resource object members
  @Type val type: String?,
  @Id val id: String?,
  @Lid val lid: String?,
  @RelationshipsObject val relationships: Relationships?,
  @LinksObject val links: Links?,
  @MetaObject val meta: Meta?,
  
  // Attributes
  var title: String?,
  // ... 
  
  // Relationships
  @ToOne("author") val author: Person?,
  @ToMany("comments") val comments: List<Comment>?
  // ...
)

Annotations

| Annotation | Target element | Target type | Description | |-----------------------|-------------------|-----------------------------------------------|------------------------------------------------------------------------------| | @Resource | class | - | Defines resource class | | @Type | field or property | String | Binds type member to field. For serialization value from this field will be used for type member. If this is not defined value from @Resource annotation is used for type member. | | @Id | field or property | String | Bind id member to field. For serialization id or lid is required. | | @Lid | field or property | String | Bind lid member to field. For serialization id or lid is required. | | @RelationshipObject | field or property | Relationships | Bind values from relationships member to field. For serialization relationships defined with this field will override ones generated from ToOne and ToMany field values. | | @LinksObject | field or property | Links | Bind links member to field. | | @MetaObject | field or property | Meta | Bind meta member to field. | | @ToOne | field or property | @Resource class | Bind relationship from/to document included member. For serialization value for relationships member is generated for this field. | | @ToMany | field or property | Collection or List of @Resource classes | Bind relationship from/to document included member. For serialization value for relationships member is generated for this field. |

Custom adapters

You can register a custom adapter for a resource type. Make sure to register adapter after JsonApiFactory since Moshi respects registration order.

Moshi.Builder()
  .add(factory)
  .add(MyCustomArticleAdapter())
  .build()

For deserialization library will delegate attributes object conversion to the adapter down the chain meaning that the registered adapter will receive only attributes json - e.g. for the following resource

{
  "type": "articles",
  "id": "1",
  "attributes": {
    "title": "Some Title",
    "slug": "slug",
    "likes": 10
  }
}

custom adapter receives the following attributes object for conversion

{
  "title": "Some Title",
  "slug": "slug",
  "likes": 10
}

For serialization library will delegate resource value to the registered custom adapter. The custom adapter should serialize only values relevant for attributes object since all other members are handled by the library.

Download

Download the latest JAR or depend via Maven:

<dependency>
    <groupId>com.markomilos.jsonapi</groupId>
    <artifactId>jsonapi-adapters</artifactId>
    <version>1.1.0</version>
</dependency>

or Gradle:

implementation("com.markomilos.jsonapi:jsonapi-adapters:1.1.0")

Annotation processor

Document included members is defined as an array of resource objects that are related to the primary data and/or each other (“included resources”). Library will try to deserialize each resource form that array to correct type based on resource object type member. In order for library to know which class should be deserialized all resources are required to be regi

Related Skills

View on GitHub
GitHub Stars35
CategoryDevelopment
Updated6mo ago
Forks6

Languages

Kotlin

Security Score

87/100

Audited on Sep 17, 2025

No findings