Map nested JSON structures onto PHP classes

Overview

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.

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

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.

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

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

Comments
  • Feature Request: Create exception if the value that a setter gives to a property be null

    Feature Request: Create exception if the value that a setter gives to a property be null

    What I mean with this Feature Request, it's the following.:

    A setter can do a check if the data passed as parameter it's real valid, not by the argument variable type.

    As example.: I have a property called "uniqueIdentifier" and when it's set by the setter (setUniqueIdentifier), will check if this uniqueIdentifier exists on the database.

    So when it does not exists, I set the property uniqueIdentifier to NULL. So jSON Mapper could have a feature that (like $mapper->bCheckSetterValue) will check if the value of the property after being set by a seeter results in a NULL value, and if does, throw an exception.

    I think it's a really interesting functioanlity and if you want I can create a PR for it.

    opened by ovflowd 14
  • Add factoryMap-Support

    Add factoryMap-Support

    Its usefull in some cases to create objects via factory

            $jm = new JsonMapper();
            $jm->factoryMap["DateTime"] = function($jvalue) {
                $date = new DateTime();
                $date->setTimestamp($jvalue);
                return $date;
            };
            $sn = $jm->map(
                    json_decode('{"datetime":1569583404}'),
                    new JsonMapperTest_Object()
            );
    
    opened by veltrup 11
  • Preventing tag duplicating

    Preventing tag duplicating

    need some param for preventing of something like this. Income JSON:

    {
        'duplicated': 'first',
        'duplicated': 'second',
    }
    

    after decoding to object(or array) i will get array of strings but not a string, and during mapping to

    class d 
    {
        /**
        * @var  string
         */
        public $duplicated;
    }
    

    I will get notice

    Notice Error: Array to string conversion in [/var/www/sepagateway/vendor/netresearch/jsonmapper/src/JsonMapper.php, line 213]

    and there is no methods to preventing of this.

    opened by Userlocal2 9
  • Added condition for checking snake case symbol.

    Added condition for checking snake case symbol.

    I've tried to map an array where the keys are written in a snake case into classes where the properties are written in a camel case, and I noticed that jsonmapper can't do that.

    Looked at the source code, found that the check for "_" is just not there.

    opened by azakhozhy 8
  • Map using Interfaces is not possible

    Map using Interfaces is not possible

    I upgraded to the version 1.6 and noticed that with that version is not possible to use interfaces to map the expected class return.

    $jsonDecodedData = $json_decode($data); return $this->jsonMapper->map( (object) jsonDecodedData , $this->jsonMapper->createInstance($type, false, $jsonDecodedData) ); The method createInstance now tries to instantiate the interface, how should I do it? Thank you :)

    opened by vitorloureiro 8
  • Coded Feature Request for Issue #77

    Coded Feature Request for Issue #77

    Feature Request based on Issue #77 proposal.

    Where you can create Exceptions for fluent setters or validator setters, those validate if the input data/argument match a specific logic process/algorithm defined by the developer. And not only the simple variable type mapping that JsonMapper provides.

    Good for what Models have as purpose, validate their own logic and set data.

    opened by ovflowd 8
  • Using the @property form PHPDoc

    Using the @property form PHPDoc

    Hi, is there the possibility for the parser to work also with @property defined above the class using PHPDoc or the property must be created in the model? Thanks

    opened by lparolari 7
  • Fix potential type error

    Fix potential type error

    Encountered this in a live system; problem is that is throws a PHP level error so on 5.6 this stops script execution unless you write a custom handler. Even with a handler it's hard to determine what was wrong with the JSON.

    So this catches a particular combination that will fail and throws a more informative exception.

    opened by M1ke 7
  • Support of Psr-5 types

    Support of Psr-5 types

    These changes brings support of PSR-5 types.

    The library TypeResolver from PhpDocumentor is used to parse all @param and @var types, including array expressions like (string|object)[] (an array that can contain strings and objects) and collections like ArrayObject<string,Contact> (an ArrayObject that must contain Contact objects as values and strings as keys).

    As the syntax ArrayObject[Contact] for collections is not defined in the PSR-5 specification, it is not supported anymore by JsonMapper.

    The use of TypeResolver brings also the support of use keyword in class files, as asked in pull request #82. To avoid performance issue when reading files to parse file classes, I will propose a patch (already written and tested) to add support of PSR-16 simple cache, so results from parsing of @param and @var could be stored in a cache.

    opened by laurentj 7
  • Stable

    Stable

    If I target version ^1.1 with composer, it shows me an error about the version not being stable. Would it be possible to release a stable version with the updates since version 0.10.*, as that's marked as the last stable version?

    Thanks in advance

    opened by finwo 7
  • Stable version?

    Stable version?

    Hello good sir,

    Could you please provide a stable version like v1.0.0? We always want stable dependencies in our project but I really like your json mapper. That way we (and I bet many others) can use it. :)

    Thanks in advance :)

    opened by MvanRietschoten 7
