Commercetools - Use query predicates to filter on a collection attribute - predicate

I'd like to get all the Category items that have between their ancestors an ancestor with a certain "id".
Here is the JSON of one of the categories returned by GETting from /categories:
{
"id": "4627f3b0-fe52-4cc6-b03e-3fd72e701342",
"version": 1,
"lastMessageSequenceNumber": 1,
"createdAt": "2019-02-18T13:48:51.677Z",
"lastModifiedAt": "2019-02-18T13:48:51.677Z",
"lastModifiedBy": {
"clientId": "_anonymous"
},
"createdBy": {
"clientId": "_anonymous"
},
"key": "snowboard-gloves",
"name": {
"en": "Snowboard Gloves"
},
"slug": {
"en": "snowboard-gloves"
},
"description": {
"en": "Gloves specifically designed for snowboarding"
},
"ancestors": [
{
"typeId": "category",
"id": "b27086d2-33f2-43c3-aad1-4c01b2b9a886"
}
],
"parent": {
"typeId": "category",
"id": "b27086d2-33f2-43c3-aad1-4c01b2b9a886"
},
"orderHint": "0.000016",
"metaTitle": {
"en": "Snowboard Gloves"
},
"metaDescription": {
"en": "Gloves specifically designed for snowboarding"
},
"assets": []
}
I'd like to call the /categories API with a where clause on ancestors[x].id = "b27086d2-33f2-43c3-aad1-4c01b2b9a886" but from the documentation I don't understand how I should write the query predicate.
Can anyone help me?

The query predicate follows the structure of the json response. Nested fields are accessed with () brackets.
Try this out
ancestors(id = "idb27086d2-33f2-43c3-aad1-4c01b2b9a886")

Related

JSON schema validation Draft 7 two type of data for one field

I need help creating a JSON schema for a value that could be an object, or an array of objects.
lib: jsonschema==3.2.0
py: 3.8
I have 2 responses from the server:
first:
{
"result": [
{
"brand": "Test"
}
]}
second:
{
"result":
{
"brand": "Test"
}
}
As you can see the difference between both in the first case its an array of obj the second just object.
my schema:
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://example.com/example.json",
"type": "object",
"required": [
"result"
],
"properties": {
"result": {
"$id": "#/properties/result",
"type": ["array", "object"],
"additionalItems": true,
"items": {
"$id": "#/properties/result/items",
"anyOf": [
{
"$id": "#/properties/result/items/anyOf/0",
"type": "object",
"required": [
"brand"
],
"properties": {
"brand": {
"$id": "#/properties/result/items/anyOf/0/properties/brand",
"type": "string"
}
},
"additionalProperties": true
}
]
}
}
},
"additionalProperties": true}
In the first case when return array, it checks the "brand" type on the second when return object, no.
How I can set up 2 types for one field "result" that it could check the brand type?
Your schema can be fixed as follows:
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://example.com/example.json",
"type": "object",
"required": [
"result"
],
"properties": {
"result": {
"$id": "#/properties/result",
"anyOf": [
{
"$id": "#/properties/result/items/brand",
"type": "object",
"properties": {
"brand": {
"$id": "#/properties/result/items/anyOf/0/properties/brand",
"type": "string"
}
},
"required": [
"brand"
],
"additionalProperties": true
},
{
"$id": "#/properties/result/items/array",
"type": "array",
"items": {
"$ref": "#/properties/result/items/brand"
}
}
]
}
},
"additionalProperties": true
}
Demos here, here and here.
However, it is customary to extract reusable portions of a schema into a separate "definitions" section, like so:
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://example.com/example.json",
"definitions": {
"brand": {
"type": "object",
"properties": {
"brand": {
"$id": "#/properties/result/items/anyOf/0/properties/brand",
"type": "string"
}
},
"required": [
"brand"
],
"additionalProperties": true
}
},
"type": "object",
"required": [
"result"
],
"properties": {
"result": {
"$id": "#/properties/result",
"anyOf": [
{
"$ref": "#/definitions/brand"
},
{
"$id": "#/properties/result/items/array",
"type": "array",
"items": {
"$ref": "#/definitions/brand"
}
}
]
}
},
"additionalProperties": true
}
Demos here, here and here.
Notes:
To express that the property "result" may be of two different types, use the "anyof" keyword for the property's schema. The value of the "anyOf" should be an array with the schemas for each possible type (here the "brand" object or an array of "brand" objects) as the array items.
See: Multiple Types.
To avoid duplicating the definitions for the "brand" object, you can use the "$ref" when defining a schema for the array's items to refer back to the previously given schema for "brand". As noted above it s customary to place reused subschemas into a "definitions" section, but it is not necessary, "$ref" can refer to any schema item via the JSON Pointer syntax.
See: Reuse.
When the items of a list have a single schema, "additionalItems" should not be used.
See: List validation.

