diff --git a/README.md b/README.md index 8725dc6..d25ab76 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,10 @@ final class ArticleCollection extends ArrayObject final class Article { public string $title; + + /** + * @var CommentCollection + */ public CommentCollection $comments; } ``` @@ -60,7 +64,7 @@ require __DIR__ . '/vendor/autoload.php'; use App\Dto\Article; use App\Dto\ArticleCollection; -use MagicSunday\JsonMapper\JsonMapper; +use MagicSunday\JsonMapper; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; @@ -89,6 +93,8 @@ var_dump($article, $articles); The first call produces an `Article` instance with a populated `CommentCollection`; the second call returns an `ArticleCollection` containing `Article` objects. +Test coverage: `tests/JsonMapper/DocsQuickStartTest.php`. + ### PHP classes In order to guarantee a seamless mapping of a JSON response into PHP classes you should prepare your classes well. Annotate all properties with the requested type. @@ -170,7 +176,7 @@ To use the `PhpDocExtractor` extractor you need to install the `phpdocumentor/re require __DIR__ . '/vendor/autoload.php'; use MagicSunday\JsonMapper\Converter\CamelCasePropertyNameConverter; -use MagicSunday\JsonMapper\JsonMapper; +use MagicSunday\JsonMapper; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; @@ -219,7 +225,7 @@ You may alternatively implement `\MagicSunday\JsonMapper\Value\TypeHandlerInterf require __DIR__ . '/vendor/autoload.php'; use DateTimeImmutable; -use MagicSunday\JsonMapper\JsonMapper; +use MagicSunday\JsonMapper; use MagicSunday\JsonMapper\Value\ClosureTypeHandler; use stdClass; use Symfony\Component\PropertyAccess\PropertyAccess; @@ -294,7 +300,7 @@ and optional the name of a collection class to the method. require __DIR__ . '/vendor/autoload.php'; use ArrayObject; -use MagicSunday\JsonMapper\JsonMapper; +use MagicSunday\JsonMapper; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; @@ -333,7 +339,7 @@ A complete set-up may look like this: require __DIR__ . '/vendor/autoload.php'; use MagicSunday\JsonMapper\Converter\CamelCasePropertyNameConverter; -use MagicSunday\JsonMapper\JsonMapper; +use MagicSunday\JsonMapper; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; @@ -372,7 +378,7 @@ Use `JsonMapper::addCustomClassMapEntry()` when the target class depends on runt ```php require __DIR__ . '/vendor/autoload.php'; -use MagicSunday\JsonMapper\JsonMapper; +use MagicSunday\JsonMapper; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; @@ -413,7 +419,7 @@ The mapper operates in a lenient mode by default. Switch to strict mapping when require __DIR__ . '/vendor/autoload.php'; use MagicSunday\JsonMapper\Configuration\JsonMapperConfiguration; -use MagicSunday\JsonMapper\JsonMapper; +use MagicSunday\JsonMapper; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; @@ -453,7 +459,7 @@ Type resolution is the most expensive part of a mapping run. Provide a PSR-6 cac ```php require __DIR__ . '/vendor/autoload.php'; -use MagicSunday\JsonMapper\JsonMapper; +use MagicSunday\JsonMapper; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; diff --git a/docs/API.md b/docs/API.md index 3556f5e..6fd906f 100644 --- a/docs/API.md +++ b/docs/API.md @@ -10,7 +10,7 @@ The `JsonMapper` class is the main entry point for mapping arbitrary JSON struct map($json, Article::class, configuration: $configuration); +$article = $mapper->map($json, Article::class); assert($article instanceof Article); assert($article->status === Status::Published); ``` -The mapper validates enum values. When strict mode is enabled (`JsonMapperConfiguration::strict()`), an invalid enum value results in a `TypeMismatchException`. + +The mapper validates enum values. In strict mode (`JsonMapperConfiguration::strict()`), an invalid enum value results in a `TypeMismatchException` instead of populating the property. + +Test coverage: `tests/JsonMapperTest.php::mapBackedEnumFromString` and `tests/JsonMapper/JsonMapperErrorHandlingTest.php::itReportsInvalidEnumValuesInLenientMode`. diff --git a/docs/recipes/nested-collections.md b/docs/recipes/nested-collections.md index 4eb677f..9a36981 100644 --- a/docs/recipes/nested-collections.md +++ b/docs/recipes/nested-collections.md @@ -53,7 +53,7 @@ use App\Dto\Article; use App\Dto\ArticleCollection; use App\Dto\NestedTagCollection; use App\Dto\TagCollection; -use MagicSunday\JsonMapper\JsonMapper; +use MagicSunday\JsonMapper; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; @@ -87,3 +87,6 @@ assert($articles[0]->tags instanceof NestedTagCollection); ``` Each custom collection advertises its value type through the `@extends` PHPDoc annotation, allowing the mapper to recurse through nested structures. + +Test coverage: `tests/JsonMapper/DocsNestedCollectionsTest.php`. + diff --git a/docs/recipes/using-attributes.md b/docs/recipes/using-attributes.md index 46e9b79..16611ad 100644 --- a/docs/recipes/using-attributes.md +++ b/docs/recipes/using-attributes.md @@ -15,12 +15,17 @@ use MagicSunday\JsonMapper\Attribute\ReplaceNullWithDefaultValue; final class User { + /** + * @var list + */ #[ReplaceNullWithDefaultValue] public array $roles = []; } ``` -When a payload contains `{ "roles": null }`, the mapper keeps the default empty array. +When a payload contains `{ "roles": null }`, the mapper keeps the default empty array instead of overwriting it with `null`. + +Test coverage: `tests/JsonMapperTest.php::mapNullToDefaultValueUsingAttribute`. ## `ReplaceProperty` Apply this attribute at class level to redirect one or more incoming property names to a different target property. @@ -33,13 +38,16 @@ namespace App\Dto; use MagicSunday\JsonMapper\Attribute\ReplaceProperty; -#[ReplaceProperty('fullName', replaces: ['first_name', 'name'])] +#[ReplaceProperty('fullName', replaces: 'first_name')] +#[ReplaceProperty('fullName', replaces: 'name')] final class Contact { public string $fullName; } ``` -Both `first_name` and `name` keys will populate the `$fullName` property. Order matters: the first matching alias wins. +Both `first_name` and `name` keys populate the `$fullName` property. Declare one attribute per alias to express the precedence order explicitly. + +Test coverage: `tests/Attribute/ReplacePropertyTest.php::replaceProperty`. Attributes can be combined with PHPDoc annotations and work alongside the classic DocBlock metadata.