Disable Hypertext Application Language (HAL) in JSON?
Asked Answered
V

3

50

Using Spring Data REST with JPA in version 2.0.2.RELEASE.

How can I disable Hypertext Application Language (HAL) in the JSON ? http://stateless.co/hal_specification.html

I have tried many things already, but to no avail. For example, I have set Accept and Content-type headers to "application/json" instead of "application/hal+json" but I still receive the JSON content with hyper links.

For example, I'd like to get something like:

{
"name" : "Foo",
"street" : "street Bar",
"streetNumber" : 2,
"streetLetter" : "b",
"postCode" : "D-1253",
"town" : "Munchen",
"country" : "Germany",
"phone" : "+34 4410122000",
"vat" : "000000001",
"employees" : 225,
"sector" : {
     "description" : "Marketing",
     "average profit": 545656665,
     "average employees": 75,
     "average profit per employee": 4556
     }
}

Instead of:

{
"name" : "Foo",
"street" : "street Bar",
"streetNumber" : 2,
"streetLetter" : "b",
"postCode" : "D-1253",
"town" : "Munchen",
"country" : "Germany",
"phone" : "+34 4410122000",
"vat" : "000000001",
"employees" : 225,
"_links" : {
     "self" : {
          "href" : "http://localhost:8080/app/companies/1"
     },
     "sector" : {
          "href" : "http://localhost:8080/app/companies/1/sector"
     }
}
}

Thanks for your help.

Variform answered 24/4, 2014 at 8:46 Comment(0)
M
43

(Hyper)media types

The default settings for Spring Data REST use HAL as the default hypermedia representation format, so the server will return the following for the given Accept headers:

  • No header -> application/hal+json -> HAL
  • application/hal+json -> application/hal+json -> HAL
  • application/json -> application/json -> HAL (this is what the default configures)
  • application/x-spring-data-verbose+json -> application/x-spring-data-verbose+json -> a Spring Data specific format (using links for the links container and content as wrapper for the collection items.

If you configure RepositoryRestConfiguration.setDefaultMediaType(…) to a non-HAL format, the server will return the Spring Data specific JSON format unless you explicitly ask for application/hal+json. Admittedly the configuration option is probably a bit misleading, so I filed DATAREST-294 to improve this. The issue was resolved in 2.1 RC1 (Dijkstra) 2014.

Note that we effectively need a hypermedia format in place to be able to express relations between managed resources and enable discoverability of the server. So there's no way you'll be able to get rid of it completely. This is mostly due to the fact that you could easily crash the server if you expose entities that have bidirectional relationships or make up an enormous object graph.

Inlining related entities

If you never want to have sectors linked to and always inline them, one option is to simply exclude the SectorRepository from being exported as a REST resource in the first place. You can achieve this by annotating the repository interface with @RepositoryRestResource(exported = false).

To get a representation returned as you posted in your lower example have a look at the projections feature introduced in Spring Data REST 2.1 M1. It basically allow you to craft optional views on a resource that can differ from the default one via a simple interface.

You'd basically define an interface:

@Projection(name = "foo", types = YourDomainClass.class)
interface Inlined {

  // list all other properties

  Sector getSector();
}

If you either put this interface into a (sub)package of your domain class or manually register it via RepositoryRestConfiguration.projectionConfiguration() the resources exposing YourDomainClass will accept a request parameter projection so that passing in foo in this example would render the inlined representation as you want it.

This commit has more info on the feature in general, this commit has an example projection defined.

Malocclusion answered 25/4, 2014 at 7:52 Comment(13)
As advised, I have tried using @RepositoryRestResource(exported = false) in order to have the sectors inlined. However, in this case, I get a JSON with "sector" : null So, I'm giving a look at Spring Data REST 2.1 M1 and the projections option.Variform
The description given into the DATAREST-221 ticket you refer me to is exactly the situation I'm facing.Variform
It took me a bit long in order to grasp all the details, but it works. I was doing two mistakes: 1. I had not created the Projection class under my entities package (or a subpackage of the entities). You told me to do so, but I hadn't noticed 2. I have to create a SectorSomething interface and return that one in the Projection class (rather than a Sector class as written in your answer). Thank you very much for your help.Variform
Just a small remark (it's not a bug and not even an annoyance in my case; only a comment on this new functionality): in the resulting JSon, the properties of the domain class are in a different order when using the projection parameter; and this order does not follow the one of the Projection interface.Variform
That's probably caused by the reflection API returning the methods in different orders. However, the projection interface is open for customization through Jackson annotations, so @JsonPropertyOrder should allow you to customize things as needed.Malocclusion
I confirm: @JsonPropertyOrder works fine. Thank you very much for all your help.Variform
It is possible to disable completely hal? I want to retrieve only JSON with entities Ids.Amazonas
@bigluis Yes, you can do it by using Projections: docs.spring.io/spring-data/rest/docs/current/reference/html/… However this can be a tedious job to define Projections for all your entities.Variform
don't you think that's kind of silly? If I have 100 models, I would need to create 100 projections. I don't think so. I'm looking for a configuration flag that let me send Json or HalJson instead only HalJsonAmazonas
Hi, when i use the header: application/x-spring-data-verbose+json the content-list remains empty. I don't get what I'm doing wrong. More detailed question is here: #29671335Tarsus
The problem about the hypermedia-only nature of SDR is that simple rest apis (i.e. level 2 of the richardson maturity model) are mainstream and production-proven. The jury is still out on the usability of Hypermedia APIs (level 3). Tying SDR to Hypermedia seems to condemn it to experimental status, making it a tough sell to anyone who needs production-ready APIs - or am I missing something?Nestle
@rogerarmstrong You're missing the thousands of APIs that already use this model successfully, even in 2018. Your personal preferences are clouding your judgement.Georgiageorgian
For spring boot 2.0.5 I just get 406 Not Acceptable as response, so application/x-spring-data-verbose+json seems to be phased out.Afraid
F
4

So you want 2 things:

1) get rid of _links field
2) include the related sector field