Include `meta` member in resource identifiers in `relationships` object

From my understanding of the JSON:API spec (specifically https://jsonapi.org/format/#document-resource-object-linkage) I should be able to include meta members for each member of a relationship.
I have been able to add a hash of meta data to the relationships object itself, but not one to each of the individual relationships within.
class PlanSerializer < ApplicationSerializer
attributes :id, :name
has_many :features do
meta value: "x"
end
end
I know I can use a block syntax for has_many, and think that's the way to achieve this. But I haven't got it working. Calling the meta method within the block adds the meta block to the features relationship object, and I need to add one to each entry in that array.
My questions:
Have I understood the spec correctly? Should I be able to add a meta object to each relationship?
How would I go about doing this with the active model serializers?
Background:
My goal is to represent a many-many from Plans to Features where each plan might have some extra information for it's own relationship to a given Feature (and that information is different for every Plan, so it doesn't belong on the Feature object)
If your answer is that I shouldn't be doing this, that's fine, but please present an alternative which you think is preferred.
// My desired output
{
"data": [
{
"id": "small",
"type": "plans",
"attributes": {
/* Some attributes */
},
"relationships": {
"features": {
"data": [
{
"id": "num-users",
"type": "features",
"meta": {
"value": 1
}
},
{
"id": "num-projects",
"type": "features",
"meta": {
"value": 5
}
}
]
}
}
},
{
"id": "large",
"type": "plans",
"attributes": {
/* Some attributes */
},
"relationships": {
"features": {
"data": [
{
"id": "num-users",
"type": "features",
"meta": {
"value": 5
}
},
{
"id": "num-projects",
"type": "features",
"meta": {
"value": 50
}
},
{
"id": "unlimited-downloads",
"type": "features"
}
]
}
}
}
],
"included": [
{
"id": "num-users",
"type": "features",
"attributes": {
"description": "Number of users"
}
},
{
"id": "num-projects",
"type": "features",
"attributes": {
"description": "Number of projects"
}
},
{
"id": "unlimited-downloads",
"type": "features",
"attributes": {
"description": "Unlimited downloads"
}
}
]
}

JSON:API Matching Collections with its respective Includes

