JsonMapper PHP Library

repository·master·Indexed 23 days ago

https://github.com/cweiske/jsonmapper

A standalone PHP library that deserializes and hydrates nested JSON data into custom PHP model classes. It determines property types using PHP type declarations, @var docblock annotations, and setter method type hints, eliminating the need for external JSON schemas. Key features include support for multidimensional arrays, backed enums, nullable types, and custom instantiation via class maps and factories.

Tokens
3.3K
Snippets
6
Records
17
Agent score
32%

What's inside JsonMapper

  1. Overview of JsonMapper

    master

    JsonMapper is a PHP library designed to map nested JSON structures onto your own PHP model classes. It converts JSON data into nested objects and arrays by hydrating class properties based on your existing class definitions.

    Key features include:

    • No Schema Required: It does not rely on JSON schemas; it uses your PHP class definitions instead.
    • Automatic Type Detection: It determines property types by parsing:
      • PHP type declarations
      • @var docblock annotations
      • Type hints in setter methods
    • Zero Modification: You do not need to add JSON-specific code to your model classes; it works by parsing existing docblocks.
    • No Dependencies: The library is standalone.

    This process is similar to the native SOAP parameter mapping provided by PHP's SoapClient, but optimized for JSON.

  2. Benefits and Drawbacks of using JsonMapper

    master

    Benefits

    • IDE Support: Provides autocompletion in IDEs because you work with real PHP objects.
    • Domain Logic: Allows you to easily add comfort methods and domain logic directly to your data model classes.
    • Decoupling: Your JSON API can change without breaking applications, as long as your model classes remain consistent.

    Drawbacks

    • Manual Model Creation: Because JsonMapper does not rely on external schemas (like json-schema), model classes must be written by hand and cannot be automatically generated from a schema.
  3. How JsonMapper detects property types

    master

    JsonMapper determines the correct type for a property using the following priority order:

    1. Setter methods: It looks for set + ucwords($propertyname). Underscores and hyphens are converted to uppercase (e.g., foo_bar-baz maps to setFooBarBaz).
      • If the setter has a type hint (e.g., public function setPerson(Contact $person)), that type is used.
      • If no type hint exists, it inspects the docblock for @param $type annotations.
      • If no type is detected, the raw JSON value is passed to the setter.
    2. Class property types: Uses PHP 7.4+ native type hints (e.g., public Contact $person;).
    3. Constructor property promotion: Uses PHP 8.0+ types (e.g., public function __construct(protected Contact $person) {}).
    4. Docblock annotations: Uses @var $type annotations on class properties.

    Important Notes:

    • You must use fully qualified namespaces for types in docblocks. JsonMapper does not parse source code to resolve imports; it treats the docblock text literally.
    • Properties must be public to be mapped directly. To map protected or private properties, set $bIgnoreVisibility = true.
  4. Handle unknown or missing JSON properties

    master

    You can configure JsonMapper to be strict about the data it receives to catch API changes during development.

    Unknown Properties

    If JSON contains keys not defined in your PHP class:

    • Throw exception: Set $bExceptionOnUndefinedProperty = true;.
    • Custom handler: Set $undefinedPropertyHandler to a callable. The callable receives ($object, $propName, $jsonValue). You can return a string to specify a new property name for the value.

    Missing Properties

    If a property is marked as @required in its docblock, you can trigger an exception if it is missing from the JSON:

    • Throw exception: Set $bExceptionOnMissingData = true; (requires $bStrictObjectTypeChecking to be enabled).
    • Remove missing properties: Set $bRemoveUndefinedAttributes = true; to remove properties from the final object if they were not present in the JSON data.
    // Throw exception on unknown properties
    $jm = new JsonMapper();
    $jm->bExceptionOnUndefinedProperty = true;
    $jm->map(...);
    
    // Use a handler for unknown properties
    function setUndefinedProperty($object, $propName, $jsonValue) {
        $object->{'UNDEF' . $propName} = $jsonValue;
    }
    $jm = new JsonMapper();
    $jm->undefinedPropertyHandler = 'setUndefinedProperty';
    $jm->map(...);
    
    // Throw exception on missing @required properties
    $jm = new JsonMapper();
    $jm->bExceptionOnMissingData = true;
    $jm->map(...);
  5. Basic usage of JsonMapper

    master

    To use JsonMapper, install it via Composer, instantiate the JsonMapper class, and use either map() for single objects or mapArray() for collections.

    • Use map($jsonData, $target) to map JSON data to a single object instance or a class name.
    • Use mapArray($jsonData, $container, $className) to map an array of JSON objects into a collection. The $container can be an array(), ArrayObject, or any class implementing ArrayAccess.
    <?php
    require 'autoload.php';
    $mapper = new JsonMapper();
    
    // Map to an object instance
    $contactObject = $mapper->map($jsonContact, new Contact());
    // Or map using a class name
    $contactObject = $mapper->map($jsonContact, Contact::class);
    
    // Map an array of objects
    $contactsArray = $mapper->mapArray(
        $jsonContacts, array(), 'Contact'
    );
  6. Handle undefined properties with a callback

    master

    If bExceptionOnUndefinedProperty is set to false, you can provide a callback to undefinedPropertyHandler to handle JSON properties that do not exist in the target PHP class. This allows you to dynamically map unknown data to specific properties or handle it manually.

    Callback Signature: function(object $object, string $name, mixed $value): ?string

    If the callback returns a string (the name of a property), JsonMapper will attempt to map the value to that returned property name instead.

  7. Handle Nullable types and strict null checks

    master

    By default, JsonMapper throws an exception if a JSON property is null unless the PHP property is explicitly marked as nullable (e.g., ?int or int|null).

    • To allow null values for all properties without declaring them nullable: set $bStrictNullTypes = false;.
    • To allow null values inside arrays without declaring them nullable (e.g., array[string|null]): set $bStrictNullTypesInArrays = false;.
  8. Execute a post-mapping callback

    master

    You can instruct JsonMapper to call a specific method on every object after it has been successfully mapped. This is useful for initialization or validation logic.

    Set postMappingMethod to the name of the method (as a string) that should be called on the mapped objects.

    $jm = new JsonMapper();
    $jm->postMappingMethod = 'afterMapping';
    $jm->map(...);
    
    // You can also pass arguments to the callback
    $jm->postMappingMethodArguments = [23, 'foo'];
    $jm->map(...);
  9. Configure Class Maps for abstract classes and interfaces

    master

    If a property is typed as an abstract class or an interface, JsonMapper cannot instantiate it directly. Use the $classMap property to map the abstract type to a concrete implementation.

    You can provide a string mapping or a callable for dynamic determination.

    // String mapping
    $jm = new JsonMapper();
    $jm->classMap['Foo'] = 'Bar'; // Maps Foo to Bar
    $jm->map(...);
    
    // Dynamic mapping via callable
    $mapper = function ($class, $jvalue) {
        // logic to determine class
        return 'DateTime';
    };
    
    $jm = new JsonMapper();
    $jm->classMap['Foo'] = $mapper;
    $jm->map(...);
  10. Use Class Factories to override instantiation

    master

    Use the $classFactories property to define custom logic for how specific classes are instantiated. This is useful for complex types like DateTime where you want to control the constructor arguments based on the JSON value.

    $jm = new JsonMapper();
    $jm->classFactories[\DateTime::class] = function ($jvalue) {
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $jvalue)) {
            return new \DateTime($jvalue);
        } else {
            throw new \Exception('Invalid date pattern');
        }
    };
    $jm->map(...);
  11. Configure JsonMapper validation and strictness

    master

    You can control how strictly JsonMapper validates the incoming JSON data by setting the following public properties:

    PropertyTypeDefaultDescription
    bExceptionOnUndefinedPropertyboolfalseIf true, throws an exception if JSON contains a property not defined in the PHP class.
    bExceptionOnMissingDataboolfalseIf true, throws an exception if JSON misses a property marked with @required in the class docblock.
    bEnforceMapTypebooltrueIf true, checks that $json is an object. Disable this if using json_decode($str, true) (associative arrays).
    bStrictObjectTypeCheckingbooltrueThrows an exception when an object is expected but JSON contains a non-object type.
    bStrictNullTypesbooltrueThrows an exception if a null value is found but the property type does not allow nulls.
    bStrictNullTypesInArraysbooltrueThrows an exception if null is found in an array but the type does not allow nulls.
    bIgnoreVisibilityboolfalseIf true, allows mapping to private and protected properties.
    bRemoveUndefinedAttributesboolfalseIf true, removes attributes from the object that were not present in the JSON data.