HATEOAS - problem with position of page links and page details - spring-hateoas

I'm starting to learn HATEOAS. In response, I would like to display the page details first, then the links to the pages and then the resources. Unfortunately, everything is displayed in reverse order. How could I put the data of "_links {}" and "page {}' at the beginning of Json response before the "_embedded {}" data. They always go to the end :(
My REST controller:
#RestController
#RequestMapping(value = "/api")
public class WebController {
private static final int DEFAULT_PAGE_NUMBER = 0;
private static final int DEFAULT_PAGE_SIZE = 5;
#Autowired
private AlbumRepository albumRepository;
#Autowired
private AlbumModelAssembler albumModelAssembler;
#GetMapping("/test")
public ResponseEntity<PagedModel<AlbumModel>> getAllAlbums(
#PageableDefault(page = DEFAULT_PAGE_NUMBER, size = DEFAULT_PAGE_SIZE) Pageable pageable,
PagedResourcesAssembler<AlbumEntity> pagedResourcesAssembler) {
Page<AlbumEntity> albumEntities = albumRepository.findAll(pageable);
Link selfLink = new Link(ServletUriComponentsBuilder.fromCurrentRequest().build().toUriString());
PagedModel<AlbumModel> collModel = pagedResourcesAssembler.toModel(albumEntities, albumModelAssembler,selfLink);
return new ResponseEntity<>(collModel, HttpStatus.OK);
}
}
I get the following response:
{
"_embedded": {
"albums": [
{
"title": "Top Hits Vol 1",
"description": "Top hits vol 1. description",
"releaseDate": "10-03-1981"
},
{
"title": "Top Hits Vol 2",
"description": "Top hits vol 2. description",
"releaseDate": "10-03-1982"
},
{
"title": "Top Hits Vol 3",
"description": "Top hits vol 3. description",
"releaseDate": "10-03-1983"
},
{
"title": "Top Hits Vol 4",
"description": "Top hits vol 4. description",
"releaseDate": "10-03-1984"
},
{
"title": "Top Hits Vol 5",
"description": "Top hits vol 5. description",
"releaseDate": "10-03-1985"
}
]
},
"_links": {
"first": {
"href": "http://localhost:8080/api/test?page=0&size=5"
},
"self": {
"href": "http://localhost:8080/api/test"
},
"next": {
"href": "http://localhost:8080/api/test?page=1&size=5"
},
"last": {
"href": "http://localhost:8080/api/test?page=1&size=5"
}
},
"page": {
"size": 5,
"totalElements": 10,
"totalPages": 2,
"number": 0
}
}

In general, RFC 8259 states the following:
JSON parsing libraries have been observed to differ as to whether or not they make the ordering of object members visible to calling software. Implementations whose behavior does not depend on member ordering will be interoperable in the sense that they will not be affected by these differences.
which does not recommend to depend on a certain given order structure. Certain implementations do i.e use a HashMap internally that is not able to retain the desired order.
If you use Jackson as your de/serialization framework you can use an annotation like
#JsonPropertyOrder({ "id", "title", "artist", "description", "releaseDate" })
public class AlbumEntity {
...
}
to return the data in the desired order on entity classes you want to return. I'm currently though not aware how you can convince Spring to generate a HAL response representation that aligns the elements (i.e. _links, embedded, ...) in the desired order.
However, even if you i.e. invoke an endpoint via curl, that may yield the desired order, and pipe the output to a pretty-printing tool like json_pp on a Mac OS X system, the order is shingled again, as it doesn't respect the given order. The point here is, every part in the chain needs to preserve the order of the JSON document otherwise the ordering of the content can not be guaranteed.

Related

Salesforce flow json to collection variable

I am receiving data from an external api in apex class. I am using a wrapper class to pass this data to salesforce screen flow. In debug the flow is showing that it receives data from the apex class. How can I convert this data to a collection variable to show in flow data table. In data table I tried to create new resource and tried to assign Apex defined variable but whenever I checked Multiple Values the data table just rejects it and no resource is shown in the resource. This is the data I am trying to show in data table.
{
"Product_Catagories": [{
"id": "8ad08aef8534de220185400383d82def",
"name": "Product One",
"description": "Desc One",
"region": "",
"category": "Main Products",
"ProductFamily": "Main",
"RelatedProducts": "POC-B0000001",
"productfeatures": []
}, {
"id": "8ad0887e8534de2701853fff5a9b22ee",
"name": "Product Two",
"description": "Desc Two",
"region": "",
"category": "Main Products",
"ProductFamily": "Main",
"RelatedProducts": "POC-B0000002",
"productfeatures": []
}, {
"id": "8ad08aef8534de2201853ffe48fc08f6",
"name": "Product Three",
"description": "Desc Three",
"region": "",
"category": "Main Products",
"ProductFamily": "Main",
"RelatedProducts": "POC-B0000003",
"productfeatures": []
}]
}
Show some code, not just the message you got.
You're using https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_InvocableMethod.htm right?
3rd example on that page shows how to return a list of helper class objects, public static List <Results> execute. So what you need is something like
public static List<CategoryWrapper> execute(List<Id> ids){
String response = new Http().send(yourRequestBuilder(ids)).getBody();
if(String.isNotBlank(response)){
Wrapper w = (Wrapper) JSON.deserialize(response, Wrapper.class); // your "main" helper class
if(w.Product_Catagories != null){
return w.ProductCatagories; // return to flow the array inside, not the whole object
}
}
return null; // something went wrong, or maybe throw exception
}
There may even be a code-free way to do it, we don't know what your API is. https://help.salesforce.com/s/articleView?id=sf.flow_http_callout.htm&type=5

Spring - Ignore JSON properties in request but not in response

This is a valid Entry object retrieved from a GET request http://domain/entry/{id}:
{
"id": 1,
"description": "ML Books",
"dueDate": "2017-06-10",
"paymentDate": "2017-06-10",
"note": "Distribution de lucros",
"value": 6500,
"type": "INCOME",
"category": {
"id": 2,
"name": "Food"
},
"person": {
"id": 3,
"name": "User",
"active": true,
"address": {
// properties suppressed for better reading
}
}
}
In a POST request I want to save the foreing objects Category and Person just sending the respective Id's, like this:
{
"description": "NEW ENTRY",
"dueDate": "2019-06-22",
"paymentDate": "2019-06-22",
"note": "Coloured pens",
"value": 10,
"type": "INCOME",
"categoryId": 5,
"personId": 5
}
To save the objects without Spring saying the person and category objects were null, I've added #JsonIgnore to them in the model, and followed this thread.
It partially worked:
now it saves de object just with the Id
but not retrieves the object in GET requests
Now, when retrieving a Entry with the same GET request http://domain/entry/{id}:
{
"id": 23,
"description": "Pens",
"dueDate": "2019-06-22",
"paymentDate": "2019-06-22",
"note": "Coloured pens",
"value": 10,
"type": "INCOME",
"categoryId": null, // It supposed to bring the entire object
"personId": null // It supposed to bring the entire object
}
PS: categoryId and personId are marked with #Transient, that's why it are null.
So as the title states, I want to ignore the properties Category and Person just in POST request (saving them), not in GET requests (retrieving them).
Any help will be welcome.
Thanks in advance
Jackson have added READ_ONLY and WRITE_ONLY annotation arguments for JsonProperty. So you could also do something like:
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
#Id
#GeneratedValue
private Long id;

How do i document optional RESTful JSON API attributes?

I've been trying to figure out how to design the documentation for an API I'm building. I've been using Swagger (swagger.io) along with JSONSchema to help me structure the documentation, but I've run into a snag. Some of our object will have lots of what we call "metadata" attached to them, which is basically an arbitrary and variable dictionary of key/values. For example:
{
"id": "aabbbccdd",
"name": "Object 1",
"metadata": {
"attributeA": "This is a text attribute",
"attributeB": {
"key1": "complex attribute",
"key2": "complex attribute 2",
},
"attributeC": 1234
}
},
{
"id": "eeffffggghh",
"name": "Object 2",
"metadata": {
"attributeA": "This is a text attribute with a different value",
"attributeD": "Another text attribute",
"attributeE": True
}
}
So I could document the object representation by enumerating all the metadata attributes, but the list is long and will likely vary over time.
Is there another way to approach documenting this, or designing the API in a different way?

JSON Slurper Offsets

I have a large JSON file that I'm trying to parse with JSON Slurper. The JSON file consists of information about bugs so it has things like issue keys, descriptions, and comments. Not every issue has a comment though. For example, here is a sample of what the JSON input looks like:
{
"projects": [
{
"name": "Test Project",
"key": "TEST",
"issues": [
{
"key": "BUG-1",
"priority": "Major",
"comments": [
{
"author": "a1",
"created": "d1",
"body": "comment 1"
},
{
"author": "a2",
"created": "d2",
"body": "comment 2"
}
]
},
{
"key": "BUG-2",
"priority": "Major"
},
{
"key": "BUG-3",
"priority": "Major",
"comments": [
{
"author": "a3",
"created": "d3",
"body": "comment 3"
}
]
}
]
}
]
}
I have a method that creates Issue objects based on the JSON parse. Everything works well when every issue has at least one comment, but, once an issue comes up that has no comments, the rest of the issues get the wrong comments. I am currently looping through the JSON file based on the total number of issues and then looking for comments using how far along in the number of issues I've gotten. So, for example,
parsedData.issues.comments.body[0][0][0]
returns "comment 1". However,
parsedData.issues.comments.body[0][1][0]
returns "comment 3", which is incorrect. Is there a way I can see if a particular issue has any comments? I'd rather not have to edit the JSON file to add empty comment fields, but would that even help?
You can do this:
parsedData.issues.comments.collect { it?.body ?: [] }
So it checks for a body and if none exists, returns an empty list
UPDATE
Based on the update to the question, you can do:
parsedData.projects.collectMany { it.issues.comments.collect { it?.body ?: [] } }

denormalizing JSON for mongoDB

I think that's the word I'm looking for. I'm trying to get parent info into each of the cards. I think that's what I need to do, but chime in if you have any other ideas.
{
"LEA": {
"name": "Limited Edition Alpha",
"code": "LEA",
"releaseDate": "1993-08-05",
"border": "black",
"type": "core",
"cards": [
{"name": "Air Elemental"},
{"name": "Earth Elemental"},
{"name": "Fire Elemental"},
{"name": "Water Elemental"}
]
},
"LEB": {
"name": "Limited Edition Beta",
"code": "LEB",
"releaseDate": "1993-10-01",
"border": "black",
"type": "core",
"cards": [
{"name": "Armageddon"},
{"name": "Fireball"},
{"name": "Swords to Plowshares"},
{"name": "Wrath of God"}
]
}
}
This is a tiny subset of the data, obviously. LEA and LEB are sets of cards, and inside each set there are a bunch of cards. I'm thinking of denormalizing this into just the cards, with the set info added to each card. Something like this...
{
{
"name": "Air Elemental",
"set": {
"name": "Limited Edition Alpha",
"code": "LEA",
"releaseDate": "1993-08-05",
"border": "black",
"type": "core"
}
},
{
"name": "Earth Elemental",
"set": {
"name": "Limited Edition Alpha",
"code": "LEA",
"releaseDate": "1993-08-05",
"border": "black",
"type": "core"
}
},
{
"name": "Armageddon",
"set": {
"name": "Limited Edition Beta",
"code": "LEB",
"releaseDate": "1993-10-01",
"border": "black",
"type": "core"
}
},
{
"name": "Fireball",
"set": {
"name": "Limited Edition Beta",
"code": "LEB",
"releaseDate": "1993-10-01",
"border": "black",
"type": "core"
}
}
}
Is my thinking right, first and foremost? Would I want a giant collection of cards and have the set information flattened into each card? In SQL, I'd do a table for the sets, and and the cards would belong_to a set. I'm trying to wrap my head around 'document thinking'.
Second, if my thinking is correct, any ideas on how I could achieve this denormalizing?
Here you go =).
OK here is where I would start. Since we've said that cards will never change (since they're based on physical MTG cards), create one collection with all of your cards in it, this will be used for easily populating a user's deck later on. You can search on it by card name or some sort of card ID (like a physical one, stored on the card).
For the user's array of card objects, you shouldn't just store the _id field for a card, because that forces you to join. Since cards will never change, completely denormalize them and just shove them in that card array, so a user object, so far, resembles:
{
name: "Tom Hanks",
skill_level: 0,
decks: [
[
{
card_name: "Balance",
card_description: "LONG_BLOCK_OF_DESCRIP_TEXT",
card_creator: "Sugargirl14",
type: "Normal",
_id: $SOME_MONGO_ID_HERE,
... rest of card data...
}, {
...card 2 complete data...
}
],
[
{ ...another deck here... }
]
]
}
OK, back to set info, I will also assume set info is a constant (based on your SO post, I can't see how it would physically change). So, if that set info is always relevant to the card, I would denormalize and include it, changing our card object to:
{
card_name: "Balance",
card_description: "LONG_BLOCK_OF_DESCRIP_TEXT",
card_creator: "Sugargirl14",
type: "Normal",
_id: $SOME_MONGO_ID_HERE
set: {
"name": "Limited Edition Alpha",
"code": "LEA",
"releaseDate": "1993-08-05",
"border": "black",
"type": "core",
"_id": $SOME_MONGO_ID_HERE
},
... rest of card data...
}
I imagine that storing the other cards in the denormalized object for a given card isn't relevant, if it is, add them. If you'll note, the key that is given in your SO example is dropped, since it seems to always == the "code" field.
OK, now to properly answer your SO question about whether you should embed sets in cards, or vice versa. First off, both collections are relevant. So, even if we embed sets into cards, you'll want those sets in a collection so they can be fetched later and inserted into new cards.
Which gets embedded in which is really determined by business logic, how the data is used and which gets pulled more often. Are you frequently displaying sets and pulling cards from them (like for users to search)? You could embed all of the card data, or any relevant data, in each set's cards array. But with the above data model, each card stores its set ID in its set object. I assume cards belong to only one set, so to get all cards for a set you can query over your card collection where set.id == the Mongo ID of the set you want. Now sets need minimal updates, due to business logic, (hopefully none at all) and your queries are still fast (and you get complete card objects). I'd, honestly, do that latter one and keep my sets clean of cards. As such, a card owns the set it belongs to as opposed to a set owning a card. That's a more SQLy way to think that actually can work fine in Mongo (you'll never join).
So our final data model resembles:
Collection 1, Set:
//data model
{
"name": "Limited Edition Alpha",
"code": "LEA",
"releaseDate": "1993-08-05",
"border": "black",
"type": "core",
"_id": $SOME_MONGO_ID_HERE
}
Collection 2, cards:
//data model
{
_id: $SOME_MONGO_ID_HERE
card_name: "Balance",
card_description: "LONG_BLOCK_OF_DESCRIP_TEXT",
card_creator: "Sugargirl14",
type: "Normal",
set: {
"name": "Limited Edition Alpha",
"code": "LEA",
"releaseDate": "1993-08-05",
"border": "black",
"type": "core",
"_id": $SOME_MONGO_ID_HERE
... rest of card data...
},
}
Collection 3, users:
{
_id: $SOME_MONGO_ID_HERE,
name: "Tom Hanks",
skill_level: 0,
decks: [
[
{
card_name: "Balance",
card_description: "LONG_BLOCK_OF_DESCRIP_TEXT",
card_creator: "Sugargirl14",
type: "Normal",
_id: $SOME_MONGO_ID_HERE,
set: {
"name": "Limited Edition Alpha",
"code": "LEA",
"releaseDate": "1993-08-05",
"border": "black",
"type": "core",
"_id": $SOME_MONGO_ID_HERE
},
}, {
...card 2 complete data...
}
],
[
{ ...another deck here... }
]
]
}
This, obviously, assumes set data for each card is relevant to the user. Now your data is denormalized, sets and cards rarely need updates (according to business logic), so you'll never need cascading updates or deletes. Manipulating users is easy. When you remove a card from a user's deck you can do a $pull from Mongo (I think that's what it's called) on the relevant decks array where a contained item's _id field == the Mongo ID of the card you want to remove. All other updates are easier.
In retrospect, you might want to make the user's decks like so:
decks: {
"SOME_ID_HERE": [
{ ...card 1... },
{ ...card 2... }
]
}
This makes identifying the decks MUCH easier and will make your pulls easier (you'll have more data on the frontend and the pull query will be more precise). It can be a number, random string, anything really, since it gets passed back to the frontend. Or just use their Mongo ID, when looking at a deck, a user will have it's Mongo ID. Then when they pull a card out of it, or add one in, you have a direct identifier to easily grab the deck needed.
Obviously all values with text like: $MONGO_ID_HERE should really be MongoId() objects.
Whew, that was intense, 6800 characters. Hope it makes sense to you and I apologize if any verbiage is confusing or if any of my JSON objects' formatting is fucked up (just let me know if any prose is confusing, I'll reword). Does this make sense/solve your problem?