• Stars
    star
    1,552
  • Rank 30,154 (Top 0.6 %)
  • Language
    PHP
  • License
    Open Software Lic...
  • Created almost 11 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Map nested JSON structures onto PHP classes

JsonMapper - map nested JSON structures onto PHP classes

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

Pro & contra

Benefits

  • Autocompletion in IDEs
  • It's easy to add comfort methods to data model classes
  • Your JSON API may change, but your models can stay the same - not breaking applications that use the model classes.

Drawbacks

  • 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.

Usage

Basic usage

  1. Register an autoloader that can load PSR-0 compatible classes.
  2. Create a JsonMapper object instance
  3. Call the map or mapArray method, depending on your data

Map 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.

Example

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);
?>

Property type mapping

JsonMapper uses several sources to detect the correct type of a property:

  1. 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.

    1. If it has a type hint in the method signature then its type used:

      public function setPerson(Contact $person) {...}
      
    2. The method's docblock is inspected for @param $type annotations:

      /**
       * @param Contact $person Main contact for this application
       */
      public function setPerson($person) {...}
      
    3. If no type could be detected, the plain JSON value is passed to the setter method.

  2. Class property types (since PHP 7.4):

    public Contact $person;
    
  3. @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.

Supported type names

  • 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 integer
    • Contact|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.

Simple type mapping

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.

Class map

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(...);

Nullables

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;

Logging

JsonMapper's setLogger() method supports all PSR-3 compatible logger instances.

Events that get logged:

  • JSON data contain a key, but the class does not have a property or setter method for it.
  • Neither setter nor property can be set from outside because they are protected or private

Handling invalid or missing data

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.

Unknown properties

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(...);

Missing properties

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(...);

Private properties and functions

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(...);

Simple types instead of objects

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.

Passing arrays to 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(...);

Post-mapping callback

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).

Installation

Via Composer from Packagist:

$ composer require netresearch/jsonmapper

Related software

Alternatives

About JsonMapper

License

JsonMapper is licensed under the OSL 3.0.

Coding style

JsonMapper follows the PEAR Coding Standards.

Author

Christian Weiske, cweiske.de

More Repositories

1

phorkie

Self-hosted pastebin software written in PHP. Pastes are editable, forkable, may have multiple files and are stored in git repositories.
PHP
216
star
2

phpfarm

UNMAINTAINED AND OUT OF DATE. Tool to install multiple versions of PHP beside each other
Shell
142
star
3

SemanticScuttle

SemanticScuttle is a social bookmarking tool experimenting new features like structured tags and collaborative tag descriptions. Report bugs on sourceforge.
PHP
110
star
4

phinde

Self-hosted search engine for your static blog
PHP
54
star
5

php-sqllint

Command line tool to validate (syntax check) SQL files. Mirror of http://git.cweiske.de/php-sqllint.git/
PHP
48
star
6

phancap

Web service to create website screenshots. Mirror of http://git.cweiske.de/phancap.git
PHP
37
star
7

stouyapi

Static API to keep the OUYA working in 2019. Mirror of https://git.cweiske.de/stouyapi.git
JavaScript
32
star
8

phubb

PHP WebSub server. Mirror of http://git.cweiske.de/phubb.git/
PHP
27
star
9

shpub

command line micropub client
PHP
23
star
10

indieauth-openid

Mirror of http://git.cweiske.de/indieauth-openid.git/
PHP
13
star
11

shutter-scp

scp upload plugin for Shutter, the screenshot tool
Perl
13
star
12

noxon-api

An attempt to document the API between Noxon iRadio devices and the servers at vtuner.com and my-noxon.net
PHP
12
star
13

adept-analysis

trying to understand adobe digital edition's protocol
11
star
14

anoweco

Anonymous web comments for the indieweb
PHP
11
star
15

ssh-dyndns

dyndns via ssh+tinydns (dbjdns/dbndns). Mirror of http://git.cweiske.de/?p=ssh-dyndns.git
Shell
10
star
16

careless

Removes LCP DRM encryption from .epub ebook files
10
star
17

surrogator

simple libravatar server
PHP
10
star
18

stapibas

Standalone pingback server. Mirror of http://git.cweiske.de/stapibas.git/
PHP
10
star
19

instagram2micropub

Downloads images of a (private) instagram profile and sends them to a Micropub endpoint
PHP
10
star
20

headphoneindicator

Android app that shows if the headset is plugged in
Java
8
star
21

tt-rss-subtome

SubToMe.com plugin for Tiny Tiny RSS. Mirror of http://git.cweiske.de/tt-rss-subtome.git/
PHP
7
star
22

noxon-gateway

Push your own content onto Noxon iRadio devices.
PHP
7
star
23

MIME_Type_PlainDetect

Detect the MIME type of source code files
PHP
7
star
24

ouya-store-api

OUYA store API documentation. Mirror of http://git.cweiske.de/ouya-store-api.git/
HTML
5
star
25

roundcube-nextcloud_sql_addressbook

Roundcube plugin that allows access to NextCloud address books. Mirror of https://git.cweiske.de/roundcube-nextcloud_sql_addressbook.git
PHP
5
star
26

tmdb2mkvtags

Generate a Matroska tags file from TMDb information. Mirror of https://git.cweiske.de/tmdb2mkvtags.git
PHP
5
star
27

xposed-ouya-plain-purchases

Xposed module that deactivates encryption on the OUYA Android gaming console. Mirror of https://git.cweiske.de/xposed-ouya-plain-purchases.git
Java
4
star
28

louyapi

Local OUYA API server app
Java
4
star
29

auerswald-callnotifier

Notifies you about incoming/outgoing calls of a Auerswald COMpact 3000
PHP
4
star
30

tolino-api-docs

Servers the Tolino Vision 3/4 HD talk to (Firmware 14.x). Mirror of https://git.cweiske.de/tolino-api-docs.git
CSS
4
star
31

usb-wde1-tools

Tools to collect and analyze data from the USB-WDE1 weather receiver from ELV.de. Mirror of http://git.cweiske.de/?p=usb-wde1-tools.git
Shell
3
star
32

errbot-exec

Execute an external command when errbot is talked to.
Python
2
star
33

enigma2-curlytx

Display plain text files fetched via HTTP
Python
2
star
34

RngToRnc

Convert an RelaxNG schema to the compact syntax. Backup of http://pantor.com/download.html
2
star
35

playVideoOnDreambox

Firefox addon (extension) that adds a "Play on Dreambox" button to the toolbar. Pressing it plays the video of the current tab on your Dreambox satellite receiver.
JavaScript
1
star
36

youtube-dl-server

Fetch information about video pages with the help of youtube-dl
PHP
1
star
37

bdrem

Birthday reminder that sends mails. Mirror of http://git.cweiske.de/bdrem.git/
PHP
1
star
38

ouya-romlauncher

Tool to make SNES roms startable via the normal OUYA launcher. Mirror of http://git.cweiske.de/ouya-romlauncher.git/
Java
1
star
39

libratone-firmwares

Firmware files for Libratone Zipp speakers
1
star