PHPackages                             dschledermann/json-coder - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. dschledermann/json-coder

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

dschledermann/json-coder
========================

Encode and decode JSON objects from and to custom PHP classes

0.8.0(3mo ago)31.8k↑250%GPL-3.0-or-laterPHPPHP &gt;=8.1CI passing

Since Dec 5Pushed 3mo ago1 watchersCompare

[ Source](https://github.com/dschledermann/json)[ Packagist](https://packagist.org/packages/dschledermann/json-coder)[ RSS](/packages/dschledermann-json-coder/feed)WikiDiscussions master Synced 3w ago

READMEChangelog (2)Dependencies (2)Versions (10)Used By (0)

JSON Coder
==========

[](#json-coder)

This package can encode and decode JSON to and from PHP classes. It is useful for packing DTOs on queues, for cloud storage or just for HTTP-replies. Reflection is used, and any constructor logic is surpassed. The decoder is quite happy to ignore extra fields that may be in a payload, so you can grab just the field your application uses.

Installation
------------

[](#installation)

```
composer require dschledermann/json-coder
```

Usage
-----

[](#usage)

Instantiate the Coder and configure it if you need to.

### Encoding

[](#encoding)

This is very straight forward:

Example:

```
class SomeClass
{
    public function __construct(
        public string $name,
        public int $age,
    ) {}
}

$someObj = new SomeClass(
    "John Doe",
    45,
);

$encoder = Encoder::create(SomeClass::class);
echo $encoder->encode($someObj);
```

This will output:

```
{"name":"John Doe","age":45}
```

### Decoding

[](#decoding)

Here you need to specify what class you wish to decode into. Apart from this, it is basically the reverse:

```
class SomeClass
{
    public function __construct(
        public string $name,
        public int $age,
    ) {}
}

$decoder = Decoder::create(SomeClass::class);
$json = '{"name":"John Doe","age":45}';
print_r($decoder->decode($json));
```

This will output something like:

```
SomeClass Object
(
    [name] => John Doe
    [age] => 45
)

```

### Configure the Coder

[](#configure-the-coder)

If you have some more advanced needs, then it's possible to pass options to the `json_encode()` and `json_decode()` functions.

```
$encoder = Encoder::create(SomeClass::class, JSON_PRETTY_PRINT);
```

If need to change the style of the keys, then that's also possible. This is useful when interfacing with API's or languages where the naming convention differs from PHP's.

```
use Dschledermann\JsonCoder\KeyConverter\ToLower;

#[ToLower]
final class SomeObj
{
    public function __construct(
        public string $myString,
        public int $myFancyInt,
    ) {}
}

$obj = new SomeObj("Walter White", 52);
$encoder = Encoder::create(SomeObj::class, JSON_PRETTY_PRINT);
echo $encoder->encode($obj);
```

Outputs:

```
{
    "mystring": "Walter White",
    "myfancyint": 52
}
```

Using snake case:

```
use Dschledermann\JsonCoder\KeyConverter\ToSnakeCase;

#[ToSnakeCase]
class SomeObj
{
    public function __construct(
        public string $myString,
        public int $myFancyInt,
    ) {}
}

$encoder = Encoder::create(SomeObj::class);
$obj = new SomeObj("Walter White", 52);
echo $encoder->encode($obj);
```

Outputs:

```
{"my_string":"Walter White","my_fancy_int":52}
```

The same "direction" of the key case converter is used both for encoding and decoding JSON, so you don't have to configure the Coder in a different way depending on use.

See the test suite for more examples.

If you need something else, then make a class where you implement the KeyConverterInterface and make it an Attribute.

Using the "Choice"
------------------

[](#using-the-choice)

In many cases you can expect multiple variants on an API reply or AMQP queue. Fortunately this packages also gives a convenient way to handle that. This package has the VariantChoiceTrait that will make it easier for you to define an umbrella for all the types you expected to receive.

The assumption is that you only have one toplevel key, and that key defines what variant you are dealing with.

There are some limitations on this:

1. Each key has to be of a unique type.
2. You should not have any additional properties on the payload choice class.
3. Check for null output. Decoding and encoding is done with soft reflection "magic". If there are no matches, you will receive a null value.

Consider that you have these two choices:

A person:

```
final class Person
{
    public function __construct(
        public string $name,
    ) {}
}
```

Or a car:

```
final class Car
{
    public function __construct(
        public string $brand,
        public float $horsePowers,
    ) {}
}
```

You can now create a payload class that has each as a variant:

```
use Dschledermann\JsonCoder\VariantChoiceTrait;
use Dschledermann\JsonCoder\Filter\Encode\SkipEncodeIfNull;

#[SkipEncodeIfNull]
final class Payload
{
	use VariantChoiceTrait;

    public ?Person $person = null;
    public ?Car $car = null;
}
```

Consider this payload:

```
[
  {"car":{"brand":"Volvo","horsePowers":193}},
  {"car":{"brand":"Tesla","horsePowers":320}},
  {"person":{"name":"Daniel"}}
]
```

This PHP-code will decode it:

```
$decoder = Decoder::create(Payload::class);
$listOfChoices = $decoder->decodeArray($json);
print_r($listOfChoices);
```

Will output something like this:

```
Array
(
    [0] => Payload Object
        (
            [person] =>
            [car] => Car Object
                (
                    [brand] => Volvo
                    [horsePowers] => 193
                )

        )

    [1] => Payload Object
        (
            [person] =>
            [car] => Car Object
                (
                    [brand] => Tesla
                    [horsePowers] => 320
                )

        )

    [2] => Payload Object
        (
            [person] => Person Object
                (
                    [name] => Daniel
                )

            [car] =>
        )

)

```

The variable $listOfChoices will now contain Payload objects. Each of them can be queried what variant they using the VariantChoiceTrait::getVariantType() method.

### Encoding a choice

[](#encoding-a-choice)

It's also useful to be able to wrap a variant object in the choice container. Consider this code:

```
$person = new Person("Mr. Bean");
$payload = Payload::createFromVariant($person);
print_r($payload);
```

This will output something like:

```
Payload Object
(
    [person] => Person Object
        (
            [name] => Mr. Bean
        )

    [car] =>
)

```

And if you encode it, the result will be this:

```
{"person":{"name":"Mr. Bean"}}
```

It's a very good idea to mark the top level choice class with the SkipEncodeIfNull-attribute. If not, then all the empty variants will be present with a null value, which is almost certainly not what you want.

### Decoding nested structures

[](#decoding-nested-structures)

Nested arrays present a challenge in PHP as the array declaration does not contain limitations on the types in the array. There are a couple of conventions to work around this. The commonly used is a docblock comment indicating the array shape. The two formats supported in this package are "T\[\]" and "array".

```
final class SomeType
{
    /** @var int[] */
    public array $listOfInts;
}
```

We can decode this JSON, and it will enforce that all the elements are indeed integers.

```
{"listOfInts":[12,12,12,3,3,4]}
```

The code will throw an exception if an element in the list is not an int.

We can also coerce a substructure into a complex type:

```
final class SomeType
{
    public string $someField;
	/** @var SubType[] */
	public array $subTypeList;
}

final class SubType
{
    public int $someValue;
}
```

This JSON can be decoded, and the elements in $subTypeList will all be of class "SubType".

```
{"someField":"This is a string","subTypeList":[{"someValue":1},{"someValue":12},{"someValue":123}]}
```

Into something like this:

```
SomeType Object
  (
      [someField] => This is a string
      [subTypeList] => Array
          (
              [0] => SubType Object
                  (
                      [someValue] => 1
                  )

              [1] => SubType Object
                  (
                      [someValue] => 12
                  )

              [2] => SubType Object
                  (
                      [someValue] => 123
                  )

          )

  )

```

This works for simple, native types and for types within the same namespace. If you are using types from other namespaces, you have two options:

- Use the full path of the class in the type hinting
- Use the ListType as an attribute to indicate.

```
namespace Path\To\SomeSpace;

final class SubType
{
    public string $value;
}
```

Using attribute:

```
namespace Path\To\AnotherSpace;

use Dschledermann\JsonCoder\ListType;
use Path\To\SomeSpace\SubType;

final class SomeType
{
    #[ListType(SubType::class)]
    public array $list;
}
```

Using full path type hinting:

```
namespace Path\To\AnotherSpace;

final class SomeType
{
    /** @var \Path\To\SomeSpace\SubType[] */
    public array $list;
}
```

### Dealing with array indexes

[](#dealing-with-array-indexes)

When encoding or decoding structures, sometimes it's required to keep the indexes, and other places they should be discarded. This can matter because JSON distinguishes between maps and lists. You can control the behaviour of this using the "SquashIndexes" attribute.

Consider this:

```
final class Element
{
    public function __construct(
        public string $str,
    ) {}
}
```

If you have this array:

```
$a = ["a" => new Element("a"), "b" => new Element("b")];
$encoder = Encoder::create(Element::class);

$json = $encoder->encodeArray($a);
```

$json will now contain this string:

```
{"a":{"str":"a"},"b":{"str":"b"}}
```

If you wish to discard the indexes to get a proper array, use the "SquashIndexes"-attribute:

```
#[SquashIndexes]
final class Element
{
    public function __construct(
        public string $str,
    ) {}
}
```

And now you will get:

```
[{"str":"a"},{"str":"b"}]
```

You can also use the attribute on lists inside a class. Example:

```
final class ElementWithList
{
    public function __construct(
        public string $str,
        /** @var string[] */
        public array $list,
    ) {}
}

$b = new ElementWithList("something", ['a' => 'aa', 'b' => 'bb']);
$encoder = Encoder::create(ElementWithList::class);
$json = $encoder->encode($b);
```

Here, $json will keep the indexes in the inner list:

```
{"str":"something","list":{"a":"aa","b":"bb"}}
```

Adding the "SquashIndexes"-attribute:

```
final class ElementWithList
{
    public function __construct(
        public string $str,
        /** @var string[] */
        #[SquashIndexes]
        public array $list,
    ) {}
}

$b = new ElementWithList("something", ['a' => 'aa', 'b' => 'bb']);
$encoder = Encoder::create(ElementWithList::class);
$json = $encoder->encode($b);
```

... and you will get this JSON instead:

```
{"str":"something","list":["aa","bb"]}
```

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance82

Actively maintained with recent releases

Popularity23

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~59 days

Recently: every ~113 days

Total

9

Last Release

93d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/9baf23c290321c7d0e4823aafab6a456c168decedaff435937653e9aceebabfe?d=identicon)[dschledermann](/maintainers/dschledermann)

---

Top Contributors

[![dschledermann](https://avatars.githubusercontent.com/u/4303450?v=4)](https://github.com/dschledermann "dschledermann (21 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/dschledermann-json-coder/health.svg)

```
[![Health](https://phpackages.com/badges/dschledermann-json-coder/health.svg)](https://phpackages.com/packages/dschledermann-json-coder)
```

###  Alternatives

[mediawiki/page-forms

Forms for creating and editing wiki pages.

2279.3k2](/packages/mediawiki-page-forms)[bymayo/delete-account

Allows users to delete their own account within Twig templates (Front end).

125.2k](/packages/bymayo-delete-account)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