What exactly is the best practice for matching JSON:API data collections with their respective includes. Considering the following code below....
What if I wanted to loop through each venue and display the Owners full information for each Venue Record. Does JSON:API expect me to just search the include array for the matching Owner Record
find(included,data[$i].relationships.owner.data.id);
Would find() loop through the included array to look for the owner that has the matching id as the collection items owner in the relationships object ?
$(data).each(function(item){
var owner = find(included,'owner', item.relationships.owner.data.id)
})
I have not found a resource that explains this or perhapes I am mis understanding the point of json:api. If someone can explain this or point to a resource that relates to my question. I would appreciate it.
{
"links": {
"self": "http://127.0.0.1/api/venues?include=owner"
},
"data": [
{
"id": "5c5b49188fd33c7a989ba9b6",
"type": "venues",
"attributes": {
"name": "Kreiger - Smith",
"address": "69675 Reilly Vista",
"location": {
"type": "Point",
"coordinates": [
-112.110492,
36.098948
]
},
"events": [
{
"_id": "ad52825a8f4812e92f87b8c6",
"name": "Cool Awesome Event!",
"user": "b3daa77b4c04a9551b8781d0",
"id": "ad52825a8f4812e92f87b8c6"
}
],
"created_at": "2019-02-07T14:27:13.207Z",
"updated_at": "2019-02-07T14:27:13.207Z"
},
"relationships": {
"owner": {
"data": {
"id": "b3daa77b4c04a9551b8781d0",
"type": "users"
}
}
}
},
{
"id": "5c5b49188fd33c7a989ba9b7",
"type": "venues",
"attributes": {
"name": "Oberbrunner Inc",
"address": "1132 Kenyon Stravenue",
"location": {
"type": "Point",
"coordinates": [
-112.110492,
36.098948
]
},
"events": [
{
"_id": "ad52825a8f4812e92f87b8c6",
"name": "Cool Awesome Event!",
"user": "b3daa77b4c04a9551b8781d0",
"id": "ad52825a8f4812e92f87b8c6"
}
],
"created_at": "2019-02-07T14:27:13.207Z",
"updated_at": "2019-02-07T14:27:13.207Z"
},
"relationships": {
"owner": {
"data": {
"id": "b3daa77b4c04a9551b8781d0",
"type": "users"
}
}
}
},
{
"id": "5c5b49188fd33c7a989ba9b8",
"type": "venues",
"attributes": {
"name": "Gibson - Muller",
"address": "8457 Hailie Canyon",
"location": {
"type": "Point",
"coordinates": [
-112.110492,
36.098948
]
},
"events": [
{
"_id": "ad52825a8f4812e92f87b8c6",
"name": "Cool Awesome Event!",
"user": "b3daa77b4c04a9551b8781d0",
"id": "ad52825a8f4812e92f87b8c6"
}
],
"created_at": "2019-02-07T14:27:13.208Z",
"updated_at": "2019-02-07T14:27:13.208Z"
},
"relationships": {
"owner": {
"data": {
"id": "a1881c06eec96db9901c7bbf",
"type": "users"
}
}
}
}
],
"included": [
{
"id": "b3daa77b4c04a9551b8781d0",
"type": "users",
"attributes": {
"username": "killerjohn",
"firstname": "John",
"lastname": "Chapman"
}
},
{
"id": "a1881c06eec96db9901c7bbf",
"type": "users",
"attributes": {
"username": "numerical25",
"firstname": "Billy",
"lastname": "Gordon"
}
}
]
}
This is my best possible solution. But is there a better way ? Seems like alot more coding just to find a collections associated included data
axios.get('http://127.0.0.1:3000/api/venues?include=owner').then(function(response) {
var venues = response.data.data;
var data = response.data;
for(x in venues) {
var owner = data.included.find(function(element) {
if(element.id == venues[x].relationships.owner.data.id) {
return element;
}
});
}
});

Acumatica API insert data "Bad Request"

