JSON → PHP 8.2+ DTO
Modern PHP Architecture

JSON to PHP 8.2+ Typed Readonly DTO Generator

Convert JSON data into modern PHP 8.2+ readonly class Data Transfer Objects (DTOs) with constructor promotion and fromArray deserializers.

Presets
Source JSON PayloadValid
<?php

declare(strict_types=1);

namespace App\DTO;

/**
 * @param string[] $tags
 */
final readonly class UserDto implements \JsonSerializable
{
    public function __construct(
        public string $id,
        public string $email,
        public string $username,
        public string $displayName,
        public string $role,
        public bool $isActive,
        public TenantDto $tenant,
        public PreferencesDto $preferences,
        /** @var string[] */
        public array $tags = [],
        public \DateTimeImmutable $lastLoginAt,
        public \DateTimeImmutable $createdAt,
    ) {}

    public static function fromArray(array $data): self
    {
        return new self(
            id: (string) ($data['id'] ?? $data['id'] ?? null ?? ''),
            email: (string) ($data['email'] ?? $data['email'] ?? null ?? ''),
            username: (string) ($data['username'] ?? $data['username'] ?? null ?? ''),
            displayName: (string) ($data['display_name'] ?? $data['displayName'] ?? null ?? ''),
            role: (string) ($data['role'] ?? $data['role'] ?? null ?? ''),
            isActive: (bool) ($data['is_active'] ?? $data['isActive'] ?? null ?? false),
            tenant: TenantDto::fromArray((array) ($data['tenant'] ?? [])),
            preferences: PreferencesDto::fromArray((array) ($data['preferences'] ?? [])),
            tags: array_map(fn($item) => (string) $item, (array) ($data['tags'] ?? [])),
            lastLoginAt: isset($data['last_login_at']) ? new \DateTimeImmutable((string) $data['last_login_at']) : null,
            createdAt: isset($data['created_at']) ? new \DateTimeImmutable((string) $data['created_at']) : null,
        );
    }

    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'email' => $this->email,
            'username' => $this->username,
            'display_name' => $this->displayName,
            'role' => $this->role,
            'is_active' => $this->isActive,
            'tenant' => $this->tenant->toArray(),
            'preferences' => $this->preferences->toArray(),
            'tags' => $this->tags,
            'last_login_at' => $this->lastLoginAt?->format(\DateTimeInterface::ATOM),
            'created_at' => $this->createdAt?->format(\DateTimeInterface::ATOM),
        ];
    }

    public function jsonSerialize(): array
    {
        return $this->toArray();
    }
}

final readonly class TenantDto implements \JsonSerializable
{
    public function __construct(
        public string $id,
        public string $name,
        public string $tier,
        public int $activeSeats,
        public string $billingContact,
    ) {}

    public static function fromArray(array $data): self
    {
        return new self(
            id: (string) ($data['id'] ?? $data['id'] ?? null ?? ''),
            name: (string) ($data['name'] ?? $data['name'] ?? null ?? ''),
            tier: (string) ($data['tier'] ?? $data['tier'] ?? null ?? ''),
            activeSeats: (int) ($data['active_seats'] ?? $data['activeSeats'] ?? null ?? 0),
            billingContact: (string) ($data['billing_contact'] ?? $data['billingContact'] ?? null ?? ''),
        );
    }

    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'tier' => $this->tier,
            'active_seats' => $this->activeSeats,
            'billing_contact' => $this->billingContact,
        ];
    }

    public function jsonSerialize(): array
    {
        return $this->toArray();
    }
}

final readonly class PreferencesDto implements \JsonSerializable
{
    public function __construct(
        public string $theme,
        public bool $notificationsEnabled,
        public string $locale,
        public string $timezone,
    ) {}

    public static function fromArray(array $data): self
    {
        return new self(
            theme: (string) ($data['theme'] ?? $data['theme'] ?? null ?? ''),
            notificationsEnabled: (bool) ($data['notifications_enabled'] ?? $data['notificationsEnabled'] ?? null ?? false),
            locale: (string) ($data['locale'] ?? $data['locale'] ?? null ?? ''),
            timezone: (string) ($data['timezone'] ?? $data['timezone'] ?? null ?? ''),
        );
    }

    public function toArray(): array
    {
        return [
            'theme' => $this->theme,
            'notifications_enabled' => $this->notificationsEnabled,
            'locale' => $this->locale,
            'timezone' => $this->timezone,
        ];
    }

    public function jsonSerialize(): array
    {
        return $this->toArray();
    }
}
3 Classes
DTO Classes Inferred
20 Fields
Typed Properties
0ms Server Lag
PHP 8.2+ Readonly Engine

PHP 8.2+ Readonly Classes & Constructor Promotion

