100% local — your data never leaves your browser

JSON to PHP — Classes With Promoted Properties

Turn a JSON sample into PHP 8 classes with promoted constructor properties. A renamed property keeps the original key in a comment beside it.

Instant Private Zero cookies

JSON input

PHP output

What this tool does

A JSON sample becomes PHP classes with promoted constructor properties.

{ "id": 1, "firstName": "Ada", "address": { "city": "Paris" } }
<?php

declare(strict_types=1);

class Root
{
    public function __construct(
        public int $id,
        public string $firstName,
        public Address $address,
    ) {
    }
}

Property promotion needs PHP 8.0; the readonly toggle needs 8.1. declare(strict_types=1) is there on purpose: without it, PHP would quietly coerce a string into an int parameter and you would lose the check the types were meant to give you.

A renamed property keeps its key

PHP property names are camelCase here, which means a key with a dash or a leading digit does not survive as a name. When that happens, the key is written beside it:

        /** JSON key: "content-type" */
        public string $contentType,
        public int $fooBar,
        /** JSON key: "foo-bar" */
        public int $fooBar2,

That last pair is the reason: fooBar and foo-bar both want $fooBar, so the second becomes $fooBar2 — and without the comment nothing in the file said which came from which. PHP has no standard mapping attribute to carry it, so a comment does, and only where the name no longer carries the key.

This was a real gap, found while writing this page: PHP was the only one of these generators to lose the key entirely. Go has its tag, Java and C# their annotation, Swift its CodingKeys, Dart its fromJson.

What PHP cannot narrow

  • array for every list. The language has no generics; list<string> lives in a PHPStan or Psalm docblock, and which one you use is not something a sample can know.
  • mixed for null and unknown values. It is the honest type: it accepts everything, and declare(strict_types=1) will not save you there. Narrow it as soon as the field has a real type.
  • A scalar at the root is refused. PHP has no type alias, so 42 or "text" has nothing to become.

What a sample cannot say

No property is nullable and none has a default: the document had a value for each. ?string and = null are the edits you make when a second payload shows the key can be absent — and readonly is worth turning on if these objects are meant to be read, not filled in.

Private by design

Everything runs locally in your browser with JavaScript. Your data is never uploaded, which makes the tool safe for sensitive content, and it keeps working offline.

Frequently asked questions

Why is the JSON key in a comment rather than an attribute?
Because PHP has no standard mapping attribute. Symfony wants `#[SerializedName]`, JMS wants its own, and plain `json_decode` wants nothing at all — emitting one would pick a framework for you. The comment costs nothing, survives copy-paste, and tells you where `$contentType` came from. Add your framework\u2019s attribute on top if you use one.
How do I build one of these from JSON?
With named arguments when the keys are valid names: `new Root(...json_decode($body, true))`. When a key is not — `content-type`, `2fa` — the spread cannot name it, so assign explicitly: `new Root(contentType: $data['content-type'], …)`. The comment next to each property tells you which key to read.
What does `array` mean here?
A PHP array, and nothing more precise: the language has no generic types. `list<string>` exists only for static analysers such as PHPStan and Psalm, in a docblock. If you run one, `/** @var list<string> $tags */` is the line to add — this output does not guess which analyser you use.

Related converters