Possible solution (works for me :D)

1) get rid of _links
For this create the class below:

[... package declaration, imports ...]
public class MyRepositoryRestMvcConfiguration extends RepositoryRestMvcConfiguration {
    public MyRepositoryRestMvcConfiguration(ApplicationContext context, ObjectFactory<ConversionService> conversionService) {
        super(context, conversionService);
    }

    @Bean
    protected LinkCollector linkCollector() {
        return new LinkCollector(persistentEntities(), selfLinkProvider(), associationLinks()) {
            public Links getLinksFor(Object object, List<Link> existingLinks) {
                return new Links();
            }
        };
    }
}

and use it e.g.:

[... package declaration, imports ...]
@SpringBootApplication
@Import({MyRepositoryRestMvcConfiguration.class})
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

I'm pretty sure (99%, but not tested) that you won't need this class for removing the _links for the related entity/entities included the way next point (2) is showing.

2) include the related sector field
For this you could use Excerpts (especially made for this scenario). Because the Spring example is so eloquent and it's silly to just copy it here I'll just point it: https://docs.spring.io/spring-data/rest/docs/3.1.x/reference/html/#projections-excerpts.excerpting-commonly-accessed-data.
But just for the record and your convenience I'll paste the main parts of the spring example:

@Projection(name = "inlineAddress", types = { Person.class }) 
interface InlineAddress {
  String getFirstName();
  String getLastName();
  Address getAddress(); 
}

see at Projection javadoc that types means The type the projection type is bound to.
The excerpt could be used this way:

@RepositoryRestResource(excerptProjection = InlineAddress.class)
interface PersonRepository extends CrudRepository<Person, Long> {}

in order to get this (when also using MyRepositoryRestMvcConfiguration):

{
  "firstName" : "Frodo",
  "lastName" : "Baggins",
  "address" : { 
    "street": "Bag End",
    "state": "The Shire",
    "country": "Middle Earth"
  }
}

For you the sector is the equivalent of address.

Final notes

When returning arrays the _links field won't be removed (it's too intrusive to do it); in the end you'll have something like this:

{
    "_embedded" : {
        "persons" : [ {person1}, {person2}, ..., {personN} ]
    },
    "_links" : {
        e.g. first, next, last, self, profile
    },
    "page" : {
      "size" : 1,
      "totalElements" : 10,
      "totalPages" : 10,
      "number" : 0
    }
}

As you can see even if we'd have _links removed that still won't be enough; one would probably also want _embedded replaced by persons which would lead to less maintainable code (too much spring intrusive overrides). But if one really wants these too he should start checking RepositoryRestMvcConfiguration and RepositoryEntityController.getCollectionResource.

Spring is evolving so I feel the need to point that this works with at least:

spring-data-rest-webmvc 3.1.3.RELEASE

or, if you prefeer spring boot version:

spring-boot-starter-parent 2.1.1.RELEASE
Fugate answered 12/12, 2018 at 20:25 Comment(4)
I like your answer, it offers a good tutorial for those who don't know about Excerpts, it might help other people. However the question dates back to 2014, and I have been able to achieve what I was trying to do in a more elegant way with Projections, according to the answer of Oliver Drotbohm (he's the Spring Data project lead). I just checked what I had done in the code: in order to include the related sector field, my main Projection returns a SectorProjection rather than a Sector entity. In this way I don't get the links at all. Your answer is valid if one wants to keep the links tooVariform
Thank you for the appreciation :). With the 1th point I showed how to remove the _links field; this doesn’t work indeed for arrays but with the Projections solution I’m pretty sure (99%) it’s the same (and for _embedded and page too).Fugate
Yes, it's very similar at the end. But I can only choose one "good" answer, sorry. And I'm sure your answer will be appreciated by the community.Variform
Starting from Spring Data Rest 3.6.0, the LinkCollector is (more) easily customizable now: github.com/spring-projects/spring-data-rest/commit/…Horseweed
E
1

If you want to delete _links do the following (worked for me):

  1. Go to your pom.xml and delete the following dependency:

    spring-boot-starter-data-rest

  2. Make an "Update project" to update the pom.xml changes.

Now it will be use your own controller for the api rest, deleting _self, _links..., that alike this:

[... package declaration, imports ...]
@RestController
@RequestMapping("/series")
public class SerieController {
    @Autowired
    private SerieRepositorio serieRepositorio;
    
    public SerieController(SerieRepositorio serieRepositorio) {
        this.serieRepositorio = serieRepositorio;
    }

    @GetMapping
    public Iterable<Serie> getAllSeries() {
        return serieRepositorio.findAll();
    }
   }
Expressly answered 3/5, 2022 at 14:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.