DocJSON

DocJSON

JSON hypermedia documents - View the project on GitHub

DocJSON is a simple document format for building Hypermedia Web APIs.

A DocJSON document consists of standard JSON with the addition of a set of hypermedia controls that are used to express the actions that may be taken. It is a flexible document format that does not impose any structural restrictions either on the data representation style or on the layout of hypermedia controls used within the document.


Specification

Warning: The DocJSON specification is currently in draft, and is subject to change at any time.

A document is represented using standard JSON, with the additional restriction that the object key "_type" is reserved. Any JSON object containing a key named "_type" is considered a control object. The currently supported control objects are "document" and "link".

The IANA registered media type for DocJSON documents is vnd.document+json. Clients SHOULD send an appropriate Accept header when making a request to a DocJSON API. Typically this header will be "Accept: vnd.document+json".

Document

A document is a control element that acts as a top-level container for the DocJSON representation.

The most minimal valid DocJSON document is simply an empty document object:

{"_type": "document"}

The structure of documents

Typically a document will contain a number of keys. The 'meta' key is reserved for storing certain information about the document. Other keys are used for storing the document content.

An example document:

{
    "_type": "document",
    "meta": {
        "url": "https://example.com/example_api/",
        "title": "An example DocJSON API"
    }
    # ...other keys containing the body of the document
}

The 'meta.url' key is used to represent the base URL for the document. Any relative URLs appearing elsewhere in the document will be treated as relative to this URL. The document may be refreshed at any time by fetching it again using this URL.

The 'meta.title' key is used for a short title containing information about the API. It will typically consist of a single line.

The 'meta.description' key is used for a longer desciption containing information about the API.

Documents indicating failure conditions

In certain conditions a server may need to return an error response. In this case the 'meta' object will contain a single key named 'error'.

An example error document:

{
    "_type": "document"
    "meta": {
        "error": "Permission denied"
    }
}

Link

A link is a control element that represents an action that may be performed by the client.

The following are examples of valid link objects:

# A link which makes a GET request.
{
    '_type': 'link',
    'url': '/user_details/1234'
}

# A link which makes a GET request, using an absolute URL.
{
    '_type': 'link',
    'url': 'http://example.com/user_details/1234'
}

# A link which makes a DELETE request.
{
    '_type': 'link',
    'url': '/user_details/1234',
    'method': 'DELETE'
}

# A link which makes a POST request, including some parameters.
{
    '_type': 'link',
    'url': '/user_details/1234',
    'method': 'POST',
    'fields': [
        {'name': 'email', 'required': True},
        {'name': 'notes'}
    ]
}

Why another Hypermedia format?

None of the existing hypermedia formats meet the particular design goals of DocJSON.

DocJSON is designed with the aim of making developers' lives easier by introducing a flexible data format with a sufficiently complete set of hypermedia controls. By doing so we enable generic client libraries to be used to interact with DocJSON APIs rather than rebuilding client libraries from scratch with each new API service.


Example

The following is an example of a DocJSON document representing a simple API for storing ToDo notes.

{
    "_type": "document", 
    "meta": {
        "url": "http://docjson.herokuapp.com/",
        "title": "DocJSON ToDo API (9 notes)"
    },
    "tabs": {
        "all": {"_type": "link", "url": "/"},
        "complete": {"_type": "link", "url": "/?completed=true"},
        "incomplete": {"_type": "link", "url": "/?completed=false"}
    },
    "create_note": {
        "_type": "link",
        "url": "/",
        "method": "POST",
        "fields": [
            {"name": "text", "required": true},
            {"name": "completed"}
        ]
    },
    "notes": [
        {
            "text": "Call mum",
            "completed": true,
            "edit": {
                "_type": "link",
                "url": "/13/",
                "method": "PUT",
                "fields": [{"name": "text"}, {"name": "completed"}]
            },
            "delete": {
                "_type": "link",
                "url": "/13/",
                "method": "DELETE"
            }
       },
       {
            "text": "Fix the garage lock",
            "completed": true,
            "edit": {
                "_type": "link",
                "url": "/12/",
                "method": "PUT",
                "fields": [{"name": "text"}, {"name": "completed"}]
            },
            "delete": {
                "_type": "link",
                "url": "/12/",
                "method": "DELETE"
            }
       },
       ...
   ]

}

The document presents the API client with the following controls:


Using a DocJSON client

Let's take a look at using a client library for DocJSON, to see what it can do. There's currently a Python implementation. Other languages are planned.

Run pip install docjson, and start python.

bash: pip install docjson
bash: python

Opening the API

To open the API we call docjson.get(). This example API is hosted on Heroku's free tier, so the first request may take a few seconds while the instance starts up.

>>> doc = docjson.get('http://docjson.herokuapp.com')
DocJSON ToDo API (9 notes) - http://docjson.herokuapp.com/

We can now inspect our current list of ToDo notes.

>>> doc.notes
[
    {
        text: 'Call mum',
        completed: False,
        delete(),
        edit([text], [completed])
    },
    {
        text: 'Fix the garage lock',
        completed: False,
        delete(),
        edit([text], [completed])
    },
    ...
]

Editing the ToDo notes

We can add new notes using the create_note link control.

>>> doc.create_note(text='New note')
DocJSON ToDo API (10 notes) - http://docjson.herokuapp.com/

Edit an existing note...

>>> doc.notes[2].edit(completed=True)
DocJSON ToDo API (10 notes) - http://docjson.herokuapp.com/

Or delete an existing note...

>>> doc.notes[3].delete()
DocJSON ToDo API (9 notes) - http://docjson.herokuapp.com/

If we attempt to submit a link with incorrect parameters, the client library will alert us.

>>> doc.create_note()
ValueError: Missing required parameter 'text'
>>> doc.create_note(foobar='New note')
ValueError: Unknown parameter 'foobar'

Similarly if we submit a link that causes the validation on the server to fail, we'll receive an error.

>>> doc.create_note(text='foobar'*100)
docjson.DocumentError: text - Ensure this value has at most 100 characters (it has 600).

Following the tab links

There are also a set of links nested under the 'tabs' object, that we can follow.

>>> doc.tabs
{
    all(),
    complete(),
    incomplete()
}

First let's retrieve a document containing just the completed notes.

>>> doc.tabs.complete()
DocJSON ToDo API (3 complete notes) - http://docjson.herokuapp.com/?completed=true
>>> doc.notes
[
    {
        'text': 'File tax return',
        'completed': True,
        'delete': form(),
        'edit': form([text], [completed])
    }
    ...
]

We can also switch to the 'incomplete' tab to get a document just containing notes that have not been completed.

>>> doc.tabs.incomplete()
DocJSON ToDo API (6 incomplete notes) - http://docjson.herokuapp.com/?completed=false
>>> doc.notes
[
    {
        text: 'Call mum',
        completed: False,
        delete(),
        edit([text], [completed])
    },
    {
        text: 'Fix the garage lock',
        completed: False,
        delete()
        edit([text], [completed]),
    },
    ...
]

And switch back to all the notes again.

>>> doc.tabs.all()
DocJSON ToDo API (9 notes) - http://docjson.herokuapp.com/

Writing DocJSON services

DocJSON is a language independant format, and you should be able to develop DocJSON services in any server-side framework, such as Node, Rails or Django.

The example service used above is developed using Django REST framework. The source code for the example API is available on GitHub.

Why you should be excited

The docjson client we've demonstrated doesn't have any prior knowledge about the server it's communicating with, and yet it's able to present the developer with a complete, ready-to-go library for interacting with the service.

It's simple, discoverable, and the client will always be instantly up to date with any server-side API changes.

DocJSON is appropriate for a very wide range of APIs, as it allows for flexible data representation, and supports a full range of hypermedia controls rather than just hyperlinks, or just CRUD-style interactions.

The future, and what you can do to help

First up, feedback!

If you're interested in DocJSON, I want to hear from you - what do you think works or doesn't work? Would you consider using DocJSON for a service you're building? Would you consider writing a DocJSON client library for your preffered programming language? You can raise issues for discussion on the GitHub issue tracker, or e-mail the Hypermedia mailing list.

Keep in mind that DocJSON is currently in early draft. There are a number of possible future changes and additions to the specification including:

The intent is that the specification should remain relatively simple, but that the hypermedia controls should be sufficiently expressive.

I've also got some ideas around improving the python client library, and would definatly like to see ruby and javascript implmentations as a minimum.

Personally, I'm really excited. I think there's huge potential for a simple, generic hypermedia type such as DocJSON.

Credits: Icon based on document image by Gustavo Cordeiro.