hamlet-framework / json-mapper
Json Data Binding
Installs: 11 890
Dependents: 0
Suggesters: 0
Security: 0
Stars: 27
Watchers: 2
Forks: 1
Open Issues: 0
Requires
- php: ^7 || ^8
- hamlet-framework/type: >= 0.2.0
Requires (Dev)
- ext-json: *
- php-parallel-lint/php-parallel-lint: @stable
- phpunit/phpunit: ^6 || ^7 || ^8
- squizlabs/php_codesniffer: @stable
- symfony/polyfill-mbstring: <=1.20.0
- vimeo/psalm: @stable
README
Quick Summary:
- Psalm type specifications including object-like-arrays, union types, associative arrays etc.
- PHP-Parser for resolving FQCN
- Uses reflection by default
- Reusable code-as-configuration
- Supports polymorphism through subtype resolvers
- Cascading configuration options for subtree resolutions
- Type safety: Psalm will know that
JsonMapper::map(_list(_class(User::class)), ...)
returnslist<User>
. - Cast exception thrown for impossible casting
As a start, to map the following JSON structure:
[ { "name": "Yuri" }, { "name": "Oleg", "email": "oleg@example.com", "address": { "city": "Vologda" } } ]
into the following class hierarchy:
<?php class User { /** @var string */ private $name; /** @var string|null */ private $email; /** @var Address|null */ private $address; } class Address { /** @var string */ private $city; }
use:
<?php $users = JsonMapper::map( _list(_class(User::class)), json_decode($data) );
The library uses hamlet-framework/type library for type specifications.
Configuration Options
The third parameter of the JsonMapper::map
is JsonMapperConfiguration
used to customize the mapping process.
Json Property
<?php $configuration ->withDefaultValue(User::class, 'name', 'unknown') ->withJsonName(User::class, 'homeAddress', 'home_address', 'homeaddress') ->ignoreUnknown(User::class);
Using Setters
<?php $configuaration ->withPropertySetters(User::class) ->withPropertySetter(User::class, 'homeAddress', 'updateHomeAddress');
Using Converter
<?php class User { /** @var DateTimeImmutable */ private $time; /** @var array<string,string> */ private $preferences; /** @var string|null */ private $email; } $json = ' { "time": 1593479541, "preferences": "{\"timeZone\":\"Russia/Moscow\"}", "email": "_.oO000_" } '; $configuration ->withConverter(User::class, 'time', function (int $unixtime) { return DateTimeImmutable::createFomFormat('U', (string) $unixtime); }) ->withConverter(User::class, 'preferences', function (string $json) { return _map(_string(), _string())->cast(json_decode($json)); }) ->withConverter(self::class, 'email', function ($email) { return filter_var($email, FILTER_VALIDATE_EMAIL) ?: null; }); $user = JsonMapper::map(_class(User::class), json_decode($json), $configuration); $user->preferences['timeZone'] == 'Russia/Moscow'; $user->time instanceof DateTimeImmutable; $user->email === null;
Using Type Dispatcher
<?php $configuration ->withTypeDispatcher(User::class, function ($properties) { if (isset($properties['name'])) { return NamedUser::class; } else { return AnonymousUser::class; } });
<?php $coniguration ->withTypeDispatcher(User::class, '__resolveType');
Using JsonMapperAware interface
If you want to keep your mapping configuration closer to the files you map, there's an option to implement JsonMapperAware
interface
<?php class Car implements JsonMapperAware { /** @var string */ protected $make; public function make(): string { return $this->make; } public static function configureJsonMapper(JsonMapperConfiguration $configuration): JsonMapperConfiguration { return $configuration ->withTypeResolver(self::class, function ($properties) { if (array_key_exists('machineGunCapacity', (array) $properties)) { return JamesBondCar::class; } else { return Car::class; } }); } } $cars = JsonMapper::map(_list(_class(Car::class)), json_decode($payload));
To do
- Add support for
ignoreUnknown
- Add support for constructor methods
- Add validators
- Add examples with psalm specs