Owner
Christian Weiske
Christian Weiske
A repository with implementations of different data structures and algorithms using PHP

PHP Data Structures and Algorithms Data structure and Algorithm is always important for any programming language. PHP, being one of the most popular l

Mizanur Rahman 610 Jan 2, 2023
Output complex, flexible, AJAX/RESTful data structures.

Fractal Fractal provides a presentation and transformation layer for complex data output, the like found in RESTful APIs, and works really well with J

The League of Extraordinary Packages 3.5k Jan 8, 2023
Library for (de-)serializing data of any complexity (supports JSON, and XML)

jms/serializer Introduction This library allows you to (de-)serialize data of any complexity. Currently, it supports XML and JSON. It also provides yo

Johannes 2.2k Jan 1, 2023
World countries in JSON, CSV, XML and Yaml. Any help is welcome!

World countries in JSON, CSV, XML and YAML. Countries data This repository contains a list of world countries, as defined by ISO Standard 3166-1, in J

Mohammed Le Doze 5.6k Jan 3, 2023
PHP Integrated Query, a real LINQ library for PHP

PHP Integrated Query - Official site What is PINQ? Based off the .NET's LINQ (Language integrated query), PINQ unifies querying across arrays/iterator

Elliot Levin 465 Dec 30, 2022
True asynchronous PHP I/O and HTTP without frameworks, extensions, or annoying code. Uses the accepted Fibers RFC to be implemented into PHP 8.1

PHP Fibers - Async Examples Without External Dependencies True asynchronous PHP I/O and HTTP without frameworks, extensions, or annoying code behemoth

Cole Green 121 Jan 6, 2023
A Collections library for PHP.

A Library of Collections for OO Programming While developing and helping others develop PHP applications I noticed the trend to use PHP's arrays in ne

Levi Morrison 629 Nov 20, 2022
Yet Another LINQ to Objects for PHP [Simplified BSD]

YaLinqo: Yet Another LINQ to Objects for PHP Online documentation GitHub repository Features The most complete port of .NET LINQ to PHP, with many add

Alexander Prokhorov 436 Jan 3, 2023
🗃 Array manipulation library for PHP, called Arrayy!

?? Arrayy A PHP array manipulation library. Compatible with PHP 7+ & PHP 8+ \Arrayy\Type\StringCollection::create(['Array', 'Array'])->unique()->appen

Lars Moelleken 430 Jan 5, 2023
`LINQ to Object` inspired DSL for PHP

Ginq Array handling in PHP? Be happy with Ginq! Ginq is a DSL that can handle arrays and iterators of PHP unified. Ginq is inspired by Linq to Object,

Atsushi Kanehara 191 Dec 19, 2022
Collections Abstraction library for PHP

Collections Collections Abstraction library for PHP The Collection library is one of the most useful things that many modern languages has, but for so

Ítalo Vietro 62 Dec 27, 2021
Leetcode for PHP, five questions a week and weekends are updated irregularly

✏️ Leetcode for PHP why do you have to sleep for a long time ,and naturally sleep after death 联系 说明 由于目前工作主要是 golang,我又新起了一个LeetCode-Go-Week项目,- Leetc

吴亲库里 370 Dec 29, 2022
Missing data types for PHP. Highly extendable.

Neverending data validation can be exhausting. Either you have to validate your data over and over again in every function you use it, or you have to rely it has already been validated somewhere else and risk potential problems.

SmartEmailing 82 Nov 11, 2022
All Algorithms implemented in Php

The Algorithms - PHP All algorithms implemented in Php (for education) These implementations are for learning purposes. They may be less efficient tha

The Algorithms 1k Dec 27, 2022
A community driven collection of sorting algorithms in PHP

algorithms A community driven collection of sorting algorithms This repository includes a comma separated file that includes 10k numbers between 1 and

Andrew S Erwin 0 May 16, 2022
Iterators - The missing PHP iterators.

PHP Iterators Description The missing PHP iterators. Features CachingIteratorAggregate ClosureIterator: ClosureIterator(callable $callable, array $arg

(infinite) loophp 24 Dec 21, 2022
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.

Netresearch 9 Aug 21, 2022
Allows generate class files parse from json and map json to php object, including multi-level and complex objects;

nixihz/php-object Allows generate class files parse from json and map json to php object, including multi-level and complex objects; Installation You

zhixin 2 Sep 9, 2022
JSONFinder - a library that can find json values in a mixed text or html documents, can filter and search the json tree, and converts php objects to json without 'ext-json' extension.

JSONFinder - a library that can find json values in a mixed text or html documents, can filter and search the json tree, and converts php objects to json without 'ext-json' extension.

Eboubaker Eboubaker 2 Jul 31, 2022
The Enobrev\ORM library is a small framework of classes meant to be used for simply mapping a mysql database to PHP classes, and for creating simply SQL statements using those classes.

The Enobrev\ORM library is a small framework of classes meant to be used for simply mapping a mysql database to PHP classes, and for creating simply SQL statements using those classes.

Mark Armendariz 0 Jan 7, 2022