Spring boot - Customize order of properties in response - spring-boot

I have a base model class with createdAt and updatedAt properties, and a user model extending that class, when spring returns the json, it puts the createdAt / updatedAt properties first, which isn't breaking anything, just super weird to look at, so I end up with something like this:
{
"createdAt": "2022-07-14T06:31:04.933027-04:00",
"updatedAt": "2022-07-14T06:31:04.933027-04:00",
"id": 1,
"email": "foo#bar.com",
"firstName": "Foo",
"lastName": "Bar"
}
Ideally, I'd like it to look like this if possible / be able to customize the order:
{
"id": 1,
"email": "foo#bar.com",
"firstName": "Foo",
"lastName": "Bar",
"createdAt": "2022-07-14T06:31:04.933027-04:00",
"updatedAt": "2022-07-14T06:31:04.933027-04:00"
}

I was facing the exactly same issue just as you do - I wanted the createdDate/updateDate from base class and only want them shown at the bottom.
annotation JsonPropertyOrder is the answer.
option 1) specify each property name in desired order in this annotation(put on class level), i.e.:
#JsonPropertyOrder(value = {"id", "email", "firstName", "lastName", "createdAt", "updatedAt"})
public class SubClass {
}
however, it's not good practice to hardcode property names as plain text, which lead to unexpected order in case you renamed an property but not updated the hardcoded name to be the same.
option 2) as this answer says https://stackoverflow.com/a/70165014/6228210, use the annotation JsonProperty to customize the order of each property. I verified it works very well.
see the sample(copied from that answer):
class animal {
#JsonProperty(index=2)
int p1;
#JsonProperty(index=3)
int p2;
}
class cat extends animal{
#JsonProperty(index=1)
int p3;
}
I personally believe this is better option.
to improve it a bit, I would suggest specifying the index this way, like 5, 10, 15, 20... for sub class and a big number 99 and 100 for base class properties, which makes it easy for you to insert new properties in the future and won't mess up the orders.

Normally the order should be the same as the model. If you want to change the order, do it in the model.

Related

Why does Upserting with HotChocolate no longer work

I recently upgraded HotChocolate.AspNetCore from 11.0.8 -> 11.3.8 and started running into an issue. As a simple example, say I have a Book and a Page entity, with records defined like so:
public record AddBookInput(string Title, AddPageInput[] pages);
public record AddPageInput(string Text)
public record UpdateBookInput(Guid Id, string Title, UpdatePageInput[] pages);
public record UpdatePageInput(Guid Id, string Text)
Previously, when updating a Book, I could pass both existing and new Page entities, and the new Pages would be added (even though the input specifies an UpdatePageInput):
{
"id": "123",
"title": "Updated Title",
"pages": [
{ id: "456", text: "Updated Text" },
{ text: "New Page" }
]
}
But now, this results in an error on the "pages" variable that "The field 'id' is required and must be set".
Does anyone know the underlying change behind this, or if there's a workaround besides defining separate "pages" parameters for add and update?

How to check if many-to-many list is empty with django-filter?

I am using django-rest-framework with django-filter to implement filtering.
Let's say I have following result:
{
"id": 13,
"created": "2017-06-21T01:08:49.790254Z",
"updated": "2017-07-21T10:25:51.706730Z",
"toylist": [],
}
How do I implement filtering so I can check if the toylist array is empty? For example, something like: /toys/?toylist__isnull=True
Ok, it was a relatively simple fix:
class ToysFilter(filters.FilterSet):
toylist__isnull = filters.BooleanFilter(name='toylist', method='list_is_empty')
class Meta:
model = Toys
fields = {
'id':['exact'],
'created':'__all__',
'updated':'__all__',
}
def list_is_empty(self, qs, name, value):
isnull = not value
lookup_expr = LOOKUP_SEP.join([name, 'isnull'])
return qs.filter(**{lookup_expr: isnull}).distinct()
It isn't necessary to use a method here. More simply, you could do the following:
class ToysFilter(filters.FilterSet):
toylist__isnull = filters.BooleanFilter(name='toylist', lookup_expr='isnull', distinct=True)

how do you access the values in a couchbase view?

