#837 - Add more media types.

Introduce `src/docs/resources` and add `{resource-dir}` as an asciidoctor variable.

Add content for HAL-FORMS, Collection+JSON, and UBER.
This commit is contained in:
Greg Turnquist
2019-03-03 09:04:44 -06:00
parent 37ef8e9960
commit 3069543b8f
22 changed files with 814 additions and 66 deletions

13
pom.xml
View File

@@ -280,6 +280,7 @@
<sectids>false</sectids>
<attributes>
<code-dir>${basedir}/src/docs/java/org/springframework/hateoas</code-dir>
<resource-dir>${basedir}/src/docs/resources/org/springframework/hateoas</resource-dir>
<docinfo>shared</docinfo>
<linkcss>true</linkcss>
<icons>font</icons>
@@ -698,6 +699,18 @@
</sources>
</configuration>
</execution>
<execution>
<id>add-docs-resources</id>
<phase>generate-test-resources</phase>
<goals>
<goal>add-test-resource</goal>
</goals>
<configuration>
<resources>
<resource>src/docs/resources</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.hateoas;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.config.EnableHypermediaSupport;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
/**
* @author Greg Turnquist
*/
// tag::code[]
@Configuration
@EnableHypermediaSupport(type = HypermediaType.COLLECTION_JSON)
public class CollectionJsonApplication {
}
// end::code[]

View File

