Spring-hateoas: Sample for embedding HAL Resources of different types with root resource

Created on 10 Dec 2014  Â·  42Comments  Â·  Source: spring-projects/spring-hateoas

Hi,
Google is full of questions around how to create embedded HAL resources of different types inside the main root resource. I looked at the Resources Interface, but that can only be leveraged by same types to model collection of a particular type.
I am currently using Spring-Hateoas to create a nested resource hierarchy and creating "_embedded" jsonproperty looks like a hack. Spring Data Rest has support for embedding entities, so it will be really helpful if you could please create a sample for embedding different type of single and collection of resources within a root resource.

enhancement core mediatypes

Most helpful comment

Congratulations to this issue reaching its 3rd birthday 🎂.

All 42 comments

+1

+1

+1

+1

+1 here, too.

Here's an example of one such Google result: http://stackoverflow.com/questions/25858698/spring-hateoas-embedded-resource-support

In my mind, there are Entities, Resources and Resource Representations.
A Resource should comprise of one or more Entities or even partial entities (only select few fields from a specific entity)
A Resource Representation serializes Resources in a specific way.

An example can be Entities are Person & Address. A Resource can be PersonDetail, which is collection of Person and Address. A PersonDetail can be linked to Orders Resource via "allPastOrders".

So, when, we want to represent PersonDetail Resource, i may for optimization reason, want to embed Orders Resource.

There is already an API to link Resource. That allows us to design relationship between Resources.
Embedding is a resource representation issue. In the stackoverflow question, HALResource class is a representation class for a particular resource. But, i dont think that representation class should have to worry about link names.

In order to avoid this long chain of things, perhaps having Resource Representation Annotations, that can be placed on a Resource, would help to determine whether to embed Resources or not??

+1

+1

+1

+1.

This issue and #169 are my pain using Spring HATEOAS with HAL.

+1

Ultimately this feature is not supported with Spring HATEOAS. Embedding non collection resources must be done manually if you want to support the entire hal spec. Currently our team has decided to consider this a HAL light and we do not embed non list resources. Ultimately this issue should turn into a feature request. Thoughts? May want to create a new issue requesting support.
"Spring HATEOAS to support embedding models in a resource"

I'd be interested in seeing a feature similar to what @anywhereinfo describes above.

... perhaps having Resource Representation Annotations, that can be placed on a Resource, would help to determine whether to embed Resources or not?

I'm currently using spring-data-rest, and there seems to be no easy way to specify if a child resource should appear in the _embedded object.

I might be missing something but this works out of the box:

// Infrastructure setup, only needed for test case
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jackson2HalModule());
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.setHandlerInstantiator(new HalHandlerInstantiator(new EvoInflectorRelProvider(), null, null));

Resources<Object> resources = new Resources<>(Arrays.asList(new Animal("Dog"), new Person("Dave", "Matthews")));

System.out.println(mapper.writeValueAsString(resources));

Yields:

{
  "_embedded" : {
    "persons" : [ {
      "firstname" : "Dave",
      "lastname" : "Matthews"
    } ],
    "animals" : [ {
      "kind" : "Dog"
    } ]
  }
}

Would anyone of you mind adding a sample of what you're expecting? It feels weird that so many people +1 this, when there isn't even an example of what's expected and I suspect a lot of the +1s here expecting something different than the original poster.

I was thinking more along the lines of @anywhereinfo's second comment about an annotation to define which Resources to embed. This probably was not the right issue to +1, but It seemed closest to what I was looking to do. I haven't had any issues embedding a single object in the _embedded tag.

Looking to embed a resource when implementing ResourceSupport ie. so that any non primitive on the root object will auto serialize in embedded. If not using ResourceSupport, but wrapping your model with Resource class, it should do the same. Or maybe provide an embed method to embed a sub item.

Examples:

public class HypermediaModel implements ResourceSupport{

    private String value;

    @Embedded
    private HypermediaSubModel subModel;
}

Then serialized should yield:

{
  "value": "",
  "_embedded": {
    "hypermediaSubModel": {
       "subValue": ""
    }
  }
}

A similar function on Resource class would be use-full if not implementing ResourceSupport.

Resource<HypermediaModel> hypermediaResourceModel = new Resource(hypermediaModel, links);

This would yield similar results.

Maybe useful:

hypermediaResourceModel.embed(...);

Let me know if I'm off base on this request.

You can embed more than just collections in HAL. How do you embed a single object? Ultimately our team decided to only place collections in embedded and provide links to associated resources. If we need to provide aggregate data we do it directly on the json object and not in embedded. Ultimately this is not proper to HAL spec and not supported by Spring HATEOAS from what I understand .

+1 definitely need this, or at least a workable way to get around it for accepting resources with arbitrary _embedded collections.

Hi! We have an issue and I think it's related for this problem, but the difference is that we want do deserialize embedded resource with different type. There is an example how we build embedded resources with different type:
https://github.com/locisvv/spring-hateoas-example

And this is our question:
http://stackoverflow.com/questions/34264721/what-is-the-convenient-way-to-deserialize-jsonlinks-embedded-container-using

