toedter / spring-hateoas-jsonapi

A JSON:API media type implementation for Spring HATEOAS
Apache License 2.0
107 stars 15 forks source link

Non-Pageable-Model #78

Closed Rezar00 closed 1 year ago

Rezar00 commented 1 year ago

How can return a List of objects as JSON:API standard with this implementation? I can see the example is used PageModel. I show a list of objects without PageModel.

toedter commented 1 year ago

Just use a CrudRepository (which is not pageable) and consult the Spring documentation.

Rezar00 commented 1 year ago

@toedter Thanks for your response. My issue is not CrudRepository. I don't have a Database. I just call a third-party service and the response is A model which contains a list of another object. I want to expose this list and its data based on your implementation. How can I do that?

toedter commented 1 year ago

Ah, now I understand. I guess this "list of other objects" does not contain REST resources but just data. In this case you would not even need a Spring HATEOAS Collection recourse, you could just include a List data structure in your DTO.

If you want to convert the list of objects to a collection of REST resources, you could use the builder from my library.

Rezar00 commented 1 year ago

I called a rest API and get the response. The model is a Product object:

{ "productName": "Statutenwijziging", "productUrl": null, "orderLink": null, "deliveryVariants": [ { "format": "PdfNietGewaarmerkt", "price": 345, "orderLink": null, "link": null, "emailRequired": true }, { "format": "PdfGewaarmerkt", "price": 845, "orderLink": null, "link": null, "emailRequired": true } ], "fromPrice": 345, "productId": "090299cc2099d937", "productCode": "75376498", "boekjaar": null, "boekjaren": null, "deponeringsDatum": "2013-10-01T00:00:00", "tapedForAvg": true, "concernNaam": null, "links": [] },

I have a list of Products. I Used :

@GetMapping(value = "{number}/{productType}", produces = "application/vnd.api+json") public ResponseEntity<List<RepresentationModel<?>>> getAvailableProducts(@PathVariable String number, @PathVariable ProductType productType) {

   AvailableProductsResponse availableProducts =
            this.productService.getAvailableProducts(number, productType);
    List<RepresentationModel<?>> products = new ArrayList<>();
    availableProducts.getProducts().forEach(product -> {
        products.add(JsonApiModelBuilder.jsonApiModel().model(product).build());
    });
    return ResponseEntity.ok(products);
} 

but in the response json i see: [ { "productName": "Statutenwijziging", "productUrl": null, "orderLink": null, "deliveryVariants": [ { "format": "PdfNietGewaarmerkt", "price": 345, "orderLink": null, "link": null, "emailRequired": true }, { "format": "PdfGewaarmerkt", "price": 845, "orderLink": null, "link": null, "emailRequired": true } ], "fromPrice": 345, "productId": "090299cc2099d937", "productCode": "75376498", "boekjaar": null, "boekjaren": null, "deponeringsDatum": "2013-10-01T00:00:00", "tapedForAvg": true, "concernNaam": null, "links": [] }, { "productName": "Statutenwijziging", "productUrl": null, "orderLink": null, "deliveryVariants": [ { "format": "PdfNietGewaarmerkt", "price": 345, "orderLink": null, "link": null, "emailRequired": true }, { "format": "PdfGewaarmerkt", "price": 845, "orderLink": null, "link": null, "emailRequired": true } ], "fromPrice": 345, "productId": "090299cc0b7ac73b", "productCode": "75376498", "boekjaar": null, "boekjaren": null, "deponeringsDatum": "2013-07-17T00:00:00", "tapedForAvg": true, "concernNaam": null, "links": [] }, { "productName": "Akte van oprichting", "productUrl": null, "orderLink": null, "deliveryVariants": [ { "format": "PdfNietGewaarmerkt", "price": 345, "orderLink": null, "link": null, "emailRequired": true }, { "format": "PdfGewaarmerkt", "price": 845, "orderLink": null, "link": null, "emailRequired": true } ], "fromPrice": 345, "productId": "090299ccda056cfe", "productCode": "13227082", "boekjaar": null, "boekjaren": null, "deponeringsDatum": "1997-06-03T00:00:00", "tapedForAvg": true, "concernNaam": null, "links": [] } ]

