Skip to content

Commit db0984b

Browse files
committed
Add feature to expand dot notation into nested arrays
- Introduced `expand_dot_notation` configuration in `data.php`, defaulting to `false`. - Updated `TransformedDataResolver` to handle dot notation expansion using `Arr::set()`. - Added documentation on nested data transformation with examples. - Implemented tests for both enabled and disabled dot notation expansion.
1 parent 342e293 commit db0984b

File tree

6 files changed

+233
-2
lines changed

6 files changed

+233
-2
lines changed

config/data.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
* ignore the value being passed into the computed property and recalculate it.
3030
*/
3131
'ignore_exception_when_trying_to_set_computed_property_value' => false,
32+
33+
/*
34+
* When enabled, the package will expand attributes that use dot notation
35+
* to access nested array values (e.g., 'user.name' will access $array['user']['name']).
36+
*/
37+
'expand_dot_notation' => false,
3238
],
3339

3440
/*

docs/as-a-data-transfer-object/mapping-property-names.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class SongData extends Data
8181
You can create the data object from an array with nested structures:
8282

8383
```php
84-
SongData::from([
84+
SongData::from([
8585
"title" => [
8686
"name" => "Never gonna give you up"
8787
],
@@ -92,3 +92,5 @@ SongData::from([
9292
```
9393

9494
The package has a set of default mappers available, you can find them [here](/docs/laravel-data/v4/advanced-usage/available-property-mappers).
95+
96+
When transforming data objects with properties that use dotted notation, you can enable the expansion of these properties into nested arrays. See [Expanding Dotted Notation](/docs/laravel-data/v4/as-a-resource/mapping-property-names#expanding-dotted-notation) for more details.

docs/as-a-resource/mapping-property-names.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,92 @@ And a transformed version of the data object will look like this:
8282
```
8383

8484
The package has a set of default mappers available, you can find them [here](/docs/laravel-data/v4/advanced-usage/available-property-mappers).
85+
86+
## Expanding Dotted Notation
87+
88+
When using `MapOutputName` or `MapName` with dot notation (e.g., 'user.name'), by default the package will keep the dotted notation as-is in the output:
89+
90+
```php
91+
class UserData extends Data
92+
{
93+
public function __construct(
94+
#[MapOutputName('user.name')]
95+
public string $name,
96+
) {
97+
}
98+
}
99+
```
100+
101+
When transformed, this will result in:
102+
103+
```php
104+
[
105+
'user.name' => 'John Doe',
106+
]
107+
```
108+
109+
However, you can enable the expansion of dotted notation to create nested arrays by setting the `expand_dot_notation` feature flag to `true` in your `config/data.php` file:
110+
111+
```php
112+
'features' => [
113+
// Other features...
114+
'expand_dot_notation' => true,
115+
],
116+
```
117+
118+
With this feature enabled, the same data object will transform to:
119+
120+
```php
121+
[
122+
'user' => [
123+
'name' => 'John Doe',
124+
],
125+
]
126+
```
127+
128+
This feature is particularly useful when you need to generate nested JSON structures from flat data objects or when integrating with APIs that expect nested data.
129+
130+
### Example with Nested Data
131+
132+
Consider a more complex example:
133+
134+
```php
135+
class OrderData extends Data
136+
{
137+
public function __construct(
138+
#[MapOutputName('order.id')]
139+
public int $id,
140+
#[MapOutputName('order.customer.name')]
141+
public string $customerName,
142+
#[MapOutputName('order.items')]
143+
public array $items,
144+
) {
145+
}
146+
}
147+
```
148+
149+
With `expand_dot_notation` disabled (default):
150+
151+
```php
152+
[
153+
'order.id' => 1234,
154+
'order.customer.name' => 'Jane Smith',
155+
'order.items' => ['item1', 'item2'],
156+
]
157+
```
158+
159+
With `expand_dot_notation` enabled:
160+
161+
```php
162+
[
163+
'order' => [
164+
'id' => 1234,
165+
'customer' => [
166+
'name' => 'Jane Smith',
167+
],
168+
'items' => ['item1', 'item2'],
169+
],
170+
]
171+
```
172+
173+
This feature uses Laravel's `Arr::set()` method internally to expand the dotted notation into nested arrays.

src/Resolvers/TransformedDataResolver.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,12 @@ private function transform(
8383
$name = $property->outputMappedName;
8484
}
8585

86-
$payload[$name] = $value;
86+
if (config('data.features.expand_dot_notation')) {
87+
Arr::set($payload, $name, $value);
88+
}
89+
else {
90+
$payload[$name] = $value;
91+
}
8792
}
8893

8994
return $payload;

tests/DottedMappingTest.php

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
use Spatie\LaravelData\Attributes\DataCollectionOf;
4+
use Spatie\LaravelData\Attributes\MapOutputName;
5+
use Spatie\LaravelData\Data;
6+
use Spatie\LaravelData\Tests\Fakes\SimpleDataWithMappedDottedProperty;
7+
8+
it('can map dotted property names when transforming without undot', function () {
9+
config()->set('data.features.expand_dot_notation', false);
10+
$data = new SimpleDataWithMappedDottedProperty('hello');
11+
$dataCollection = SimpleDataWithMappedDottedProperty::collect([
12+
['dotted.description' => 'never'],
13+
['dotted.description' => 'gonna'],
14+
['dotted.description' => 'give'],
15+
['dotted.description' => 'you'],
16+
['dotted' => ['description' => 'up']],
17+
]);
18+
19+
$dataClass = new class ('hello', $data, $data, $dataCollection, $dataCollection) extends Data {
20+
public function __construct(
21+
#[MapOutputName('dotted.property')]
22+
public string $string,
23+
public SimpleDataWithMappedDottedProperty $nested,
24+
#[MapOutputName('dotted.nested_other')]
25+
public SimpleDataWithMappedDottedProperty $nested_renamed,
26+
#[DataCollectionOf(SimpleDataWithMappedDottedProperty::class)]
27+
public array $nested_collection,
28+
#[
29+
MapOutputName('dotted.nested_other_collection'),
30+
DataCollectionOf(SimpleDataWithMappedDottedProperty::class)
31+
]
32+
public array $nested_renamed_collection,
33+
) {
34+
}
35+
};
36+
37+
expect($dataClass->toArray())->toMatchArray([
38+
'dotted.property' => 'hello',
39+
'nested' => [
40+
'dotted.description' => 'hello',
41+
],
42+
'dotted.nested_other' => [
43+
'dotted.description' => 'hello',
44+
],
45+
'nested_collection' => [
46+
['dotted.description' => 'never'],
47+
['dotted.description' => 'gonna'],
48+
['dotted.description' => 'give'],
49+
['dotted.description' => 'you'],
50+
['dotted.description' => 'up'],
51+
],
52+
'dotted.nested_other_collection' => [
53+
['dotted.description' => 'never'],
54+
['dotted.description' => 'gonna'],
55+
['dotted.description' => 'give'],
56+
['dotted.description' => 'you'],
57+
['dotted.description' => 'up'],
58+
],
59+
]);
60+
});
61+
62+
it('can map dotted property names when transforming with undot', function () {
63+
config()->set('data.features.expand_dot_notation', true);
64+
$data = new SimpleDataWithMappedDottedProperty('hello');
65+
$dataCollection = SimpleDataWithMappedDottedProperty::collect([
66+
['dotted.description' => 'never'],
67+
['dotted.description' => 'gonna'],
68+
['dotted.description' => 'give'],
69+
['dotted.description' => 'you'],
70+
['dotted' => ['description' => 'up']],
71+
]);
72+
73+
$dataClass = new class ('hello', $data, $data, $dataCollection, $dataCollection) extends Data {
74+
public function __construct(
75+
#[MapOutputName('dotted.property')]
76+
public string $string,
77+
public SimpleDataWithMappedDottedProperty $nested,
78+
#[MapOutputName('dotted.nested_other')]
79+
public SimpleDataWithMappedDottedProperty $nested_renamed,
80+
#[DataCollectionOf(SimpleDataWithMappedDottedProperty::class)]
81+
public array $nested_collection,
82+
#[
83+
MapOutputName('dotted.nested_other_collection'),
84+
DataCollectionOf(SimpleDataWithMappedDottedProperty::class)
85+
]
86+
public array $nested_renamed_collection,
87+
) {
88+
}
89+
};
90+
91+
expect($dataClass->toArray())->toMatchArray([
92+
'dotted' => [
93+
'property' => 'hello',
94+
'nested_other' => ['dotted' => ['description' => 'hello']],
95+
'nested_other_collection' => [
96+
['dotted' => ['description' => 'never']],
97+
['dotted' => ['description' => 'gonna']],
98+
['dotted' => ['description' => 'give']],
99+
['dotted' => ['description' => 'you']],
100+
['dotted' => ['description' => 'up']],
101+
],
102+
],
103+
'nested' => [
104+
'dotted' => ['description' => 'hello'],
105+
],
106+
'nested_collection' => [
107+
['dotted' => ['description' => 'never']],
108+
['dotted' => ['description' => 'gonna']],
109+
['dotted' => ['description' => 'give']],
110+
['dotted' => ['description' => 'you']],
111+
['dotted' => ['description' => 'up']],
112+
],
113+
]);
114+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Spatie\LaravelData\Tests\Fakes;
4+
5+
use Spatie\LaravelData\Attributes\MapName;
6+
use Spatie\LaravelData\Data;
7+
8+
class SimpleDataWithMappedDottedProperty extends Data
9+
{
10+
public function __construct(
11+
#[MapName('dotted.description', 'dotted.description')]
12+
public string $string
13+
) {
14+
}
15+
}

0 commit comments

Comments
 (0)