-
Notifications
You must be signed in to change notification settings - Fork 11.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[5.8] Fix how resources handle arrays with mixed numeric and string keys #27394
Conversation
Please give a better summary of breaking changes. I don't understand why "1." is a breaking change at all. Needs much more explanation. Regarding "2.", I don't know what you mean by "actual plain arrays". Also needs much more explanation. |
Hi @taylorotwell , I'll try my best, to clarify the changes, English is not my native language, if any doubts are left please do not hesitate to ask. 1. Change on how arrays were serialized. The previous code on If the first key was numeric it returned the result of This a BC, if we consider the following code: Route::get('/', function () {
return new \Illuminate\Http\Resources\Json\JsonResource([
1 => 10, // note that the first key is_numeric()
2 => 20,
'total' => 30 // this array has a string key, which was ignored before
]);
}); Before we would have: {"data":[10,20,30]} After this PR: {"data":{"1": 10, "2": 20, "total": 30}} This changed beahavior happens as now we use a sentinel to check if the array being evaluated by the As the output changes for the same input before and after this PR, I think it might be documented as a BC. 2. "Plain" arrays By "plain" array, I meant an array that the following expression evaluates to array_values($arr) === $arr; Which means (of course you know), that the array is empty, or that the first key is As internally all PHP arrays are associative, I didn't find a better term to differentiate between arrays that might have non-numeric or non-contiguous keys and the kind of array described above. Considering this definition, this PR introduces the possibility to a user define a Only arrays which the expression above ( So, if you have this situation: Route::get('/', function () {
// MyResource has $preserveKeys = true;
return new MyResource([
'foo' => [10, 20, 30], // array_values($foo) === $foo
'bar' => [1 => 10, 20, 30], // first key is not zero, so array_values($bar) !== $bar
]);
}); The output would be: {"foo":[10, 20, 30], "bar": {"1": 10, "2": 20, "3": 30}} Although weird, this allows the user to use techniques such as described in the Redux docs, which require objects as lookup-tables. Note that this behavior should be explicitly enabled by the user by adding If the |
@rodrigopedra While working on #27950, I noticed that the example from the documentation ("Preserving Collection Keys") doesn't actually work.
How do we fix this? |
I am taking a look right now and, if needed, will open a new PR |
Hi @staudenmeir The problem is when the I think of two approaches:
I will submit method 2 as a new PR, as it seems to me the better one, but please send me your opinions and suggestions. Will also add tests |
Since PR #23414 the way Resources handle payload with numeric keys has changed breaking the way some users expected them to be returned.
After this PR, any array in a Resource that had a numeric value in its first key was converted to a plain JSON array. For example, assume we have this route:
Currently the response would be:
While as a user might excpect it to be:
This problem was tried to be solved in some other PR's ( #24339 , #27325 , #27384 ). And some use-cases were described in the comments within these PR's.
The example above (having a
total
key and the payload) was described by @hotmeteor in the PR #23414 comments ( link to the comment ).Other valid use case, described by @SethTompkins in the comments of PR #24339 ( link to the comment ) was to adopt a recommendation from the Redux JavaScript framework to "normalize frontend state to allow for easy transaction and lookups with api resources". ( Reference: https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape#relationships-and-tables )
In the example from Redux, linked above, it suggests to return a lookup-table shaped like this (simplified):
Note that the
entities.authorBook.byId
property is a JavaScript object with numeric keys.In the current form we could not return the data shaped like this example. Currently a Resource when serialize its data it only checks if the first key of an array is numeric and, if so, converts all the array to a plain array. The data returned would look like this:
Which is different than expected.
If this PR gets merged, a user could define a
$preserveKeys = true
property in the Resource returning this data structure and the keys within theentities.authorBook.byId
would be preserved.This PR does the following changes:
Change how the
ConditionallyLoadsAttributes@removeMissingValues
methods detects if an array has only numeric keys, by using an sentinel flag while it iterates to the array element.This is a breaking change, as it would check all keys in a array before converting it to a 'plain' without further modification from a user (see first use-case above).
A user can now define a
$preserveKeys
to a resource, so only actual plain arrays (array_values($data) === $data
) are converted to a JS array, anything different than that have their keys preserved.Much of the code in this PR was possible summing up on the previous work and comments from @staudenmeir in PR's #24339 , #27325 .