I'm trying to insert a stock item into Acumatica using the API, but I'm getting a 400 error - Bad Request. I'm using HttpClient to login, retrieve a stock item, and send the insert request. All is working except the insert request. I have tried the following url (including expand parameter):
http://localhost/AcumaticaERP/entity/Default/6.00.001/StockItem?$expand=Attributes,CrossReferences,UOMConversions,VendorDetails,WarehouseDetails
... and also the following url (without expand parameter)
http://localhost/AcumaticaERP/entity/Default/6.00.001/StockItem
I'm calling via HttpClient PutAsync, passing in the URLs mentioned above, and the data is JSON from a stock item retrieved with the API, and which doesn't exist in the current db.
client.PutAsync(insertUrl, new StringContent(data, Encoding.UTF8, "application/json")).Result;
Any ideas what I'm missing?
NEW DETAILS:
After further debugging and testing with Postman, the error from the PUT seems to be "Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray. Path '', line 1, position 1." The JSON was taken directly from a GET though, so I'm not sure what the problem could be. Below is the JSON returning an error from the PUT.
[
{
"id": "cc27ce56-6302-4f1b-97aa-49cca3ed32ea",
"rowNumber": 1,
"note": "",
"Attributes": [],
"BaseUOM": {
"value": "EA"
},
"CrossReferences": [],
"DefaultIssueLocationID": {
"value": "R1S1"
},
"DefaultReceiptLocationID": {
"value": "RECEIVING"
},
"DefaultWarehouseID": {
"value": "WHOLESALE"
},
"Description": {
"value": "tonyitem2"
},
"ImageUrl": {},
"InventoryID": {
"value": "TONYITEM2"
},
"IsAKit": {
"value": false
},
"ItemClass": {
"value": "CONSUMER 300TOYS"
},
"ItemStatus": {
"value": "Active"
},
"ItemType": {
"value": "Finished Good"
},
"LastModified": {
"value": "2018-08-03T12:09:19.907-04:00"
},
"LotSerialClass": {
"value": "NOTTRACKED"
},
"PurchaseUOM": {
"value": "EA"
},
"SalesUOM": {
"value": "EA"
},
"UOMConversions": [],
"VendorDetails": [],
"Volume": {
"value": 0
},
"WarehouseDetails": [
{
"id": "3ca5ea4c-c651-498e-8e6c-49119481982c",
"rowNumber": 1,
"note": "",
"DefaultIssueLocationID": {
"value": "R1S1"
},
"DefaultReceiptLocationID": {
"value": "RECEIVING"
},
"IsDefault": {
"value": true
},
"QtyOnHand": {
"value": 0
},
"WarehouseID": {
"value": "WHOLESALE"
},
"custom": {},
"files": []
}
],
"Weight": {
"value": 0
},
"custom": {},
"files": []
}
]
I also tried removing the brackets surrounding the JSON, and then the error is:
"No entity satisfies the condition.". Could the issue be that the ids have GUIDs, but I'm trying to do an insert? How do you indicate that the PUT is supposed to be inserting?
I finally got the PUT to insert. What finally worked was removing the wrapping braces "[" and "]" around the entire JSON; plus removing all "id", "rowNumber", "custom", and "files" fields, and all empty collections (e.g. Attributes, CrossReferences) in my JSON. I'm not sure which of these being removed resolved it and allowed me to insert, but it finally worked.
It's real unfortunate that the JSON you retrieve via GET can't be inserted via PUT without stripping all of this out first though. What a pain.

Speeding up Cloudant query for type text index

We have a table with this type of structure:
{_id:15_0, createdAt: 1/1/1, task_id:[16_0, 17_0, 18_0], table:”details”, a:b, c: d, more}
We created indexes using
{
"index": {},
"name": "paginationQueryIndex",
"type": "text"
}
It auto created
{
"ddoc": "_design/28e8db44a5a0862xxx",
"name": "paginationQueryIndex",
"type": "text",
"def": {
"default_analyzer": "keyword",
"default_field": {
},
"selector": {
},
"fields": [
],
"index_array_lengths": true
}
}
We are using the following query
{
"selector": {
"createdAt": { "$gt": 0 },
"task_id": { "$in": [ "18_0" ] },
"table": "details"
},
"sort": [ { "createdAt": "desc" } ],
"limit”: 20
}
It takes 700-800 ms for first time, after that it decreases to 500-600 ms
Why does it take longer the first time?
Any way to speed up the query?
Any way to add indexes to specific fields if type is “text”? (instead of indexing all the fields in these records)
You could try creating the index more explicitly, defining the type of each field you wish to index e.g.:
{
"index": {
"fields": [
{
"name": "createdAt",
"type": "string"
},
{
"name": "task_id",
"type": "string"
},
{
"name": "table",
"type": "string"
}
]
},
"name": "myindex",
"type": "text"
}
Then your query becomes:
{
"selector": {
"createdAt": { "$gt": "1970/01/01" },
"task_id": { "$in": [ "18_0" ] },
"table": "details"
},
"sort": [ { "createdAt": "desc" } ],
"limit": 20
}
Notice that I used strings where the data type is a string.
If you're interested in performance, try removing clauses from your query one at-a-time to see if one is causing the performance problem. You can also look at the explanation of your query to see if it using your index correctly.
Documentation on creating an explicit text query index is here

Resources