I have a widget.json file which is loaded into a document in couchbase:
{
"type": "widget",
"name": "clicker",
"description": "clicks!"
}
I also have a couchbase design document, couchbase.ddoc, for a bucket. It is registered with the name "views":
{
"_id": "_design/accesscheck",
"language": "javascript",
"views": {
"all_widgets": {
"map": "function(doc, meta) { if(doc.type == 'widget') { emit(meta.id, doc.name); } }"
}
}
}
and some golang code, using the couchbase golang API, to fetch it:
opts := map[string]interface{}{
"stale": false
}
data, _ := bucket.View("views", "all_widgets", opts)
and at this point i still have A Few Questions:
what is the best way to determine what i can do with the "data" variable? I suspect it's a list of integer-indexed rows, each of which contains a key/value map where the value can be of different types. I see plenty of trivial examples for map[string]interface{}, but this seems to have an additional level of indirection. Easily understandable in C, IMHO, but the interface{}{} is puzzling to me.
probably an extension of the above answer, but how can i effectively search using the design document? I would rather have the Couchbase server doing the sifting.
some of the Get() examples pass in a struct type i.e.
type User struct {
Name string `json:"name"`
Id string `json:"id"`
}
err = bucket.Get("1", &user)
if err != nil {
log.Fatalf("Failed to get data from the cluster (%s)\n", err)
}
fmt.Printf("Got back a user with a name of (%s) and id (%s)\n", user.Name, user.Id)
Is it possible to do something like this with a View()?
does somebody know of a good way to flexibly hook the View mechanism into a net/http REST handler? Just hoping..
I didn't find these questions covered in the golang client API examples or documentation. I probably missed something. If someone has links please let me know.
Thanks for any help!
Customize View Result In Go
If you are using github.com/couchbaselabs/go-couchbase, you can use bucket.ViewCustom to solve your problem. It accepts a item which view result parsed to.
The bucket.View is just calling bucket.ViewCustom with predefined struct and returns it.
An executed view result is a json object like below;
{
"total_rows": 123,
"rows": [
{
"id": "id of document",
"key": {key you emitted},
"value": {value you emitted},
"doc": {the document}
},
...
]
}
As we are know this structure, we can set struct type manually to bucket.ViewCustom.
In your case, you can write custom view struct for "all_widgets" like this;
type AllWidgetsView struct {
Total int `json:"total_rows"`
Rows []struct {
ID string `json:"id"` // document id
Key string `json:"string"` // key you emitted, the 'meta.id'
Value string `json:"value"` // value you emitted, the 'doc.name'
} `json:"rows"`
}
And use bucket.ViewCustom to retrieve the value.
var result AllWidgetsView
opts := map[string]interface{}{}
viewErr := bucket.ViewCustom("views", "all_widgets", opts, &result)
if viewErr != nil {
// handle error
}
If this pattern of code appears frequently, you can refactor it.
Effective searching
For the question 2, it's not related to golang but Couchbase itself.
View has some feature that bucket has not. View results are sorted by key so you can specify startkey option to where the result start. You can use this attribute and make view for searching. See Querying views for more information.
But you need more detailed search like full-text search, you should use ElasticSearch plugin or N1QL to do it.
(note the N1QL is preview and not officially released yet)

OData v4 Web API 2.2 deep level expand not working

