JSON to PHP
Turn a JSON sample into PHP DTOs with typed properties, nullable types and the docblocks your IDE needs.
Property names are kept exactly as they appear in the JSON
PHP has no equivalent of @JsonProperty built into the language, so there is no way
to write a rename annotation and have it just work. That makes keeping the original key the
only honest choice: a key like isActive stays $isActive rather than
being "prettified" into something that no longer matches the payload.
Keys that are not legal PHP identifiers (first-name, user id) are
converted instead, since the alternative is a parse error.
<?php
class Root
{
public int $id;
public bool $isActive;
public string $name;
/** @var string[] */
public array $tags;
public ?string $website = null;
} Typed properties or constructor promotion
| Style | Looks like | Needs |
|---|---|---|
| Typed properties | One public property per key, assigned after construction. | PHP 7.4+ |
| Constructor promotion | Properties declared directly in __construct. | PHP 8.0+ |
Promotion produces far less code and makes the object immutable in practice, which is usually what you want for a DTO. Typed properties are better when a hydrator or an ORM needs to write to the object after construction.
Type mapping
| JSON value | PHP type |
|---|---|
| whole number | int / ?int |
| decimal number | float / ?float |
true / false | bool / ?bool |
| string | string / ?string |
| array | array, with a @var T[] docblock |
| object | its own class |
null | the ? form, defaulted to null |
| mixed or unknown | mixed |
Why the array docblocks matter
PHP's array type says nothing about what is inside it, so
public array $tags; gives your IDE nothing to work with — every
foreach body ends up as a guessing game. The generator adds a
/** @var string[] */ line above each array property, which is the convention
PHPStan, Psalm and PhpStorm all understand.
Hydrating from a decoded payload
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
$root = new Root();
foreach ($data as $key => $value) {
if (property_exists($root, $key)) {
$root->$key = $value;
}
} This works precisely because the property names match the JSON keys. With a renamed property the loop would need a translation table, which is the maintenance burden this tool avoids.
Edge cases worth knowing
- One class per file is the PSR-4 rule. The download puts every class in a
single file, which PHP happily loads. If you follow PSR-4, split them into
Root.php,Address.phpand so on before committing. -
?mixedis not legal.mixedalready includesnull, so nullable unknown types stay as plainmixed. - Nested objects are not converted automatically.
json_decodereturnsstdClassor arrays; constructing the child classes is your call. - Keys starting with a digit gain an underscore, since
$2fais not a valid variable name.
FAQ
Do I need PHP 7.4 or 8.0?
Typed properties need 7.4; constructor promotion needs 8.0. If you are still on 7.2 or 7.3, drop the type declarations — the property list itself is still correct.
Should I use json_decode with objects or arrays?
Arrays are easier to hydrate from and to validate. Objects are closer to the DTO shape you are building. Either works with this output; the example above uses arrays.
Why does an optional field get = null?
Without a default, an uninitialised typed property throws Error: must not be accessed before initialization. Defaulting to null makes the nullable type match the declared type and keeps the object safe to read.
Is my JSON uploaded?
No. The generator runs entirely in your browser.