majic-parser v0.0.8
:sparkles: MAJIC :sparkles:
Module Architecture for JsonAPI Ingesting Consumers
This tool makes building front-end JavaScript applications against JsonAPI backends easier.
Installation
Yarn
$ yarn add majic-parsernpm
$ npm install --save majic-parserUsage
In practice, we've used this sitting in the api-layer of an application, abstracting away the need to know about the JsonAPI implementation in the application. We've seen it as an elegant way to uniformly handle and store data delivered via JsonAPI.
In these examples, we're using isomorphic-fetch as a stand in for the native browesr fetch.
Parsing a JsonAPI Response
import fetch from 'isomorphic-fetch';
import {parseResponse} from 'majic-parser';
function getArticle(articleId) {
return fetch(`http://example.com/articles/${articleId}`, {
method: 'GET',
headers: {
'content-type': 'application/vnd.api+json'
}
})
.then(response => response.json())
.then(parseResponse);
}
getArticle('1')
.then(response => console.log(JSON.stringify(response, null, 4)));This takes the below JsonAPI object
{
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "resource-linkage"
},
"meta": {
"revisionNumber": 0
},
"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"
}
}
]
}and turns it in to
{
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "resource-linkage"
},
"meta": {
"revisionNumber": 0
},
"__primaryEntities": ["articles"],
"articles": {
"keys": ["1"],
"data": {
"1": {
"type": "articles",
"id": "1",
"title": "JSON API paints my bikeshed!",
"links": {
"self": "http://example.com/articles/1"
},
"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"
}
]
}
}
}
},
"people": {
"data": {
"9": {
"type": "people",
"id": "9",
"first-name": "Dan",
"last-name": "Gebhardt",
"twitter": "dgeb",
"links": {
"self": "http://example.com/people/9"
}
}
}
},
"comments": {
"data": {
"5": {
"type": "comments",
"id": "5",
"body": "First!",
"author": {
"data": {
"type": "people",
"id": "2"
}
},
"links": {
"self": "http://example.com/comments/5"
}
},
"12": {
"type": "comments",
"id": "12",
"body": "I like XML better",
"author": {
"data": {
"type": "people",
"id": "9"
}
},
"links": {
"self": "http://example.com/comments/12"
}
}
}
}
}Converting a Majic Data Object in to a JsonAPI Request
import fetch from 'isomorphic-fetch';
import {composeRequest} from 'majic-parser';
const articleSchema = {
"type": "articles",
"attributes": ["title"],
"topLevelMeta": ["requestId"],
"meta": ["revisionNumber"],
"relationships": [
{
"key": "author",
"defaultType": "people"
},
{
"key": "comments",
"defaultType": "comments"
}
],
"included": [
{
"key": "author",
"attributes": ["first-name", "last-name", "twitter"]
},
{
"key": "comments",
"attributes": ["body"],
"relationships": [
{
"key": "author",
"defaultType": "people"
}
]
}
]
};
const article = {
"type": "articles",
"id": "1",
"title": "JSON API paints my bikeshed!",
"revisionNumber": 1,
"requestId": 42,
"links": {
"self": "http://example.com/articles/1"
},
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
},
"data": {
"type": "people",
"id": "9",
"first-name": "Dan",
"last-name": "Gebhardt",
"twitter": "dgeb",
"links": {
"self": "http://example.com/people/9"
}
}
},
"comments": {
"links": {
"self": "http://example.com/articles/1/relationships/comments",
"related": "http://example.com/articles/1/comments"
},
"data": [
{
"type": "comments",
"id": "5",
"body": "First!",
"author": {
"data": {
"type": "people",
"id": "2"
}
},
"links": {
"self": "http://example.com/comments/5"
}
},
{
"type": "comments",
"id": "12",
"body": "I like XML better",
"author": {
"data": {
"type": "people",
"id": "9"
}
},
"links": {
"self": "http://example.com/comments/12"
}
}
]
}
};
function putArticle(article) {
return fetch(`http://example.com/articles/${article.id}`, {
method: 'PUT',
body: JSON.stringify(composeRequest(article, articleSchema)),
headers: {
'content-type': 'application/vnd.api+json'
}
});
}
putArticle(article);takes the above Majic representation of an article (with its related entites expanded) and turns it in to
{
"meta": {
"requestId": 42
},
"data": [
{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!"
},
"relationships": {
"author": {
"data": {
"type": "people",
"id": "9"
}
},
"comments": {
"data": [
{
"type": "comments",
"id": "5"
},
{
"type": "comments",
"id": "12"
}
]
}
},
"meta": {
"revisionNumber": 1
}
}
],
"included": [
{
"type": "people",
"id": "9",
"attributes": {
"first-name": "Dan",
"last-name": "Gebhardt",
"twitter": "dgeb"
}
},
{
"type": "comments",
"id": "5",
"attributes": {
"body": "First!"
},
"relationships": {
"author": {
"data": {
"type": "people",
"id": "2"
}
}
}
},
{
"type": "comments",
"id": "12",
"attributes": {
"body": "I like XML better"
},
"relationships": {
"author": {
"data": {
"type": "people",
"id": "9"
}
}
}
}
]
}Advanced
If you need to do something that involves receiving and tracking entites with non-unique ids (i.e., tracking multiple revisions of the same entity), we provide a parseResponseFactory that accepts an identifier function that accepts an entity and returns the key used to identify the distinct entities.
Usage
For example, if you have article type entities that have revisionNumbers on their meta fields, we could use the below identity function
import fetch from 'isomorphic-fetch';
import {parseResponseFactory} from 'majic-parser';
function revisionNumberIdentifier(entity) {
if (entity.type === 'articles') {
return `${entity.id}${('meta' in entity && 'revisionNumber' in entity.meta)? `:${entity.meta.revisionNumber}`: ''}`;
}
return entity.id;
}
const articleParser = parseResponseFactory(revisionNumberIdentifier);
function getArticle(articleId) {
return fetch(`http://example.com/articles/${articleId}`, {
method: 'GET',
headers: {
'content-type': 'application/vnd.api+json'
}
})
.then(response => response.json())
.then(articleParser);
}
getArticle('1')
.then(response => console.log(JSON.stringify(response, null, 4)));which would parse the below JsonAPI Resposne
{
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "resource-linkage"
},
"data": [
{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed! Boom!"
},
"links": {
"self": "http://example.com/articles/1"
},
"meta": {
"revisionNumber": 1
}
}
],
"included": [
{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!"
},
"links": {
"self": "http://example.com/articles/1"
},
"meta": {
"revisionNumber": 0
}
}
]
}into the below object. Note the identifiers in the data object and keys array.
{
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "resource-linkage"
},
"__primaryEntities": ["articles"],
"articles": {
"keys": ["1:1"],
"data": {
"1:1": {
"type": "articles",
"id": "1",
"title": "JSON API paints my bikeshed! Boom!",
"links": {
"self": "http://example.com/articles/1"
},
"revisionNumber": 1
},
"1:0": {
"type": "articles",
"id": "1",
"title": "JSON API paints my bikeshed!",
"links": {
"self": "http://example.com/articles/1"
},
"revisionNumber": 0
}
}
}
}Thanks
Thank you to the people behind JsonAPI for the hard work of defining the schema and building the awesome documentation. Also, thank you for the examples you provide in the documentation, as you made building test cases so much easier!
License
MIT