@@ -117,10 +117,10 @@ public class EmployeeController {
// Return the affordance + a link back to the entire collection resource.
return new EntityModel<>(EMPLOYEES.get(id), //
findOneLink //
.andAffordance(afford(methodOn(controllerClass) //
.updateEmployee(null, id))) // <2>
.andAffordance(afford(methodOn(controllerClass) //
.partiallyUpdateEmployee(null, id)))); // <3>
.andAffordance( //
afford(methodOn(controllerClass).updateEmployee(null, id))) // <2>
.andAffordance( //
afford(methodOn(controllerClass).partiallyUpdateEmployee(null, id)))); // <3>
}
// end::get[]

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.hateoas;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.config.EnableHypermediaSupport;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
/**
* @author Greg Turnquist
*/
// tag::code[]
@Configuration
@EnableHypermediaSupport(type = HypermediaType.HAL_FORMS)
public class HalFormsApplication {
}
// end::code[]

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.hateoas;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.mediatype.hal.HalConfiguration;
import org.springframework.hateoas.mediatype.hal.HalConfiguration.RenderSingleLinks;
/**
* @author Greg Turnquist
*/
@Configuration
public class SampleAppConfiguration {
// tag::1[]
@Bean
public HalConfiguration globalPolicy() {
return new HalConfiguration() //
.withRenderSingleLinks(RenderSingleLinks.AS_ARRAY); // <1>
}
// end::1[]
// tag::2[]
@Bean
public HalConfiguration linkRelationBasedPolicy() {
return new HalConfiguration() //
.withRenderSingleLinksFor( //
IanaLinkRelations.ITEM, RenderSingleLinks.AS_ARRAY) // <1>
.withRenderSingleLinksFor( //
LinkRelation.of("prev"), RenderSingleLinks.AS_SINGLE); // <2>
}
// end::2[]
// tag::3[]
@Bean
public HalConfiguration patternBasedPolicy() {
return new HalConfiguration() //
.withRenderSingleLinksFor( //
"http*", RenderSingleLinks.AS_ARRAY); // <1>
}
// end::3[]
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.hateoas;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.config.EnableHypermediaSupport;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
/**
* @author Greg Turnquist
*/
// tag::code[]
@Configuration
@EnableHypermediaSupport(type = HypermediaType.UBER)
public class UberApplication {
}
// end::code[]

View File

@@ -0,0 +1,6 @@
{
"collection": {
"version": "1.0",
"href": "http://example.org/friends/"
}
}

View File

@@ -0,0 +1,135 @@
{
"collection": {
"version": "1.0",
"href": "http://example.org/friends/",
"links": [
{
"rel": "feed",
"href": "http://example.org/friends/rss"
}
],
"items": [
{
"href": "http://example.org/friends/jdoe",
"data": [
{
"name": "fullname",
"value": "J. Doe",
"prompt": "Full Name"
},
{
"name": "email",
"value": "jdoe@example.org",
"prompt": "Email"
}
],
"links": [
{
"rel": "blog",
"href": "http://examples.org/blogs/jdoe",
"prompt": "Blog"
},
{
"rel": "avatar",
"href": "http://examples.org/images/jdoe",
"prompt": "Avatar",
"render": "image"
}
]
},
{
"href": "http://example.org/friends/msmith",
"data": [
{
"name": "fullname",
"value": "M. Smith",
"prompt": "Full Name"
},
{
"name": "email",
"value": "msmith@example.org",
"prompt": "Email"
}
],
"links": [
{
"rel": "blog",
"href": "http://examples.org/blogs/msmith",
"prompt": "Blog"
},
{
"rel": "avatar",
"href": "http://examples.org/images/msmith",
"prompt": "Avatar",
"render": "image"
}
]
},
{
"href": "http://example.org/friends/rwilliams",
"data": [
{
"name": "fullname",
"value": "R. Williams",
"prompt": "Full Name"
},
{
"name": "email",
"value": "rwilliams@example.org",
"prompt": "Email"
}
],
"links": [
{
"rel": "blog",
"href": "http://examples.org/blogs/rwilliams",
"prompt": "Blog"
},
{
"rel": "avatar",
"href": "http://examples.org/images/rwilliams",
"prompt": "Avatar",
"render": "image"
}
]
}
],
"queries": [
{
"rel": "search",
"href": "http://example.org/friends/search",
"prompt": "Search",
"data": [
{
"name": "search",
"value": ""
}
]
}
],
"template": {
"data": [
{
"name": "fullname",
"value": "",
"prompt": "Full Name"
},
{
"name": "email",
"value": "",
"prompt": "Email"
},
{
"name": "blog",
"value": "",
"prompt": "Blog"
},
{
"name": "avatar",
"value": "",
"prompt": "Avatar"
}
]
}
}
}

View File

@@ -0,0 +1,50 @@
{
"collection": {
"version": "1.0",
"href": "http://example.org/friends/", // <1>
"links": [ // <2>
{
"rel": "feed",
"href": "http://example.org/friends/rss"
},
{
"rel": "queries",
"href": "http://example.org/friends/?queries"
},
{
"rel": "template",
"href": "http://example.org/friends/?template"
}
],
"items": [ // <3>
{
"href": "http://example.org/friends/jdoe",
"data": [ // <4>
{
"name": "fullname",
"value": "J. Doe",
"prompt": "Full Name"
},
{
"name": "email",
"value": "jdoe@example.org",
"prompt": "Email"
}
],
"links": [ // <5>
{
"rel": "blog",
"href": "http://examples.org/blogs/jdoe",
"prompt": "Blog"
},
{
"rel": "avatar",
"href": "http://examples.org/images/jdoe",
"prompt": "Avatar",
"render": "image"
}
]
}
]
}
}

View File

@@ -0,0 +1,19 @@
{
"collection": {
"version": "1.0",
"href": "http://example.org/friends/",
"queries": [
{
"rel": "search",
"href": "http://example.org/friends/search",
"prompt": "Search",
"data": [
{
"name": "search",
"value": ""
}
]
}
]
}
}

View File

@@ -0,0 +1,30 @@
{
"collection": {
"version": "1.0",
"href": "http://example.org/friends/",
"template": {
"data": [
{
"name": "full-name",
"value": "",
"prompt": "Full Name"
},
{
"name": "email",
"value": "",
"prompt": "Email"
},
{
"name": "blog",
"value": "",
"prompt": "Blog"
},
{
"name": "avatar",
"value": "",
"prompt": "Avatar"
}
]
}
}
}

View File

@@ -0,0 +1,11 @@
{
"collection": {
"version": "1.0",
"href": "http://example.org/friends/",
"error": {
"title": "Server Error",
"code": "X1C2",
"message": "The server have encountered an error, please wait and try again."
}
}
}

View File

@@ -0,0 +1,22 @@
{
"template": {
"data": [
{
"name": "full-name",
"value": "W. Chandry"
},
{
"name": "email",
"value": "wchandry@example.org"
},
{
"name": "blog",
"value": "http://example.org/blogs/wchandry"
},
{
"name": "avatar",
"value": "http://example.org/images/wchandry"
}
]
}
}

View File

@@ -0,0 +1,42 @@
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"role" : "ring bearer",
"_links" : {
"self" : {
"href" : "http://localhost:8080/employees/1"
}
},
"_templates" : {
"default" : {
"title" : null,
"method" : "put",
"contentType" : "",
"properties" : [ {
"name" : "firstName",
"required" : true
}, {
"name" : "lastName",
"required" : true
}, {
"name" : "role",
"required" : true
} ]
},
"partiallyUpdateEmployee" : {
"title" : null,
"method" : "patch",
"contentType" : "",
"properties" : [ {
"name" : "firstName",
"required" : false
}, {
"name" : "lastName",
"required" : false
}, {
"name" : "role",
"required" : false
} ]
}
}
}

