laravel
laravel copied to clipboard
Record object appears in "data" and "included" simountaneously
Version: v1.0.0
Bug description:
I have a recursive (tree) hierarchy of categories. So I have "Category" model and associated "categories" JSON:API resource. The database structure is very simple. I have just these columns: id, name, parent_id (nullable). The "parent_id" contans the ID of the parent category or it's null if the category has no parent (root category). On the Category model I have "parent" relationship. Now the bug... Imagine I have these 3 categories: A (id 1), B (id 2) and C (id 3). B is the child of C and C is the child of A. So the tree would look like this A -> C -> B. When I hit my API with the following URL .../api/categories?include=parent I got the response. In the "data" field of the response all 3 resources (categories) are presend with correct parent relationship. The problem is, that ONLY category C is present in the "included" field of the response. I mean... The C category is presend in "data" and in "included" as well. I was playing with this for a while and I found out that it's because of the IDs. It seems like the library is going through categories 1, 2, 3 and it pushes "missing" category to "included" field. So ti parses category A (id 1) at first. There is no parent so it continues to B (id 2). B is the child of C (and we haven't parse C yet...) so it pushes the C to "included". Finally if parses C and pushes it to "data" as well.
Im not 100% sure what official JSON:API spec is saying about such a scenario, but I think that one record should NOT be present in "data" and in "included" as well. In the screenshot, category with id 42 is present in both.
Also notice, that the values of attributes are present in the response twice. In this case the string "Nitrogen generator" is present in data and in included. If there would be let's say 50 attributes, the same data would be present twice in the response for no reason and the response would be too big for no good reason.
Hi. Please can you provide the JSON as properly formatted text? I find that image exceptionally difficult to read.
@lindyhopchris It's a little bit different response, but the principle is the same. The resource object with type product-categories
and id 36
is present in included
and in data
as well.
{
"jsonapi": {
"version": "1.0"
},
"data": [
{
"type": "product-categories",
"id": "34",
"attributes": {
"name": "Generator",
"createdAt": "2021-09-09T12:23:55.000000Z",
"updatedAt": "2021-09-10T13:39:03.000000Z"
},
"relationships": {
"parentCategory": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/34/parent-category",
"self": "http://localhost:8080/api/jsonapi/product-categories/34/relationships/parent-category"
},
"data": null
},
"childCategories": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/34/child-categories",
"self": "http://localhost:8080/api/jsonapi/product-categories/34/relationships/child-categories"
}
},
"productTypes": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/34/product-types",
"self": "http://localhost:8080/api/jsonapi/product-categories/34/relationships/product-types"
}
},
"componentDependencies": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/34/component-dependencies",
"self": "http://localhost:8080/api/jsonapi/product-categories/34/relationships/component-dependencies"
}
},
"productAttributeDefinitions": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/34/product-attribute-definitions",
"self": "http://localhost:8080/api/jsonapi/product-categories/34/relationships/product-attribute-definitions"
}
},
"productTypeAttributeDefinitions": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/34/product-type-attribute-definitions",
"self": "http://localhost:8080/api/jsonapi/product-categories/34/relationships/product-type-attribute-definitions"
}
}
},
"links": {
"self": "http://localhost:8080/api/jsonapi/product-categories/34"
}
},
{
"type": "product-categories",
"id": "37",
"attributes": {
"name": "Modular",
"createdAt": "2021-09-09T12:24:11.000000Z",
"updatedAt": "2021-09-10T13:39:10.000000Z"
},
"relationships": {
"parentCategory": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/37/parent-category",
"self": "http://localhost:8080/api/jsonapi/product-categories/37/relationships/parent-category"
},
"data": {
"type": "product-categories",
"id": "36"
}
},
"childCategories": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/37/child-categories",
"self": "http://localhost:8080/api/jsonapi/product-categories/37/relationships/child-categories"
}
},
"productTypes": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/37/product-types",
"self": "http://localhost:8080/api/jsonapi/product-categories/37/relationships/product-types"
}
},
"componentDependencies": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/37/component-dependencies",
"self": "http://localhost:8080/api/jsonapi/product-categories/37/relationships/component-dependencies"
}
},
"productAttributeDefinitions": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/37/product-attribute-definitions",
"self": "http://localhost:8080/api/jsonapi/product-categories/37/relationships/product-attribute-definitions"
}
},
"productTypeAttributeDefinitions": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/37/product-type-attribute-definitions",
"self": "http://localhost:8080/api/jsonapi/product-categories/37/relationships/product-type-attribute-definitions"
}
}
},
"links": {
"self": "http://localhost:8080/api/jsonapi/product-categories/37"
}
},
{
"type": "product-categories",
"id": "36",
"attributes": {
"name": "Nitrogen generator",
"createdAt": "2021-09-09T12:24:07.000000Z",
"updatedAt": "2021-09-10T13:39:07.000000Z"
},
"relationships": {
"parentCategory": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/36/parent-category",
"self": "http://localhost:8080/api/jsonapi/product-categories/36/relationships/parent-category"
},
"data": {
"type": "product-categories",
"id": "34"
}
},
"childCategories": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/36/child-categories",
"self": "http://localhost:8080/api/jsonapi/product-categories/36/relationships/child-categories"
}
},
"productTypes": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/36/product-types",
"self": "http://localhost:8080/api/jsonapi/product-categories/36/relationships/product-types"
}
},
"componentDependencies": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/36/component-dependencies",
"self": "http://localhost:8080/api/jsonapi/product-categories/36/relationships/component-dependencies"
}
},
"productAttributeDefinitions": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/36/product-attribute-definitions",
"self": "http://localhost:8080/api/jsonapi/product-categories/36/relationships/product-attribute-definitions"
}
},
"productTypeAttributeDefinitions": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/36/product-type-attribute-definitions",
"self": "http://localhost:8080/api/jsonapi/product-categories/36/relationships/product-type-attribute-definitions"
}
}
},
"links": {
"self": "http://localhost:8080/api/jsonapi/product-categories/36"
}
},
{
"type": "product-categories",
"id": "35",
"attributes": {
"name": "Oxygen generator",
"createdAt": "2021-09-09T12:24:02.000000Z",
"updatedAt": "2021-09-10T13:39:13.000000Z"
},
"relationships": {
"parentCategory": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/35/parent-category",
"self": "http://localhost:8080/api/jsonapi/product-categories/35/relationships/parent-category"
},
"data": {
"type": "product-categories",
"id": "34"
}
},
"childCategories": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/35/child-categories",
"self": "http://localhost:8080/api/jsonapi/product-categories/35/relationships/child-categories"
}
},
"productTypes": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/35/product-types",
"self": "http://localhost:8080/api/jsonapi/product-categories/35/relationships/product-types"
}
},
"componentDependencies": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/35/component-dependencies",
"self": "http://localhost:8080/api/jsonapi/product-categories/35/relationships/component-dependencies"
}
},
"productAttributeDefinitions": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/35/product-attribute-definitions",
"self": "http://localhost:8080/api/jsonapi/product-categories/35/relationships/product-attribute-definitions"
}
},
"productTypeAttributeDefinitions": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/35/product-type-attribute-definitions",
"self": "http://localhost:8080/api/jsonapi/product-categories/35/relationships/product-type-attribute-definitions"
}
}
},
"links": {
"self": "http://localhost:8080/api/jsonapi/product-categories/35"
}
}
],
"included": [
{
"type": "product-categories",
"id": "36",
"attributes": {
"name": "Nitrogen generator",
"createdAt": "2021-09-09T12:24:07.000000Z",
"updatedAt": "2021-09-10T13:39:07.000000Z"
},
"relationships": {
"parentCategory": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/36/parent-category",
"self": "http://localhost:8080/api/jsonapi/product-categories/36/relationships/parent-category"
}
},
"childCategories": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/36/child-categories",
"self": "http://localhost:8080/api/jsonapi/product-categories/36/relationships/child-categories"
}
},
"productTypes": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/36/product-types",
"self": "http://localhost:8080/api/jsonapi/product-categories/36/relationships/product-types"
}
},
"componentDependencies": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/36/component-dependencies",
"self": "http://localhost:8080/api/jsonapi/product-categories/36/relationships/component-dependencies"
}
},
"productAttributeDefinitions": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/36/product-attribute-definitions",
"self": "http://localhost:8080/api/jsonapi/product-categories/36/relationships/product-attribute-definitions"
}
},
"productTypeAttributeDefinitions": {
"links": {
"related": "http://localhost:8080/api/jsonapi/product-categories/36/product-type-attribute-definitions",
"self": "http://localhost:8080/api/jsonapi/product-categories/36/relationships/product-type-attribute-definitions"
}
}
},
"links": {
"self": "http://localhost:8080/api/jsonapi/product-categories/36"
}
}
]
}
Thanks!
I've noticed this as well and would prefer it only appears once (my vote would be in data
), to save data transfer.
Yeah, get that. This however is really low down on my list to look at because (1) it only happens in very particular circumstances, (2) it's an optimisation not a bug and (3) it's going to be exceptionally complex to fix. (3) being the biggest reason why I can't prioritise it at the moment - my Open Source time is exceptionally limited and this issue will have a huge time impact.
Just being up-front about when this might get sorted out!