Can't represent it as JSON:API standard?

Rezar00 commented 1 year ago

@Data @JsonIgnoreProperties(ignoreUnknown = true) @JsonApiTypeForClass(value = "products") public class Product { private String productName; private String productUrl; private String orderLink; private List deliveryVariants; private int fromPrice; private String productId; private String productCode; @Nullable private Integer boekjaar; private List boekjaren; @JsonFormat(shape = JsonFormat.Shape.STRING) //2008-06-12T00:00:00 private LocalDateTime deponeringsDatum; private Boolean tapedForAvg; @JsonProperty("concernNaam") private String concernName;

@Data
public static class DeliveryVariant {
    private DeliveryFormat format;
    private int price;
    private String orderLink;
    private String link;
    private boolean async;

    @JsonProperty("emailRequired")
    public void setAsync(String required) {
        this.async = StringUtils.equalsIgnoreCase("yes", required);
    }
}

}

this is my product class

Rezar00 commented 1 year ago

@Data @JsonIgnoreProperties(ignoreUnknown = true) public class AvailableProductsResponse { @JsonProperty("number") public String number; public List products;

}

toedter commented 1 year ago

You have to use the builder differently and return the result of the builder, like

final JsonApiModelBuilder jsonApiModelBuilder =  jsonApiModel().model(availableProducts.getProducts());
return ResponseEntity.ok(jsonApiModelBuilder.build());

Hope this helps.

Rezar00 commented 1 year ago

I got: "Content must not be a collection! Use CollectionModel instead!"

toedter commented 1 year ago

Then convert your collection in a collection model, like

final JsonApiModelBuilder jsonApiModelBuilder = 
    jsonApiModel().model(CollectionModel.of(availableProducts.getProducts()));
return ResponseEntity.ok(jsonApiModelBuilder.build());
Rezar00 commented 1 year ago

Now I get :

{ "_embedded": { "products": [ { "productName": "Statutenwijziging", "productUrl": null, "orderLink": null, "deliveryVariants": [ { "format": "PdfNietGewaarmerkt", "price": 345, "orderLink": null, "link": null, "emailRequired": true }, { "format": "PdfGewaarmerkt", "price": 845, "orderLink": null, "link": null, "emailRequired": true } ], "fromPrice": 345, "productId": "090299cc2099d937", "productCode": "75376498", "boekjaar": null, "boekjaren": null, "deponeringsDatum": "2013-10-01T00:00:00", "tapedForAvg": true, "concernNaam": null }, { "productName": "Statutenwijziging", "productUrl": null, "orderLink": null, "deliveryVariants": [ { "format": "PdfNietGewaarmerkt", "price": 345, "orderLink": null, "link": null, "emailRequired": true }, { "format": "PdfGewaarmerkt", "price": 845, "orderLink": null, "link": null, "emailRequired": true } ], "fromPrice": 345, "productId": "090299cc0b7ac73b", "productCode": "75376498", "boekjaar": null, "boekjaren": null, "deponeringsDatum": "2013-07-17T00:00:00", "tapedForAvg": true, "concernNaam": null }, { "productName": "Akte van oprichting", "productUrl": null, "orderLink": null, "deliveryVariants": [ { "format": "PdfNietGewaarmerkt", "price": 345, "orderLink": null, "link": null, "emailRequired": true }, { "format": "PdfGewaarmerkt", "price": 845, "orderLink": null, "link": null, "emailRequired": true } ], "fromPrice": 345, "productId": "090299ccda056cfe", "productCode": "13227082", "boekjaar": null, "boekjaren": null, "deponeringsDatum": "1997-06-03T00:00:00", "tapedForAvg": true, "concernNaam": null } ] } }

But still need to have a : {data[ attributes]} structure. Does it need something else?

toedter commented 1 year ago

You have to provide an accept request header with value application/vnd.api+json, when you do HTTP requests.

Or, if you want it to be the default, use an annotation for your REST Controller like:

@RequestMapping(produces = JSON_API_VALUE)
public YourRestController {
...

Also take a look at the example in this repo., like the the MovieController.

Rezar00 commented 1 year ago

@toedter I got "Failed to write request".

Thanks for your answer. I will check the MovieController