Stars: 1513
Forks: 184
Pull Requests: 98
Issues: 117
Watchers: 46
Last Updated: 2023-08-26 05:29:28
Map nested JSON structures onto PHP classes
License: Open Software License 3.0
Languages: PHP
Takes data retrieved from a JSON web service and converts them into nested object and arrays - using your own model classes.
Starting from a base object, it maps JSON data on class properties, converting them into the correct simple types or objects.
It's a bit like the native SOAP parameter mapping PHP's SoapClient
gives you, but for JSON.
It does not rely on any schema, only your PHP class definitions.
Type detection works by parsing @var
docblock annotations of
class properties, as well as type hints in setter methods.
You do not have to modify your model classes by adding JSON specific code; it works automatically by parsing already-existing docblocks.
This library has no dependencies.
Keywords: deserialization, hydration
Model classes need to be written by hand
Since JsonMapper does not rely on any schema information (e.g. from json-schema), model classes cannot be generated automatically.
JsonMapper
object instancemap
or mapArray
method, depending on your dataMap a normal object:
<?php
require 'autoload.php';
$mapper = new JsonMapper();
$contactObject = $mapper->map($jsonContact, new Contact());
// or as classname
$contactObject = $mapper->map($jsonContact, Contact::class);
?>
Map an array of objects:
<?php
require 'autoload.php';
$mapper = new JsonMapper();
$contactsArray = $mapper->mapArray(
$jsonContacts, array(), 'Contact'
);
?>
Instead of array()
you may also use ArrayObject
and descending classes
as well as classes implementing ArrayAccess
.
JSON from an address book web service:
{
'name':'Sheldon Cooper',
'address': {
'street': '2311 N. Los Robles Avenue',
'city': 'Pasadena'
}
}
Your local Contact
class:
<?php
class Contact
{
/**
* Full name
* @var string
*/
public $name;
/**
* @var Address
*/
public $address;
}
?>
Your local Address
class:
<?php
class Address
{
public $street;
public $city;
public function getGeoCoords()
{
//do something with $street and $city
}
}
?>
Your application code:
<?php
$json = json_decode(file_get_contents('http://example.org/sheldon.json'));
$mapper = new JsonMapper();
$contact = $mapper->map($json, new Contact());
echo "Geo coordinates for " . $contact->name . ": "
. var_export($contact->address->getGeoCoords(), true);
?>
JsonMapper
uses several sources to detect the correct type of
a property:
The setter method (set
+ ucwords($propertyname)
) is inspected.
Underscores "_
" and hyphens "-
" make the next letter uppercase.
Property foo_bar-baz
leads to setter method setFooBarBaz
.
If it has a type hint in the method signature then its type used:
public function setPerson(Contact $person) {...}
The method's docblock is inspected for @param $type
annotations:
/** * @param Contact $person Main contact for this application */ public function setPerson($person) {...}
If no type could be detected, the plain JSON value is passed to the setter method.
Class property types (since PHP 7.4):
public Contact $person;
@var $type
docblock annotation of class properties:
/** * @var \my\application\model\Contact */ public $person;
The property has to be public to be used directly. You may also use $bIgnoreVisibility to utilize protected and private properties.
If no type could be detected, the property gets the plain JSON value set.
If a property can not be found, JsonMapper tries to find the property
in a case-insensitive manner.
A JSON property isempty
would then be mapped to a PHP property
isEmpty
.
Note
You have to provide the fully qualified namespace for the type to work. Relative class names are evaluated in the context of the current classes namespace, NOT respecting any imports that may be present.
PHP does not provide the imports via Reflection; the comment text only contains the literal text of the type. For performance reasons JsonMapper does not parse the source code on its own to detect and expand any imports.
Simple types
string
bool
, boolean
int
, integer
double
, float
array
object
Class names, with and without namespaces
Contact
- exception will be thrown if the JSON value is null
Arrays of simple types and class names:
int[]
Contact[]
Multidimensional arrays:
int[][]
TreeDeePixel[][][]
ArrayObjects of simple types and class names:
ContactList[Contact]
NumberList[int]
Backed enums, with and without namespaces
Suit:string|Suit:int
- exception will be thrown if the JSON value is not present in the enum
Nullable types:
int|null
- will be null
if the value in JSON is
null
, otherwise it will be an integerContact|null
- will be null
if the value in JSON is
null
, otherwise it will be an object of type Contact
ArrayObjects and extending classes are treated as arrays.
Variables without a type or with type mixed
will get the
JSON value set directly without any conversion.
See phpdoc's type documentation for more information.
When an object shall be created but the JSON contains a simple type only (e.g. string, float, boolean), this value is passed to the classes' constructor. Example:
PHP code:
/**
* @var DateTime
*/
public $date;
JSON:
{"date":"2014-05-15"}
This will result in new DateTime('2014-05-15')
being called.
When variables are defined as objects of abstract classes or interfaces, JsonMapper would normally try to instantiate those directly and crash.
Using JsonMapper's $classMap
property, you can specify which classes
shall get instantiated instead:
$jm = new JsonMapper();
$jm->classMap['Foo'] = 'Bar';
$jm->map(...);
This would create objects of type Bar
when a variable is defined to be
of type Foo
.
It is also possible to use a callable in case the actual implementation class needs to be determined dynamically (for example in case of a union). The mapped class ('Foo' in the example below) and the Json data are passed as parameters into the call.
$mapper = function ($class, $jvalue) {
// examine $class and $jvalue to figure out what class to use...
return 'DateTime';
};
$jm = new JsonMapper();
$jm->classMap['Foo'] = $mapper;
$jm->map(...);
JsonMapper throws an exception when a JSON property is null
,
unless the PHP class property has a nullable type - e.g. Contact|null
.
If your API contains many fields that may be null
and you do not want
to make all your type definitions nullable, set:
$jm->bStrictNullTypes = false;
JsonMapper's setLogger()
method supports all PSR-3 compatible
logger instances.
Events that get logged:
During development, APIs often change. To get notified about such changes, JsonMapper can be configured to throw exceptions in case of either missing or yet unknown data.
When JsonMapper sees properties in the JSON data that are
not defined in the PHP class, you can let it throw an exception
by setting $bExceptionOnUndefinedProperty
:
$jm = new JsonMapper();
$jm->bExceptionOnUndefinedProperty = true;
$jm->map(...);
You may also choose to handle those properties yourself by setting
a callable to $undefinedPropertyHandler
:
/**
* Handle undefined properties during JsonMapper::map()
*
* @param object $object Object that is being filled
* @param string $propName Name of the unknown JSON property
* @param mixed $jsonValue JSON value of the property
*
* @return void
*/
function setUndefinedProperty($object, $propName, $jsonValue)
{
$object->{'UNDEF' . $propName} = $jsonValue;
}
$jm = new JsonMapper();
$jm->undefinedPropertyHandler = 'setUndefinedProperty';
$jm->map(...);
Or if you would let JsonMapper handle the setter for you, you can return a string
from the $undefinedPropertyHandler
which will be used as property name.
/**
* Handle undefined properties during JsonMapper::map()
*
* @param object $object Object that is being filled
* @param string $propName Name of the unknown JSON property
* @param mixed $jsonValue JSON value of the property
*
* @return void
*/
function fixPropName($object, $propName, $jsonValue)
{
return ucfirst($propName);
}
$jm = new JsonMapper();
$jm->undefinedPropertyHandler = 'fixPropName';
$jm->map(...);
Properties in your PHP classes can be marked as "required" by
putting @required
in their docblock:
/**
* @var string
* @required
*/
public $someDatum;
When the JSON data do not contain this property, JsonMapper will throw
an exception when $bExceptionOnMissingData
is activated:
$jm = new JsonMapper();
$jm->bExceptionOnMissingData = true;
$jm->map(...);
Option $bRemoveUndefinedAttributes
causes JsonMapper to remove properties
from the final object if they have not been in the JSON data:
$jm = new JsonMapper();
$jm->bRemoveUndefinedAttributes = true;
$jm->map(...);
You can allow mapping to private and protected properties and
setter methods by setting $bIgnoreVisibility
to true:
$jm = new JsonMapper();
$jm->bIgnoreVisibility = true;
$jm->map(...);
When a variable's type is a class and JSON data is a simple type
like string
, JsonMapper passes this value to the class' constructor.
If you do not want this, set $bStrictObjectTypeChecking
to true
:
$jm = new JsonMapper();
$jm->bStrictObjectTypeChecking = true;
$jm->map(...);
An exception is then thrown in such cases.
map()
You may wish to pass array data into map()
that you got by calling
json_decode($jsonString, true)
By default, JsonMapper will throw an exception because map()
requires
an object as first parameter.
You can circumvent that by setting $bEnforceMapType
to false
:
$jm = new JsonMapper();
$jm->bEnforceMapType = false;
$jm->map(...);
JsonMapper is able to call a custom method directly on each object after mapping it is finished:
$jm = new JsonMapper();
$jm->postMappingMethod = 'afterMapping';
$jm->map(...);
Now afterMapping()
is called on each mapped object
(if the class has that method).
Via Composer from Packagist:
$ composer require netresearch/jsonmapper
Alternatives
JsonMapper is licensed under the OSL 3.0.
JsonMapper follows the PEAR Coding Standards.