The situation
I'm trying to expand "Item" to three levels:
Item.Product.Model.Type
So I call this nested query options url:
http://xxx/api/Items?$expand=Product($expand=Model($expand=Type))
I Get a warning that the max depth of 2 has been reached so I set the suggested MaxExpansionDepth attribute to 3. But then, the "Type" property is not returned! This is covered by this SO question
Then I look at the official OData V4 standard and it says I should use slashes in the expand option, like so:
http://xxx/api/Items?$expand=Product/Model/Type
But that gives me an error telling:
The query specified in the URI is not valid. Found a path traversing multiple navigation properties. Please rephrase the query such that each expand path contains only type segments and navigation properties.
Which this SO answer covers but the answer is contradictory to the official OData doc. What does that even mean anyway.
The question
What is the official, standard and working way of using the $expand query option for deep levels with OData v4 and Web API 2.2
After working with Jerther in chat, we narrowed the problem down to the expanded properties not being marked as contained navigations. As a result, the OData framework was removing them since they did not have corresponding entity sets defined. Updating the model to specifically declare the containment appears to have solved the problem.
Containment can be specified in a couple of ways, depending on what model builder is being used. In the case of the ODataConventionModelBuilder you can add the System.Web.OData.Builder.ContainedAttribute to the property in question, while for the ODataModelBuilder you can use the ContainsMany<T> method on the EntityTypeConfiguration instance for the containing class.
Also, at the moment, a cascaded expand will stop where a complex type contains an Entity type.
UPDATE:
Defining all types in the chain as EntitySet works.
builder.EntitySet<Item>("Items");
builder.EntitySet<Product>("Products");
builder.EntitySet<Model>("Models");
builder.EntitySet<Type>("Types");
It seems defining them as EntityType isn't sufficient.
see here: https://github.com/OData/WebApi/issues/226
Original Answer
I tried reproing your situation and couldn't. Is it possible that "Types" isn't being set in your action? Here was my little repro
public class ItemsController : ODataController
{
[HttpGet]
[EnableQuery(MaxExpansionDepth = 10)]
[ODataRoute("/Items")]
public IHttpActionResult GetItems()
{
return this.Ok(CreateItem());
}
private Item CreateItem()
{
return new Item
{
Id = 1,
Products = new Product[]
{
new Product
{
Id = 2,
Models = new Model[]
{
new Model
{
Id = 3,
Types = new MyType[]
{
new MyType
{
Id = 4,
},
},
},
},
},
},
};
}
}
Which when called with /Items?$expand=Products($expand=Models($expand=Types)) resulted in the following:
{
"#odata.context": "http://localhost:9001/$metadata#Items/$entity",
"Id": 1,
"Products#odata.context": "http://localhost:9001/$metadata#Items(1)/Products",
"Products": [{
"Id": 2,
"Models#odata.context": "http://localhost:9001/$metadata#Items(1)/Products(2)/Models",
"Models": [{
"Id": 3,
"Types#odata.context": "http://localhost:9001/$metadata#Items(1)/Products(2)/Models(3)/Types",
"Types": [{
"Id": 4
}]
}]
}]
}

MVC 1.0 DropDownList static data select not working

I'm new to MVC and C#. I'm trying to get a static list to work with a DropDownList control such that the selected value rendered is set by the current Model value from the DB.
In the controller, I have:
ViewData["GenderList"] = new SelectList(new[] { "Female", "Male", "Unknown" }, donor.Gender);
In the view:
Gender:<%=Html.DropDownList("Gender", (IEnumerable<SelectListItem>)ViewData["GenderList"]) %>
In the debugger, donor.Gender is "Male", but "Female" gets rendered in the view.
I've read a number of posts related to select, but I've not found one that applies to a static list (e.g., where there's no "value" and "name" to play with). I'm probably doing something stupid...
This may sound like a stupid question but is donor.Gender a string value, and does it match case with the hard-coded values you've used EG 'Male'?
The reason I ask is because this;
ViewData["GenderList"] = new SelectList(new[] { "Female", "Male", "Unknown" }, "Male");
works a treat, but this;
ViewData["GenderList"] = new SelectList(new[] { "Female", "Male", "Unknown" }, "male");
returns your result
Thanks to Si, here's the final solution I came up with:
In the controller, this:
ViewData["GenderList"] = repo.GetGenders(donor.Gender);
In the DonorRepository, this:
public SelectList GetGenders(string selected) {
SelectList genders = new SelectList(new[] { "Female ", "Male ", "Unknown" }, (selected == null ? null : selected.ToString().PadRight(7).ToCharArray()));
return (genders);
}
Then in the View, just this:
<%= Html.DropDownList("Gender", (IEnumerable<SelectListItem>)ViewData["GenderList"], "--Select--")%>
NOTE: PadRight(7) equals the Donor.Gender DB specification of Char(7). Also note the SelectList constant space padding of "1234567" for each selectlistitem.

Resources