View File

@@ -0,0 +1,42 @@
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"role" : "ring bearer",
"_links" : {
"self" : {
"href" : "http://localhost:8080/employees/1"
}
},
"_templates" : {
"default" : {
"title" : null,
"method" : "put",
"contentType" : "",
"properties" : [ {
"name" : "firstName",
"required" : true
}, {
"name" : "lastName",
"required" : true
}, {
"name" : "role",
"required" : true
} ]
},
"partiallyUpdateEmployee" : {
"title" : null,
"method" : "patch",
"contentType" : "",
"properties" : [ {
"name" : "firstName",
"required" : false
}, {
"name" : "lastName",
"required" : false
}, {
"name" : "role",
"required" : false
} ]
}
}
}

View File

@@ -0,0 +1,9 @@
{
"_links": {
"item": [
{ "href": "http://myhost/cart/42" },
{ "href": "http://myhost/inventory/12" }
]
},
"customer": "Dave Matthews"
}

View File

@@ -0,0 +1,6 @@
{
"_links": {
"item": [{ "href": "http://myhost/inventory/12" }]
},
"customer": "Dave Matthews"
}

View File

@@ -0,0 +1,6 @@
{
"_links": {
"item": { "href": "http://myhost/inventory/12" }
},
"customer": "Dave Matthews"
}

View File

@@ -0,0 +1,17 @@
{
"_links": {
"self": {
"href": "http://myhost/person/1"
},
"curies": {
"name": "ex",
"href": "http://example.com/rels/{rel}",
"templated": true
},
"ex:orders": {
"href": "http://myhost/person/1/orders"
}
},
"firstname": "Dave",
"lastname": "Matthews"
}

View File

@@ -0,0 +1,18 @@
{
"uber" : {
"version" : "1.0",
"data" : [ {
"rel" : [ "self" ],
"url" : "/employees/1"
}, {
"name" : "employee",
"data" : [ {
"name" : "role",
"value" : "ring bearer"
}, {
"name" : "name",
"value" : "Frodo"
} ]
} ]
}
}

View File

@@ -52,7 +52,7 @@ This allows clients to turn parameterized templates into URIs without having to
.Using links with templated URIs
====
[source, java, indent=0]
[source, java, indent=0, tabsize=2]
----
include::{code-dir}/FundamentalsTest.java[tags=templatedLinks]
----
@@ -211,7 +211,7 @@ The following code shows how to take a *self* link and associate two more afford
.Connecting affordances to `GET /employees/{id}`
====
[source,java,indent=0]
[source, java, indent=0, tabsize=2]
----
include::{code-dir}/EmployeeController.java[tag=get]
----
@@ -226,7 +226,7 @@ Imagine that the related methods *afforded* above looking like this:
.`updateEmpoyee` method that responds to `PUT /employees/{id}`
====
[source,java,indent=0]
[source, java, indent=0, tabsize=2]
----
include::{code-dir}/EmployeeController.java[tag=put]
----
@@ -234,7 +234,7 @@ include::{code-dir}/EmployeeController.java[tag=put]
.`partiallyUpdateEmployee` method that responds to `PATCH /employees/{id}`
====
[source,java,indent=0]
[source, java, indent=0, tabsize=2]
----
include::{code-dir}/EmployeeController.java[tag=patch]
----
@@ -271,50 +271,9 @@ The same resource above will render the following HAL-FORMS document:
.HAL-FORMS document with affordances
====
[source,json]
[source, json, tabsize=2]
----
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"role" : "ring bearer",
"_links" : {
"self" : {
"href" : "http://localhost:8080/employees/1"
}
},
"_templates" : { // <1>
"default" : {
"title" : null,
"method" : "put", // <2>
"contentType" : "",
"properties" : [ { // <3>
"name" : "firstName",
"required" : true // <4>
}, {
"name" : "lastName",
"required" : true
}, {
"name" : "role",
"required" : true
} ]
},
"partiallyUpdateEmployee" : { // <5>
"title" : null,
"method" : "patch", // <6>
"contentType" : "",
"properties" : [ {
"name" : "firstName",
"required" : false // <7>
}, {
"name" : "lastName",
"required" : false
}, {
"name" : "role",
"required" : false
} ]
}
}
}
include::{resource-dir}/docs/mediatype/hal/forms/hal-forms-sample-with-notes.json[]
----
<1> The `_templates` attribute provided by HAL-FORMS with affordance-based information.
<2> The `updateEmployee` method's `@PutMapping` annotation is translated to `put`.
@@ -330,7 +289,7 @@ attributes), includes enough extra details for full interaction with the resourc
In fact, this type of document makes it easy to write custom client-side code to generate an HTML form:
[source,html]
[source, html, tabsize=2]
----
<form method="put" action="http://localhost:8080/employees/1">
<input type="text" id="firstName" name="firstName"/>