Harness modern PHP 8.2 readonly classes with constructor property promotion, strict type declarations, and deep immutability for enterprise-grade type safety.

Zero-Server Privacy Guarantee

All JSON parsing, AST deduction, and PHP code generation happen 100% inside your local browser sandbox. No proprietary payloads or credentials ever touch a remote server.

Dual Serialization & Deserialization

Automatically outputs type-safe fromArray() static factory hydrators with recursive child DTO mapping, plus toArray() and JsonSerializable interfaces for instant API readiness.

Technical Reference & Architecture Guide

JSON to PHP 8.2+ Typed Readonly DTO Generator — Reference Guide

Convert JSON data into modern PHP 8.2+ readonly class Data Transfer Objects (DTOs) with constructor promotion and fromArray deserializers.

Key Parameters & Controls

Root Class NameString (PHP Identifier)

Top-level class identifier generated for the root JSON object.

Default: RootDto
NamespaceString (PHP Namespace)

PHP namespace prepended to the generated DTO classes (PSR-4 compliant).

Default: App\DTO
PHP Target VersionEnum ('8.2+', '8.1', '8.0')

Target PHP release syntax; 8.2+ outputs readonly class declarations with constructor promotion.

Default: 8.2+
Constructor PromotionBoolean

Declares and assigns typed properties directly within the __construct() argument list.

Default: true
Deserializer Hydration (fromArray)Boolean

Generates a type-safe static fromArray(array $data): self factory method with recursive nested DTO mapping.

Default: true
Property CasingEnum ('camelCase', 'snake_case', 'preserve')

Normalizes property identifiers into standard PHP camelCase while retaining JSON key mappings.

Default: camelCase
DateTime DetectionBoolean

Detects ISO 8601 timestamps and automatically maps them to \DateTimeImmutable properties.

Default: true

Pro Tips & Best Practices

Recursive Nested DTO Hydration

When mapping nested JSON structures or arrays of sub-objects, ensure your fromArray() method recursively invokes child DTO static hydrators using array_map() with null safety.

PHP 8.2 Readonly Class Immutability

In PHP 8.2+, declaring readonly class automatically applies the readonly modifier to all instance properties, preventing accidental property mutation and enforcing true value object semantics.

For JSON collections with mixed

For JSON collections with mixed nulls and scalars, leverage PHP 8.0+ union types (e.g., string|int|null) and typed docblocks (/** @var list<UserDto> */) to maintain PHPStan and Psalm Level 8+ compliance.

Framework Attribute Decoupling

Combine constructor promotion with Symfony's #[SerializedName('snake_key')] or Spatie's #[MapInputName] attributes to cleanly decouple external JSON API contracts from internal domain models.

Common Gotchas & Troubleshooting

Cannot modify readonly property runtime error

PHP 8.2 readonly properties cannot be modified after constructor execution. Use immutable withers (clone with modification) or pass all required state directly during fromArray() initialization.

Nested associative array fails during class instantiation

Ensure that child objects within arrays are mapped through their respective child DTO fromArray() static factory rather than passing raw nested arrays directly into typed object parameters.

Date parsing throws DateMalformedStringException in PHP 8.3+

Validate date string formats prior to instantiating new \DateTimeImmutable(), or wrap deserialization in a try-catch block falling back to nullable ?\DateTimeImmutable.

Frequently Asked Questions

What are the advantages of PHP 8.2 readonly classes for DTOs?▼

PHP 8.2 readonly classes prevent property mutation after instantiation and strictly prohibit dynamic properties. This provides compile-time and runtime immutability, thread safety in async runtimes like Swoole or RoadRunner, and zero defensive copying overhead.

How does constructor property promotion reduce boilerplate code?▼

Instead of separately declaring class properties, constructor parameters, and assignment expressions ($this->prop = $prop), PHP constructor promotion combines visibility, readonly status, types, and defaults into a single __construct() signature, cutting boilerplate by over 60%.

Can this generator output DTOs compatible with Symfony Serializer or Laravel Spatie Data?▼

Yes. You can toggle framework attributes such as Symfony #[SerializedName] and Spatie Laravel Data #[MapInputName] to map between snake_case JSON payloads and camelCase PHP object properties seamlessly.

How does fromArray() handle missing or nullable JSON fields?▼

The generator inspects nullable values and missing keys across sample objects, assigning nullable type hints (?string $name = null) and using null-coalescing operators ($data['name'] ?? null) to eliminate undefined array key notices.

Is any JSON payload sent to an external server during DTO generation?▼

No. All JSON parsing, AST tree deduction, and PHP code rendering execute entirely inside your local browser sandbox via client-side JavaScript with zero network requests and complete data confidentiality.

Related Tools & Utilities

All Tools →