magicsunday/jsonmapper

Map JSON to PHP

Maintainers

Package info

github.com/magicsunday/jsonmapper

pkg:composer/magicsunday/jsonmapper

Transparency log

Fund package maintenance!

paypal.me/magicsunday

Statistics

Installs: 12 876

Dependents: 2

Suggesters: 0

Stars: 2

Open Issues: 25

3.2.0 2026-07-16 15:54 UTC

README

Map JSON data to strongly-typed PHP classes using Symfony's PropertyInfo and PropertyAccess components.

CI

PHPStan Max Level PHPUnit 12 | 13 Rector 2.0 PSR-12

PHP Version

Latest version License

📌 Overview

JsonMapper is a PHP library that maps JSON data to strongly-typed PHP classes (DTOs, value objects, entities) using reflection and PHPDoc annotations. It leverages Symfony's PropertyInfo and PropertyAccess components to provide flexible, extensible JSON-to-PHP object mapping.

Key Value
Package magicsunday/jsonmapper
PHP ^8.3
Main API MagicSunday\JsonMapper
Output Mapped PHP objects + optional MappingReport

❓ What is this?

JsonMapper takes decoded JSON (via json_decode) and hydrates typed PHP objects, including nested objects, collections, enums, DateTime values, and custom types. It supports both lenient and strict mapping modes with detailed error reporting.

🎯 Why does this exist?

Mapping API responses or configuration payloads to typed PHP classes is a common task that involves repetitive boilerplate. JsonMapper automates this with a clean, extensible architecture based on Symfony components, supporting advanced scenarios like polymorphic APIs, custom name conversion, and recursive collection handling.

🚀 Usage

composer require magicsunday/jsonmapper

Quick start

namespace App\Dto;

use ArrayObject;

final class Comment
{
    public string $message;
}

/**
 * @extends ArrayObject<int, Comment>
 */
final class CommentCollection extends ArrayObject
{
}

/**
 * @extends ArrayObject<int, Article>
 */
final class ArticleCollection extends ArrayObject
{
}

final class Article
{
    public string $title;

    /**
     * @var CommentCollection<int, Comment>
     */
    public CommentCollection $comments;
}
require __DIR__ . '/vendor/autoload.php';

use App\Dto\Article;
use App\Dto\ArticleCollection;
use MagicSunday\JsonMapper;

$single = json_decode('{"title":"Hello world","comments":[{"message":"First!"}]}', associative: false, flags: JSON_THROW_ON_ERROR);
$list = json_decode('[{"title":"Hello world","comments":[{"message":"First!"}]},{"title":"Second","comments":[]}]', associative: false, flags: JSON_THROW_ON_ERROR);

$mapper = JsonMapper::createWithDefaults();

$article = $mapper->map($single, Article::class);
$articles = $mapper->map($list, Article::class, ArticleCollection::class);

JsonMapper::createWithDefaults() wires the default Symfony PropertyInfoExtractor (reflection + PhpDoc) and a PropertyAccessor. For custom extractors, caching, or a specialised accessor see Manual instantiation.

PHP classes

Annotate all properties with the requested type. For collections, use the phpDocumentor collection annotation type:

/** @var SomeCollection<DateTime> $dates */
/** @var SomeCollection<string> $labels */
/** @var Collection\\SomeCollection<App\\Entity\\SomeEntity> $entities */

Immutable / final readonly classes

Classes whose constructor declares promoted or otherwise required parameters are hydrated through their constructor — the mapped values are passed as constructor arguments — instead of being assigned after an argument-less instantiation. This makes final readonly value objects first-class mapping targets:

final readonly class Money
{
    public function __construct(
        public int $amount,
        public string $currency,
    ) {
    }
}

$money = $mapper->map(['amount' => 500, 'currency' => 'EUR'], Money::class);

Every payload value is converted exactly once, through the same pipeline as a property (nested objects, collections, enums, DateTime, custom types, name conversion, ReplaceProperty and ReplaceNullWithDefaultValue all apply); the constructor parameters are then filled from those converted values and any remaining property is assigned afterwards, so a class that mixes promoted constructor parameters with additional settable properties loses nothing. A missing argument falls back to its constructor default, then to null for a nullable parameter; a missing required, non-nullable argument raises a MissingConstructorArgumentException. Plain mutable classes keep the existing property-assignment behaviour unchanged.

📚 Documentation

🛠️ Development

Prerequisites:

  • PHP ^8.3
  • Extensions: json
  • Node.js (for the copy-paste detection gate, run via npx)

Install dependencies:

composer install

Run the mandatory quality gate:

composer ci:test

ci:test includes:

  • Linting (phplint)
  • Unit tests (phpunit)
  • Static analysis (phpstan)
  • Refactoring dry-run (rector --dry-run)
  • Coding standards dry-run (php-cs-fixer --dry-run)
  • Copy/paste detection (jscpd)

🤝 Contributing

See CONTRIBUTING.md for contributor workflow and minimal setup.

If contributions are prepared or modified by an LLM/agent, follow AGENTS.md.