View File

@@ -1,9 +1,96 @@
[[mediatypes]]
= Media types
:code-dir: ../../../src/docs/java/org/springframework/hateoas
:resource-dir: ../../../src/docs/resources/org/springframework/hateoas
[[mediatypes.hal]]
== HAL Hypertext Application Language
https://tools.ietf.org/html/draft-kelly-json-hal-08[JSON Hypertext Application Language] or HAL is one of the simplest
and most widely adopted hypermedia media types adopted when not discussing specific web stacks.
It was the first spec-based media type adopted by Spring HATEAOS.
[[mediatypes.hal.configuration]]
=== Configuring link rendering
In HAL, the `_links` entry is a JSON object. The property names are <<fundamentals.link-relations,link relations>> and
each value is either https://tools.ietf.org/html/draft-kelly-json-hal-07#section-4.1.1[a link object or an array of link objects].
For a given link relation that has two or more links, the spec is clear on representation:
.HAL document with two links associated with one relation
====
[source, json, tabsize=2]
----
include::{resource-dir}/docs/mediatype/hal/hal-multiple-entry-link-relation.json[]
----
====
But if there is only one link for a given relation, the spec is ambiguous. You could render that as either a single object
or as 1-item array.
By default, Spring HATEOAS uses the most terse approach and renders a single-link relation like this:
.HAL document with single link rendered as an object
====
[source, json, tabsize=2]
----
include::{resource-dir}/docs/mediatype/hal/hal-single-entry-link-relation-object.json[]
----
====
Some users prefer to not switch between arrays and objects when consuming HAL. They would prefer this type of rendering:
.HAL with single link rendered as an array
====
[source, json, tabsize=2]
----
include::{resource-dir}/docs/mediatype/hal/hal-single-entry-link-relation-array.json[]
----
====
If you wish to customize this policy, all you have to do is inject a `HalConfiguration` bean into your application configuration.
There are multiple choices.
.Global HAL single-link rendering policy
====
[source, java, indent=0, tabsize=2]
----
include::{code-dir}/SampleAppConfiguration.java[tag=1]
----
<1> Override Spring HATEOAS's default by rendering ALL single-link relations as arrays.
====
If you prefer to only override some particular link relations, you can create a `HalConfiguration`
bean like this:
.Link relation-based HAL single-link rendering policy
====
[source, java, indent=0, tabsize=2]
----
include::{code-dir}/SampleAppConfiguration.java[tag=2]
----
<1> Always render `item` link relations as an array.
<2> Render `prev` link relations as an object when there is only one link.
====
If neither of these match your needs, you can use an Ant-style path patterns:
.Pattern-based HAL single-link rendering policy
====
[source,java,indent=0,tabsize=2]
----
include::{code-dir}/SampleAppConfiguration.java[tag=3]
----
<1> Render all link relations that start with `http` as an array.
NOTE: The pattern-based approach uses Spring's `AntPathMatcher`.
====
All of these `HalConfiguration` withers can be combined to form one comprehensive policy. Be sure to test your API
extensively to avoid surprises.
[[mediatypes.hal.curie-provider]]
=== [[spis.curie-provider]] Using the `CurieProvider` API
@@ -33,26 +120,151 @@ Note that now the `ex:` prefix automatically appears before all rel values that
The following example shows how to do so:
====
[source, json]
[source, json, tabsize=2]
----
{
_"links" : {
"self" : { href: "http://myhost/person/1" },
"curies" : {
"name" : "ex",
"href" : "http://example.com/rels/{rel}",
"templated" : true
},
"ex:orders" : { href : "http://myhost/person/1/orders" }
},
"firstname" : "Dave",
"lastname" : "Matthews"
}
include::{resource-dir}/docs/mediatype/hal/hal-with-curies.json[]
----
====
Since the purpose of the `CurieProvider` API is to allow for automatic curie creation, you can define only one `CurieProvider` bean per application scope.
[[mediatypes.hal-forms]]
== HAL-FORMS
https://rwcbook.github.io/hal-forms/[HAL-FORMS] is designed to add runtime FORM support to the <<mediatypes.hal,HAL media type>>.
[quote, Mike Amundsen, HAL-FORMS spec]
____
HAL-FORMS "looks like HAL." However, it is important to keep in mind that HAL-FORMS is not the same as HALthe two
should not be thought of as interchangeable in any way.
____
To enable this media type, put the following configuration in your code:
.HAL-FORMS enabled application
====
[source, java, tabsize=2]
----
include::{code-dir}/HalFormsApplication.java[tag=code]
----
====
Anytime a client supplies an `Accept` header with `application/prs.hal-forms+json`, you can expect something like this:
.HAL-FORMS sample document
====
[source, json, tabsize=2]
----
include::{resource-dir}/docs/mediatype/hal/forms/hal-forms-sample.json[]
----
====
Checkout the https://rwcbook.github.io/hal-forms/[HAL-FORMS spec] to understand the details of the *_templates* attribute.
Read about the <<fundamentals.affordances,Affordances API>> to augment your controllers with this extra metadata.
As for single-item (`EntityModel`) and aggregate root collections (`CollectionModel`), Spring HATEOAS renders them
identically to <<mediatypes.hal,HAL documents>>.
[[mediatypes.collection-json]]
== Collection+JSON
http://amundsen.com/media-types/collection/format/[Collection+JSON] is a JSON spec registered with IANA-approved media type `application/vnd.collection+json`.
[quote, Mike Amundsen, Collection+JSON spec]
____
http://amundsen.com/media-types/collection/[Collection+JSON] is a JSON-based read/write hypermedia-type designed to support
management and querying of simple collections.
____
Collection+JSON provides a uniform way to represent both single item resources as well as collections.
To enable this media type, put the following configuration in your code:
.Collection+JSON enabled application
====
[source, java, tabsize=2]
----
include::{code-dir}/CollectionJsonApplication.java[tag=code]
----
====
This configuration will make your application respond to requests that have an `Accept` header of `application/vnd.collection+json`
as shown below.
The following example from the spec shows a single item:
.Collection+JSON single item example
====
[source, json, tabsize=2]
----
include::{resource-dir}/docs/mediatype/collectionjson/spec-part3.json[]
----
<1> The `self` link is stored in the document's `href` attribute.
<2> The document's top `links` section contains collection-level links (minus the `self` link).
<3> The `items` section contains a collection of data. Since this is a single-item document, it only has one entry.
<4> The `data` section contains actual content. It's made up of properties.
<5> The item's individual `links`.
====
[IMPORTANT]
====
The previous fragment was lifted from the spec. When Spring HATEOAS renders an `EntityModel`, it will:
* Put the `self` link into both the document's `href` attribute and the item-level `href` attribute.
* Put the rest of the model's links into both the top-level `links` as well as the item-level `links`.
* Extract the properties from the `EntityModel` and turn them into
====
When rendering a collection of resources, the document is almost the same, except there will be multiple entries inside
the `items` JSON array, one for each entry.
Spring HATEOAS more specifically will:
* Put the entire collection's `self` link into the top-level `href` attribute.
* The `CollectionModel` links (minus `self`) will be put into the top-level `links`.
* Each item-level `href` will contain the corresponding `self` link for each entry from the `CollectionModel.content` collection.
* Each item-level `links` will contain all other links for each entry from `CollectionModel.content`.
[[mediatypes.uber]]
== UBER - Uniform Basis for Exchanging Representations
http://uberhypermedia.org/[UBER] is an experimental JSON spec
NOTE: *UBER media type* is not associated in any way with Uber Technologies Inc., the ride sharing company.
[quote, Mike Amundsen, UBER spec]
____
The UBER document format is a minimal read/write hypermedia type designed to support simple state transfers and ad-hoc
hypermedia-based transitions.
____
UBER provides a uniform way to represent both single item resources as well as collections.
To enable this media type, put the following configuration in your code:
.UBER+JSON enabled application
====
[source, java, tabsize=2]
----
include::{code-dir}/UberApplication.java[tag=code]
----
====
This configuration will make your application respond to requests using the `Accept` header `application/vnd.amundsen-uber+json`
as show below:
.UBER sample document
====
[source, json, tabsize=2]
----
include::{resource-dir}/docs/mediatype/uber/uber-sample.json[]
----
====
This media type is still under development as is the spec itself. Feel free to
https://github.com/spring-projects/spring-hateoas/issues[open a ticket] if you run into issues using it.
[[mediatypes.custom]]
== Registering a custom media type