spring-projects / spring-hateoas

Spring HATEOAS - Library to support implementing representations for hyper-text driven REST web services.
https://spring.io/projects/spring-hateoas
Apache License 2.0
1.04k stars 476 forks source link

spring boot hypermedia convert child elements to Array instead of Object #982

Open VelDeveloper opened 5 years ago

VelDeveloper commented 5 years ago

I am using spring-boot-1.5.8 and spring-boot-hateoas in my project. Please find the code below

@SpringBootApplication
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
public class SpringBootK8Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootK8Application.class, args);
    }
}

Controller

 @GetMapping("/greeting")
    public ResponseEntity<Greeting> greeting(@RequestParam(value = "name", required = false, defaultValue = "World") String name) {
        Greeting greeting = new Greeting(String.format("Hello, %s!", name));
        greeting.add(linkTo(methodOn(HelloController.class).greeting(name)).withSelfRel());
        Link link = new Link("http://localhost:8080/something").withRel("ACTIONS");
        SuperLink superLink = new SuperLink(link, MediaTypes.HAL_JSON_VALUE,"GET","/v1/books","v1/bookBy");
        greeting.add(superLink);
        return new ResponseEntity<>(greeting, HttpStatus.OK);
    }

Actual output:

{
  "content": "Hello, World!",
  "_links": {
    "self": {
      "href": "http://localhost:9086/greeting?name=World"
    },
    "ACTIONS": {
      "href": "http://localhost:8080/something",
      "template": "http://localhost:8080/something/v1/books",
      "type": "application/hal+json",
      "method": "GET",
      "describedBy": "http://localhost:8080/something/v1/bookBy"
    }
  }
}

Expected output:

{
  "content": "Hello, World!",
  "_links": {
    "self": {
      "href": "http://localhost:9086/greeting?name=World"
    },
    "ACTIONS": [{
      "href": "http://localhost:8080/something",
      "template": "http://localhost:8080/something/v1/books",
      "type": "application/hal+json",
      "method": "GET",
      "describedBy": "http://localhost:8080/something/v1/describedBy"
    }]
  }
}

I would like to show action as an array. If I add more than one element in ACTIONS attribute then it shows array but if we have only one element then it is showing as an object. I would always like to display that element as an array. Any help would be really appreciable.

gregturn commented 5 years ago

What happens when you drop the Enable annotation?

VelDeveloper commented 5 years ago

@gregturn Thanks a lot for your quick reply. I have removed and did a check but still no luck. It's not rendering an array.

VelDeveloper commented 5 years ago

I have gone through this documentation https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/reference/html/

Here, we can configure the item to be returned as Object or Array,

@Bean
public HalConfiguration linkRelationBasedPolicy() {
  return new HalConfiguration() //
      .withRenderSingleLinksFor( //
          IanaLinkRelations.ITEM, RenderSingleLinks.AS_ARRAY) 
      .withRenderSingleLinksFor( //
          LinkRelation.of("prev"), RenderSingleLinks.AS_SINGLE); 
}

I am trying the same in my code but unfortunately, HalConfiguration.class is missing. I think the above configuration will fix my issue. Any help should be highly appreciated.

gregturn commented 5 years ago

You cited 1.0.0.M1 which i believe requires Boot 2.2.

VelDeveloper commented 5 years ago

Do we have any similar configuration for spring-boot-1.5.10? Or do we have any other way to resolve the above issue?

gregturn commented 5 years ago

No. Boot 1.5 uses Spring HATEOAS 0.25.x. 1.0 is where we are focusing most efforts. Only a small handful of things are getting backported.

VelDeveloper commented 5 years ago

ok, @gregturn thanks a lot for your prompt reply. So now we don't have any way to resolve the above issue? Any dirty hacks? Already I have spent the entire day but still no luck. Hoping for your positive reply.

gregturn commented 5 years ago

Actually https://github.com/spring-projects/spring-hateoas/commit/5eee30127dfdea90024dd72aa1de2bd4d6ef7521 Did make it into 0.25.

gregturn commented 5 years ago

To get a finer grained solution you’d have to backport this => https://github.com/spring-projects/spring-hateoas/commit/aea4c4c91f4bbb37049124133f4465078a02c769

That is a complex scenario and given Boot 1.5 is approaching EOL...

VelDeveloper commented 5 years ago

@gregturn I have used 0.25 It works partially, Please find the below code,

@Bean
    public HalConfiguration linkRelationBasedPolicy() {
        return new HalConfiguration()
                .withRenderSingleLinks(HalConfiguration.RenderSingleLinks.AS_ARRAY);
    }

Actual output :

{
  "content": "Hello, World!",
  "_links": {
    "self": [
      {
        "href": "http://localhost:9086/greeting?name=World"
      }
    ],
    "ACTIONS": [
      {
        "href": "http://localhost:8080/something",
        "type": "application/hal+json",
        "template": {
          "variables": [],
          "variableNames": []
        },
        "method": "GET",
        "describedBy": "http://localhost:8080/somethingv1/describedBy"
      }
    ]
  }
}

Even the self-link is an array now. Is it possible for us to configure the attribute name in HalConfiguration? I mean in this case, only ACTIONS attribute should be rendered single item as an array.

I didn't understand your later solution. Backport means? What do you? Could you please elaborate a bit?

VelDeveloper commented 5 years ago

@gregturn I see the link you have shared. So in 0.25, we don't have this method withRenderSingleLinksFor and which should be there only 1.0.0 M1. This is the only solution?

gregturn commented 5 years ago

That's what I'm saying. In 0.25, there is an all-or-nothing solution in place, and you just displayed it.

The commit I later linked is the fine grained solution presently only in 1.0. To get it into 0.25 would require a bigger backport.

One option would be to create a fork and backport it until you can time to upgrade to Boot 2.2 + HATEOAS 1.0.

VelDeveloper commented 5 years ago

@gregturn I did a backport but it seems still there is an issue. Please find the code in the following branch https://github.com/VelDeveloper/spring-boot-k8/tree/dev/src/main/java ...Getting below error,

{
  "timestamp": 1555415335439,
  "status": 500,
  "error": "Internal Server Error",
  "exception": "org.springframework.http.converter.HttpMessageNotWritableException",
  "message": "Could not write JSON: java.util.ArrayList cannot be cast to org.springframework.hateoas.Links; nested exception is com.fasterxml.jackson.databind.JsonMappingException: java.util.ArrayList cannot be cast to org.springframework.hateoas.Links (through reference chain: org.springframework.hateoas.Resource[\"_links\"])",
  "path": "/greeting"
}

Getting this issue only after Backport. Am i doing anything wrong?

VelDeveloper commented 5 years ago

@gregturn Any idea? Or is there any way to intercept the response and change the structure of the links using AOP?

Any help should be really appreciated.

gregturn commented 5 years ago

Your only alternative apart from backporting it yourself in a fork would be to write either a piece of AOP advice or perhaps a custom filter that rewrites your JSON as needed. Neither of these are endeavors we have time/resources to undertake.