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.03k stars 475 forks source link

Returns duplicate links causing JSON deserialization exception #406

Open asarkar opened 8 years ago

asarkar commented 8 years ago

Vanilla resource User -

public class User implements Serializable {
    private static final long serialVersionUID = 262950482349139355L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "FIRST_NAME", nullable = false, unique = false)
    @Convert(converter = NameConverter.class)
    private String firstName;

    @Column(name = "LAST_NAME", nullable = false, unique = false)
    @Convert(converter = NameConverter.class)
    private String lastName;

    @Column(name = "PHONE_NUM", nullable = false, unique = false)
    @Convert(converter = PhoneNumberConverter.class)
    private String phoneNum;

    @Column(name = "EMAIL", nullable = true, unique = false)
    @Convert(converter = OptionalStringConverter.class)
    private Optional<String> email;
}

Search result with hostname changed in the URL - 2 _links present

{
  "_links" : {
    "self" : {
      "href" : "http://hostname/users/search/findByLastName?lastName=doe{&page,size,sort}",
      "templated" : true
    }
  },
  "_embedded" : {
    "users" : [ {
      "firstName" : "John",
      "lastName" : "Doe",
      "phoneNum" : "111-111-1111",
      "email" : null,
      "_links" : { },
      "_embedded" : { },
      "_links" : {
        "self" : {
          "href" : "http://hostname/users/1",
          "templated" : false
        }
      }
    } ]
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}

The duplicate links are not present when the resource is accessed on it's own. However, when a client (a microservice in my case) makes a request using a RestTemplate, the response comes back with 2 _links as shown above.

@Override
public Collection<Long> getUserIdsByFirstName(String firstName) {
    String findByFirstNameUri = UriComponentsBuilder.fromUriString(userServiceUrl).path("/users/search/findByFirstName").queryParam("firstName", firstName).toUriString();

    // This is where ResponseEntity.body would have the 2 _links, if ResponseEntity<String> were used. I used a custom object to weed that out.
    ResponseEntity<UserSearchResult> userSearchResult = restTemplate.exchange(findByFirstNameUri, GET, dummyEntity, UserSearchResult.class);
         ...
}

I'm using Spring Data JPA, Spring Data Rest, Spring HATEOAS and Spring Cloud. Here's a link to the project on my Github.

Karakaz commented 5 years ago

I encountered this multiple links issue today. It was a slightly different scenario but I managed to fix it by letting my resource extend ResourceSupport.

gregturn commented 4 years ago

It's recommended for any Spring MVC/WebFlux controller to return one of HATEOAS's "types", ie a subclass of RepresentationModel in order for it to properly render hypermedia.

onacit commented 9 months ago

I just faced the same problem with 3.2.0.

The following base class generates two $.links property.

@JsonInclude(JsonInclude.Include.NON_NULL)
@Setter
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
//@SuperBuilder
@SuppressWarnings({
        "java:S101", // class __Base...
        "java:S119"  // <SELF ...>
})
public abstract class __BaseType<SELF extends __BaseType<SELF>>
        extends RepresentationModel<SELF>
        implements Serializable {

    /**
     * Returns all {@link Link}s contained in this resource.
     *
     * @return all {@link Link}s contained in this resource.
     * @apiNote Overridden to be annotated with {@link Schema.AccessMode#READ_ONLY} and
     * {@link JsonProperty.Access#READ_ONLY}.
     */
    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    @Override
    public Links getLinks() {
        return super.getLinks();
    }
}
{
  "links" : [ ],
  ...
  "links" : {
    "self" : {
      "href" : "http://localhost:56923/api/stores/..."
    },
    "store" : {
      "href" : "http://localhost:56923/api/stores/..."
    }
  }
}