You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -439,128 +439,6 @@ When collecting, only the lexically last flattened array will get any data, and
439
439
440
440
In this case, the `$other` property has two keys, `foo` and `bar`, with values `beep` and `boop`, respectively. The same JSON will deserialize back to the same object as before.
441
441
442
-
#### Value objects
443
-
444
-
Flattening can also be used in conjunction with renaming to silently translate value objects. Consider:
445
-
446
-
```php
447
-
class Person
448
-
{
449
-
public function __construct(
450
-
public string $name,
451
-
#[Field(flatten: true)]
452
-
public Age $age,
453
-
#[Field(flatten: true)]
454
-
public Email $email,
455
-
) {}
456
-
}
457
-
458
-
readonly class Email
459
-
{
460
-
public function __construct(
461
-
#[Field(serializedName: 'email')] public string $value,
462
-
) {}
463
-
}
464
-
465
-
readonly class Age
466
-
{
467
-
public function __construct(
468
-
#[Field(serializedName: 'age')] public int $value
469
-
) {
470
-
$this->validate();
471
-
}
472
-
473
-
#[PostLoad]
474
-
private function validate(): void
475
-
{
476
-
if ($this->value < 0) {
477
-
throw new \InvalidArgumentException('Age cannot be negative.');
478
-
}
479
-
}
480
-
}
481
-
```
482
-
483
-
In this example, `Email` and `Age` are value objects, in the latter case with extra validation. However, both are marked `flatten: true`, so their properties will be moved up a level to `Person` when serializing. However, they both use the same property name, so both have a custom serialization name specified. The above object will serialize to (and deserialize from) something like this:
484
-
485
-
```json
486
-
{
487
-
"name": "Larry",
488
-
"age": 21,
489
-
"email": "me@example.com"
490
-
}
491
-
```
492
-
493
-
Note that because deserialization bypasses the constructor, the extra validation in `Age` must be placed in a separate method that is called from the constructor and flagged to run automatically after deserialization.
494
-
495
-
It is also possible to specify a prefix for a flattened value, which will also be applied recursively. For example, assuming the same Age class above:
496
-
497
-
```php
498
-
readonly class JobDescription
499
-
{
500
-
public function __construct(
501
-
#[Field(flatten: true, flattenPrefix: 'min_')]
502
-
public Age $minAge,
503
-
#[Field(flatten: true, flattenPrefix: 'max_')]
504
-
public Age $maxAge,
505
-
) {}
506
-
}
507
-
508
-
class JobEntry
509
-
{
510
-
public function __construct(
511
-
#[Field(flatten: true, flattenPrefix: 'desc_')]
512
-
public JobDescription $description,
513
-
) {}
514
-
}
515
-
```
516
-
517
-
In this case, serializing `JobEntry` will first flatten the `$description` property, with `desc_` as a prefix. Then, `JobDescription` will flatten both of its age fields, giving each a separate prefix. That will result in a serialized output something like this:
518
-
519
-
```json
520
-
{
521
-
"desc_min_age": 18,
522
-
"desc_max_age": 65
523
-
}
524
-
```
525
-
526
-
And it will deserialize back to the same original 3-layer-object structure.
527
-
528
-
#### List objects
529
-
530
-
It is common in some conventions to have not an object, but an array (sequence) get sent on the wire. This is especially true for JSON APIs, that may send a payload like this:
531
-
532
-
```json
533
-
[
534
-
{"x": 1, "y": 2},
535
-
{"x": 3, "y": 4},
536
-
{"x": 5, "y": 6}
537
-
]
538
-
```
539
-
540
-
Flattening provides a way to support those structures by reading them into an object property.
541
-
542
-
For example, we could model the above JSON like this:
543
-
544
-
```php
545
-
class Point
546
-
{
547
-
public function(public int $x, public int $y) {}
548
-
}
549
-
550
-
class PointList
551
-
{
552
-
public function __construct(
553
-
#[Field(flatten: true)]
554
-
#[SequenceField(arrayType: Point::class)]
555
-
public array $points,
556
-
) {}
557
-
}
558
-
```
559
-
560
-
On serialization, that will flatten the array of points to the level of the `PointList` object itself, producing the JSON shown above.
561
-
562
-
On deserialization, Serde will "collect" any otherwise undefined properties up into `$points`, as it is an array marked `flatten`. Serde will also detect that there is a `SequenceField` or `DictionaryField` defined, and deserialize each entry in the incoming array as that object. That provides a full round-trip between a JSON array-of-objects and a PHP object-with-array-property.
563
-
564
442
### `flattenPrefix` (string, default '')
565
443
566
444
When an object or array property is flattened, by default its properties will be flattened using their existing name (or `serializedName`, if specified). That may cause issues if the same class is included in a parent class twice, or if there is some other name collision. Instead, flattened fields may be given a `flattenPrefix` value. That string will be prepended to the name of the property when serializing.
@@ -1261,6 +1139,132 @@ Instead, Serde will look for any method or methods that have a `#[\Crell\Serde\A
1261
1139
1262
1140
The visibilty of the method is irrelevant. Serde will call `public`, `private`, or `protected` methods the same. Note, however, that a `private` method in a parent class of the class being deserialized to will not get called, as it is not accessible to PHP from that scope.
1263
1141
1142
+
## Advanced patterns
1143
+
1144
+
Serde contains numerous dials and switches, which allows for some very powerful if not always obvious usage patterns. A collection of them are provided below.
1145
+
1146
+
### Value objects
1147
+
1148
+
Flattening can also be used in conjunction with renaming to silently translate value objects. Consider:
1149
+
1150
+
```php
1151
+
class Person
1152
+
{
1153
+
public function __construct(
1154
+
public string $name,
1155
+
#[Field(flatten: true)]
1156
+
public Age $age,
1157
+
#[Field(flatten: true)]
1158
+
public Email $email,
1159
+
) {}
1160
+
}
1161
+
1162
+
readonly class Email
1163
+
{
1164
+
public function __construct(
1165
+
#[Field(serializedName: 'email')] public string $value,
1166
+
) {}
1167
+
}
1168
+
1169
+
readonly class Age
1170
+
{
1171
+
public function __construct(
1172
+
#[Field(serializedName: 'age')] public int $value
1173
+
) {
1174
+
$this->validate();
1175
+
}
1176
+
1177
+
#[PostLoad]
1178
+
private function validate(): void
1179
+
{
1180
+
if ($this->value < 0) {
1181
+
throw new \InvalidArgumentException('Age cannot be negative.');
1182
+
}
1183
+
}
1184
+
}
1185
+
```
1186
+
1187
+
In this example, `Email` and `Age` are value objects, in the latter case with extra validation. However, both are marked `flatten: true`, so their properties will be moved up a level to `Person` when serializing. However, they both use the same property name, so both have a custom serialization name specified. The above object will serialize to (and deserialize from) something like this:
1188
+
1189
+
```json
1190
+
{
1191
+
"name": "Larry",
1192
+
"age": 21,
1193
+
"email": "me@example.com"
1194
+
}
1195
+
```
1196
+
1197
+
Note that because deserialization bypasses the constructor, the extra validation in `Age` must be placed in a separate method that is called from the constructor and flagged to run automatically after deserialization.
1198
+
1199
+
It is also possible to specify a prefix for a flattened value, which will also be applied recursively. For example, assuming the same Age class above:
1200
+
1201
+
```php
1202
+
readonly class JobDescription
1203
+
{
1204
+
public function __construct(
1205
+
#[Field(flatten: true, flattenPrefix: 'min_')]
1206
+
public Age $minAge,
1207
+
#[Field(flatten: true, flattenPrefix: 'max_')]
1208
+
public Age $maxAge,
1209
+
) {}
1210
+
}
1211
+
1212
+
class JobEntry
1213
+
{
1214
+
public function __construct(
1215
+
#[Field(flatten: true, flattenPrefix: 'desc_')]
1216
+
public JobDescription $description,
1217
+
) {}
1218
+
}
1219
+
```
1220
+
1221
+
In this case, serializing `JobEntry` will first flatten the `$description` property, with `desc_` as a prefix. Then, `JobDescription` will flatten both of its age fields, giving each a separate prefix. That will result in a serialized output something like this:
1222
+
1223
+
```json
1224
+
{
1225
+
"desc_min_age": 18,
1226
+
"desc_max_age": 65
1227
+
}
1228
+
```
1229
+
1230
+
And it will deserialize back to the same original 3-layer-object structure.
1231
+
1232
+
### List objects
1233
+
1234
+
It is common in some conventions to have not an object, but an array (sequence) get sent on the wire. This is especially true for JSON APIs, that may send a payload like this:
1235
+
1236
+
```json
1237
+
[
1238
+
{"x": 1, "y": 2},
1239
+
{"x": 3, "y": 4},
1240
+
{"x": 5, "y": 6}
1241
+
]
1242
+
```
1243
+
1244
+
Flattening provides a way to support those structures by reading them into an object property.
1245
+
1246
+
For example, we could model the above JSON like this:
1247
+
1248
+
```php
1249
+
class Point
1250
+
{
1251
+
public function(public int $x, public int $y) {}
1252
+
}
1253
+
1254
+
class PointList
1255
+
{
1256
+
public function __construct(
1257
+
#[Field(flatten: true)]
1258
+
#[SequenceField(arrayType: Point::class)]
1259
+
public array $points,
1260
+
) {}
1261
+
}
1262
+
```
1263
+
1264
+
On serialization, that will flatten the array of points to the level of the `PointList` object itself, producing the JSON shown above.
1265
+
1266
+
On deserialization, Serde will "collect" any otherwise undefined properties up into `$points`, as it is an array marked `flatten`. Serde will also detect that there is a `SequenceField` or `DictionaryField` defined, and deserialize each entry in the incoming array as that object. That provides a full round-trip between a JSON array-of-objects and a PHP object-with-array-property.
1267
+
1264
1268
## Extending Serde
1265
1269
1266
1270
Internally, Serde has six types of extensions that work in concert to produce a serialized or deserialized product.
0 commit comments