@olivergierke
The example you gave handles just one specific (not very useful) case of embedding resources. Let's start with the sample from the HAL Internet Draft:

  {
     "_links": {
       "self": { "href": "/books/the-way-of-zen" },
       "author": { "href": "/people/alan-watts" }
     }
   }

Assume the main JSON object is generated from class Book and author is of class Person. Now you may want to reduce the number of server hits by embedding author in the book payload. You should use same relation name in the '_embedded'. So we would expect:

  {
     "_links": {
       "self": { "href": "/books/the-way-of-zen" },
       "author": { "href": "/people/alan-watts" }
     }
   }
     "_embedded": {
       "author": {
         "_links": { "self": { "href": "/people/alan-watts" } },
         "name": "Alan Watts",
         "born": "January 6, 1915",
         "died": "November 16, 1973"
       }
     }

Untill now all is possible with spring-hateoas exept an array of object instead of a single object.
But let's say our book is illustrated:

  {
     "_links": {
       "self": { "href": "/books/the-way-of-zen" },
       "author": { "href": "/people/alan-watts" },
       "illustrator": { "href": "/people/john-smith" }
     }
   }

and now we want to embedd both the author and the illustrator. Both of them are of class Person.
This is not possible with spring-hateoas:

  {
     "_links": {
       "self": { "href": "/books/the-way-of-zen" },
       "author": { "href": "/people/alan-watts" }
       "illustrator": { "href": "/people/john-smith" }
     }
   }
     "_embedded": {
       "author": {
         "_links": { "self": { "href": "/people/alan-watts" } },
         "name": "Alan Watts",
         "born": "January 6, 1915",
         "died": "November 16, 1973"
       }
       "illustrator": {
         "_links": { "self": { "href": "/people/john-smith" } },
         "name": "John Smith",
       }
     }

Why? Because the relation name in the _embedded map is based on the type of the resource.

The idea of interface RelProvider is flawed with its get...RelFor(Class<?> type).

