Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sample for embedding HAL Resources of different types with root resource #270

Closed
anywhereinfo opened this issue Dec 9, 2014 · 42 comments
Closed
Assignees
Labels
in: core Core parts of the project in: mediatypes Media type related functionality type: enhancement
Milestone

Comments

@anywhereinfo
Copy link

anywhereinfo commented Dec 9, 2014

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.

@lazee
Copy link

lazee commented Dec 13, 2014

+1

3 similar comments
@williewheeler
Copy link

+1

@andreasevers
Copy link

+1

@billyyarosh
Copy link

+1

@dminkovsky
Copy link

+1 here, too.

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

@anywhereinfo
Copy link
Author

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??

@sesto
Copy link

sesto commented Mar 18, 2015

+1

2 similar comments
@aisven
Copy link

aisven commented Jun 3, 2015

+1

@harttamale
Copy link

+1

@jiwhiz
Copy link

jiwhiz commented Sep 18, 2015

+1.

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

@aglassman
Copy link

+1

@billyyarosh
Copy link

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"

@aglassman
Copy link

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.

@odrotbohm
Copy link
Member

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.

@odrotbohm odrotbohm self-assigned this Oct 5, 2015
@aglassman
Copy link

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.

@billyyarosh
Copy link

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.

@billyyarosh
Copy link

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 .

@vsliouniaev
Copy link

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

@locisvv
Copy link

locisvv commented Dec 14, 2015

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

@trombka
Copy link

trombka commented Jan 22, 2016

@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.

@billyyarosh
Copy link

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

@Ramblurr
Copy link

Any updates on this topic?

Sideloading related data is quite important for many use cases.

@gregturn
Copy link
Contributor

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.

@D-BlindSide
Copy link

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...

@vnobo
Copy link

vnobo commented Oct 31, 2017

?

@jkubrynski
Copy link

jkubrynski commented Nov 2, 2017

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)));
	}
}

@gregturn
Copy link
Contributor

gregturn commented Nov 2, 2017

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

gregturn added a commit that referenced this issue Nov 9, 2017
The HAL spec shows an example of a single resource, embedded at the top level. Spring HATEOAS, up until this point, has only supports embedded for the purposes of collections.
@vsliouniaev
Copy link

Congratulations to this issue reaching its 3rd birthday 🎂.

@Sam-Kruglov
Copy link

Sam-Kruglov commented Jan 25, 2018

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>.

@sean-huni
Copy link

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.

@CChange777
Copy link

CChange777 commented Oct 13, 2018

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);
    }

@vsliouniaev
Copy link

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

@gregturn
Copy link
Contributor

gregturn commented Mar 6, 2019

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.

@gregturn
Copy link
Contributor

gregturn commented Mar 8, 2019

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();
}

@Sam-Kruglov
Copy link

Sam-Kruglov commented Mar 11, 2019

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

@gregturn
Copy link
Contributor

gregturn commented Mar 11, 2019

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

@Sam-Kruglov
Copy link

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

@gregturn
Copy link
Contributor

Show me how you would prefer it.

@Sam-Kruglov
Copy link

Sam-Kruglov commented Mar 13, 2019

@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();
}

@odrotbohm
Copy link
Member

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.

@gregturn
Copy link
Contributor

Can we gather this feedback underneath #864?

odrotbohm added a commit that referenced this issue May 7, 2020
HalModelBuilder expose HAL-idiomatic API to set up representations. That includes embeds, previews and syntactic sugar around the inclusion of potentially empty collections as embeds.

Related tickets: #175, #193, #270, #920.
Original pull request: #1273.
@odrotbohm
Copy link
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Core parts of the project in: mediatypes Media type related functionality type: enhancement
Projects
None yet
Development

No branches or pull requests