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/JsonapiREADME
JSON:API for Java & Kotlin
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/Nothingfor 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 aListorCollectionof 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
relationshipsmember based on the value of the annotated field and add the value toincludedresources 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
node-connect
340.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
84.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
340.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
84.2kCommit, push, and open a PR