In most cases resource embedding is the next step to resource linking (http://tools.ietf.org/html/draft-kelly-json-hal-07#section-8.3). If you think that a linked resource is follow in most use cases, you consider embedding it because of performance. Resource embedding should be similar to linking i.e. if you have ResourceSupport::add(Links... links) you should probably have something like ResourceSupport::add(EmbeddedResources... embedded) where class EmbeddedResource should have a field String rel just like class Link has.

I suggest to change 'Sample' into 'Support' in this issue title.

Embedding a single object is part of the HAL spec draft Section 4.1.2

Any updates on this topic?

Sideloading related data is quite important for many use cases.

You've provided a very good example from the HAL spec. Embedding values for performance reasons makes sense and is clearly marked in the spec itself.

{
     "_links": {
       "self": { "href": "/blog-post" },
       "author": { "href": "/people/alan-watts" }
     },
     "_embedded": {
       "author": {
         "_links": { "self": { "href": "/people/alan-watts" } },
         "name": "Alan Watts",
         "born": "January 6, 1915",
         "died": "November 16, 1973"
       }
     }
   }

I think this providers a good test case to work against.

I was expecting to find a miraculous @JsonProperty(type=HalEmbedded.class) annotation to achieve this functionality but everything that I found was a StackOverflow question to achieve that with spring HATEOAS, but without success with Spring Data Rest...
Strongly expecting this enhancement...

?

Any progress with this issue? Not being able to add single elements to _embedded is really a pain. However, I found a solution with using EmbeddedWrappers:

class MyObject {
    @JsonUnwrapped
    private final Resources<EmbeddedWrapper> embeddeds;

    MyObject(EmbeddableClass embeddable) {
        embeddeds = new Resources<>(Arrays.asList(
                new EmbeddedWrappers(false).wrap(embeddable)));
    }
}

I drafted a branch to poke at this issue and am working through various issues to not break everything else.

Congratulations to this issue reaching its 3rd birthday 🎂.

jkubrynski thinks the same as me! :) To make this work it would be ideal to have a class, that extends ResourceSupport, that has content as the properties at the root level of JSON (that already exists and it's the Resource<T> where the content is of type T), and modifiable _embedded section. I already have this one working for our system and posted the utility here . Basically, I extended Resource<T> to also include what jkubrynski said. I think we could have this additional class along with Resource<T> or even deprecate it since one may not embed anything and it will behave equally to the simple Resource<T>.

Is there a way we can deserialize responses from the spring.data.rest @RestResource annotated repository? In a much simpler way & not through hacking, unlike what I see on Stackoverflow.

Given that I knew which properties I wanted to have embedded directly, the simplest way for me to just include one json-public method to my entity/ies with those embedded members:

class WhateverParentEntity {
private List<ChildEntityType> whatEverChildEntities;
...
 @JsonUnwrapped
    private final Resources<EmbeddedWrapper> getEmbeddeds() {
        List<EmbeddedWrapper> embedded = new ArrayList<>();
        embedded.add(new EmbeddedWrappers(false).wrap(whatEverChildEntities));
        return new Resources<>(embedded);
    }

Sorry I missed it, little one! Happy 4th birthday! 🎂

Using EmbeddedWrappers you can really construct anything you need. The following fragment of code is based on our new types found in 1.0.0.M1.

@GetMapping("/author/{id}")
CollectionModel<?> authorDetails(@PathVariable int id) {

    EmbeddedWrappers wrappers = new EmbeddedWrappers(false);

    List<Object> items = new ArrayList<>();

    EntityModel<Author> authorModel = new EntityModel<>( //
            new Author("Alan Watters", "January 6, 1915", "November 16, 1973"), //
            new Link("/people/alan-watts"));

    items.add(wrappers.wrap(authorModel, LinkRelation.of("author")));

    EntityModel<Author> illustratorModel = new EntityModel<>( //
            new Author("John Smith", null, null), //
            new Link("/people/john-smith"));

    items.add(wrappers.wrap(illustratorModel, LinkRelation.of("illustrator")));

    CollectionModel<?> collectionModel = new CollectionModel<>(items);
    collectionModel.add(new Link("/books/the-way-of-zen"));
    collectionModel.add(new Link("/people/alan-watts", LinkRelation.of("author")));
    collectionModel.add(new Link("/people/john-smith", LinkRelation.of("illustrator")));

    return collectionModel;
}

It generates this:

{
  "_embedded" : {
    "author" : {
      "name" : "Alan Watters",
      "born" : "January 6, 1915",
      "died" : "November 16, 1973",
      "_links" : {
        "self" : {
          "href" : "/people/alan-watts"
        }
      }
    },
    "illustrator" : {
      "name" : "John Smith",
      "_links" : {
        "self" : {
          "href" : "/people/john-smith"
        }
      }
    }
  },
  "_links" : {
    "self" : {
      "href" : "/books/the-way-of-zen"
    },
    "author" : {
      "href" : "/people/alan-watts"
    },
    "illustrator" : {
      "href" : "/people/john-smith"
    }
  }
}

Perhaps you're thinking that returning a CollectionModel is an awkward type for returning a single Author. The truth is, you're returning a unique combination of resources merged together.

I'm working on a fluent API that builds the same thing. What do you think?

@GetMapping("/author/{id}")
RepresentationModel<?> authorDetails(@PathVariable int id) {

    return ModelBuilder //
            .embed(LinkRelation.of("author")) //

            .entity(new Author("Alan Watts", "January 6, 1915", "November 16, 1973")) //
            .link(new Link("/people/alan-watts")) //

            .embed(LinkRelation.of("illustrator")) //
            .entity(new Author("John Smith", null, null)) //
            .link(new Link("/people/john-smith")) //

            .rootLink(new Link("/books/the-way-of-zen")) //
            .rootLink(new Link("/people/alan-watts", LinkRelation.of("author"))) //
            .rootLink(new Link("/people/john-smith", LinkRelation.of("illustrator"))) //

            .build();
}

@gregturn if I remember correctly, _embeddedand _links are supposed to be at the root level only, aren't they?

If you look at the first HAL document shown on the spec you’ll find links on multiple levels.

http://stateless.co/hal_specification.html

@gregturn ok, good job! But it would be more readable if entity and link methods would be used inside embed.

Show me how you would prefer it.

@gregturn

@GetMapping("/author/{id}")
RepresentationModel<?> authorDetails(@PathVariable int id) {

    return ModelBuilder
                      //.root(someObj) possibility to add some fields to the root is missing?
            .embed(
                          LinkRelation.of("author"), // that could be a string
                      entity(new Author("Alan Watts", "January 6, 1915", "November 16, 1973")), // that could be an Object without call to entity(..)
              link(new Link("/people/alan-watts")) // this is varargs as it is optional. also could be a Link without calls to link(..)
                        )
            .embed(
                          LinkRelation.of("illustrator")), 
              entity(new Author("John Smith", null, null)), 
              link(new Link("/people/john-smith")) 
                        )
            .link(new Link("/books/the-way-of-zen")) 
            .link(new Link("/people/alan-watts", LinkRelation.of("author"))) 
            .link(new Link("/people/john-smith", LinkRelation.of("illustrator"))) 
            .build();
}

That still feels like quite a bit of duplication? What if we started with a Link and allow users to augment this with a preview?

Link wattsLink = new Link("/people/alan-watts", LinkRelation.of("author"));
Link illustratorLink = new Link("/people/john-smith", LinkRelation.of("illustrator"));

ModelBuilder.entity(…)
  .andLink(wattsLink).withEmbed(new Author(…))
  .andLink(illustratorLink).withEmbed(new Author(…))
  .build();

That could still keep the ….embed(…) methods around if you wanted to add embeds excplitly.

Can we gather this feedback underneath #864?

We took a first stab at a HAL specific model builder with 2cab91a33253e6e2e2587b868b871deb872dc168. See the updated reference documentation for details.

Was this page helpful?
0 / 5 - 0 ratings