magicsunday / jsonmapper
Map JSON to PHP
Fund package maintenance!
paypal.me/magicsunday
Installs: 3 931
Dependents: 1
Suggesters: 0
Security: 0
Stars: 2
Watchers: 1
Forks: 0
Open Issues: 0
Requires
- php: >=8.1.0 <8.4.0
- ext-json: *
- doctrine/annotations: ^2.0
- doctrine/inflector: ^2.0
- symfony/property-access: ^6.0 || ^7.0
- symfony/property-info: ^6.0 || ^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.50
- overtrue/phplint: ^3.4 || ^9.0
- phpdocumentor/reflection-docblock: ^5.0
- phpstan/phpstan: ^1.10
- phpstan/phpstan-deprecation-rules: ^1.1
- phpstan/phpstan-phpunit: ^1.3
- phpstan/phpstan-strict-rules: ^1.5
- phpunit/phpunit: ^10.0 || ^11.0
- rector/rector: ^1.0
Suggests
- phpdocumentor/reflection-docblock: In order to use the PhpDocExtractor this library is required too.
README
JsonMapper
This module provides a mapper to map JSON to PHP classes utilizing Symfony's property info and access packages.
Installation
Using Composer
To install using composer, just run the following command from the command line.
composer require magicsunday/jsonmapper
To remove the module run:
composer remove magicsunday/jsonmapper
Usage
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.
In order to ensure correct mapping of a collection, the property has to be annotated using the phpDocumentor collection annotation type. A collection is a non-scalar value capable of containing other values.
For example:
@var SomeCollection<DateTime> @var SomeCollection<string> @var Collection\SomeCollection<App\Entity\SomeEntity>
Custom annotations
Sometimes its may be required to circumvent the limitations of a poorly designed API. Together with custom annotations it becomes possible to fix some API design issues (e.g. mismatch between documentation and webservice response), to create a clean SDK.
@MagicSunday\JsonMapper\Annotation\ReplaceNullWithDefaultValue
This annotation is used to inform the JsonMapper that an existing default value should be used when setting a property, if the value derived from the JSON is a NULL value instead of the expected property type.
This can be necessary, for example, in the case of a bad API design, if the API documentation defines a certain type (e.g. array), but the API call itself then returns NULL if no data is available for a property instead of an empty array that can be expected.
/** * @var array<string> * * @MagicSunday\JsonMapper\Annotation\ReplaceNullWithDefaultValue */ public array $array = [];
If the mapping tries to assign NULL to the property, the default value will be used, as annotated.
@MagicSunday\JsonMapper\Annotation\ReplaceProperty
This annotation is used to inform the JsonMapper to replace one or more properties with another one. It's used in class context.
For instance if you want to replace a cryptic named property to a more human-readable name.
/** * @MagicSunday\JsonMapper\Annotation\ReplaceProperty("type", replaces="crypticTypeNameProperty") */ class FooClass { /** * @var string */ public $type; }
Instantiation
In order to create an instance of the JsonMapper you are required to pass some arguments to the constructor. The
constructor requires an instance of \Symfony\Component\PropertyInfo\PropertyInfoExtractor
and an instance of
\Symfony\Component\PropertyAccess\PropertyAccessor
. The other arguments are optional.
So first create instances of Symfony's property info extractors. Each list of extractors could contain any number of available extractors. You could also create your own extractors to adjust the process of extracting property info to your needs.
To use the PhpDocExtractor
extractor you need to install the phpdocumentor/reflection-docblock
library too.
use \Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use \Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use \Symfony\Component\PropertyInfo\PropertyInfoExtractor; use \Symfony\Component\PropertyAccess\PropertyAccessor;
A common extractor setup:
$listExtractors = [ new ReflectionExtractor() ]; $typeExtractors = [ new PhpDocExtractor() ]; $propertyInfoExtractor = new PropertyInfoExtractor($listExtractors, $typeExtractors);
Create an instance of the property accessor:
$propertyAccessor = PropertyAccess::createPropertyAccessor();
Using the third argument you can pass a property name converter instance to the mapper. With this you can convert the JSON property names to you desired format your PHP classes are using.
$nameConverter = new \MagicSunday\JsonMapper\Converter\CamelCasePropertyNameConverter();
The last constructor parameter allows you to pass a class map to JsonMapper in order to change the default mapping behaviour. For instance if you have an SDK which maps the JSON response of a webservice to PHP. Using the class map you could override the default mapping to the SDK's classes by providing an alternative list of classes used to map.
$classMap = [ SdkFoo::class => Foo::class, ];
Create an instance of the JsonMapper:
$mapper = new \MagicSunday\JsonMapper( $propertyInfoExtractor, $propertyAccessor, $nameConverter, $classMap );
To handle custom or special types of objects, add them to the mapper. For instance to perform special treatment if an object of type Bar should be mapped:
$mapper->addType( Bar::class, /** @var mixed $value JSON data */ static function ($value): ?Bar { return $value ? new Bar($value['name']) : null; } );
or add a handler to map DateTime values:
$mapper->addType( \DateTime::class, /** @var mixed $value JSON data */ static function ($value): ?\DateTime { return $value ? new \DateTime($value) : null; } );
Convert a JSON string into a JSON array/object using PHPs built in method json_decode
$json = json_decode('JSON STRING', true, 512, JSON_THROW_ON_ERROR);
Call method map
to do the actual mapping of the JSON object/array into PHP classes. Pass the initial class name
and optional the name of a collection class to the method.
$mappedResult = $mapper->map($json, Foo::class, FooCollection::class);
A complete set-up may look like this:
/** * Returns an instance of the JsonMapper for testing. * * @param string[]|Closure[] $classMap A class map to override the class names * * @return \MagicSunday\JsonMapper */ protected function getJsonMapper(array $classMap = []): \MagicSunday\JsonMapper { $listExtractors = [ new ReflectionExtractor() ]; $typeExtractors = [ new PhpDocExtractor() ]; $extractor = new PropertyInfoExtractor($listExtractors, $typeExtractors); return new \MagicSunday\JsonMapper( $extractor, PropertyAccess::createPropertyAccessor(), new CamelCasePropertyNameConverter(), $classMap ); }
Development
Testing
composer update composer ci:cgl composer ci:test composer ci:test:php:phplint composer ci:test:php:phpstan composer ci:test:php:rector composer ci:test:php:unit