mschop/json-serializer

A very simple, fast and configurable object serializer.

v1.1.0 2024-12-18 17:05 UTC

This package is auto-updated.

Last update: 2024-12-18 19:32:01 UTC


README

A PHP object to JSON serializer build with simplicity in mind.

What does this serializer distinguish from others?

This serializer does not use reflection, php attributes or doc block annotations for automatically figuring out the way the objects needs to be serialized or deserialized. Instead a configuration must be provided for every serializable class.

Advantages

Less complex

This library is easy to understand. Everybody should be able to understand the code in a few minutes.

Performance

On my machine mschop/json-serializer is roughly 15x faster for serialization and 5x faster for deserialization compared to symfony/serializer.

Serialization ~15x faster than symfony/serializer:

symfony/serializer: 22.44 seconds
mschop/json-serializer: 1.45 seconds

Deserialization ~5x faster than symfony/serializer:

symfony/serializer: 5.09 seconds
mschop/json-serializer: 0.96 seconds

Take a look at tests/DeserializePerformanceTest.php and tests/SerializePerformanceTest.php for details.

Easier to setup

Ever ran into a situation, that a serializer did not exactly do what you want it todo and you spend hours figuring out the problem? -> not with this serializer!

Locality of behavior

See in the class, that you want to serializer, how objects are serialized.

Tradeoffs

  • Only JSON is supported.
  • Only constructor arguments are supported.
  • Configuration for every class must be created separately.

How to use?

Install:

composer require mschop/json-serializer 

Example Usage:

use Mschop\JsonSerializer\JsonSerializableInterface;
use Mschop\JsonSerializer\JsonSerializeTrait;
use Mschop\JsonSerializer\Config;
use Mschop\JsonSerializer\Field;
use Mschop\JsonSerializer\Normalizer\ArrayNormalizer;
use Mschop\JsonSerializer\Normalizer\ObjectNormalizer;

class Category implements JsonSerializableInterface
{
    use JsonSerializeTrait;
    
    public function __construct(
        private int $id,
        private string $name,
        private array $products,
    ) {}
    
    public static function getJsonSerializerConfig(): Config
    {
        return new Config([
            new Field('id'),
            new Field('name'),
            new Field('products', new ArrayNormalizer(
                new ObjectNormalizer(Product::class)            
            ))
        ]);
    }
}

class Product implements JsonSerializableInterface
{
    use JsonSerializeTrait;
    
    public function __construct(
        private int $id,
        private string $name,
    ) {}
    
    public static function getJsonSerializerConfig(): Config
    {
        return new Config([
            new Field('id'),
            new Field('name'),
        ]);    
    }
}


$category = new Category(
    10,
    'Example',
    [
        new Product(50, 'Best Serializer'),
        new Product(60, 'Is this one'),        
    ]
);

$json = $category->jsonSerialize();

# and now deserialize again:

$deserializedCategory = Category::jsonDeserialize($json);

Core Concepts / Extend

This serializer uses a similar approach to serialization as symfony/serializer.

See https://symfony.com/doc/current/serializer.html#the-serialization-process-normalizers-and-encoders

  • We also have a normalized form of the data (array)
  • You can extend this serializer with your own Normalizer (implement interface \Mschop\JsonSerializer\FieldNormalizerInterface)
  • You can use jsonNormalize or jsonDenormalize to work with arrays instead.

In contrast to symfony/serializer you cannot:

  • Use different encoders -> We just use JSON

When shouldn't you use this serializer?

  • If you need to support different encodings like YAML or XML
  • If you want to globally configure, how objects are serialized / deserialized
  • If you need to serialize objects with fields not exposed in constructor

When should you use this serializer?

  • If all you need is JSON.
  • If you like tiny libraries, which do exactly one thing.
  • If you don't like reflection.
  • If you like locality of behavior.
  • If you expose all fields through the constructor.
  • If you like performance

Available Normalizer

NoopNormalizer

The NoopNormalizer is the default normalizer which is used for all fields unless you explicitly specify a normalizer. It does not modify the values in normalization/de-normalization. You can use it for all fields, that can be converted to and from json via json_decode and json_encode without information loss. This means: Use it for scalar types (string, int, float, bool).

ArrayNormalizer

ArrayNormalizer can be used for object properties, which contain an array.

Note: Only arrays are supported!

use Mschop\JsonSerializer\Field;
use Mschop\JsonSerializer\Normalizer\ArrayNormalizer;

new Field('items', new ArrayNormalizer(
    // Here you must provide a normalizer capable of normalizing / de-normalizing the contained fields
    // This includes e.g. the ObjectNormalizer
))

ObjectNormalizer

ObjectNormalizer can be used to de-/normalize properties containing some form of object.

use Mschop\JsonSerializer\Field;
use Mschop\JsonSerializer\Normalizer\ArrayNormalizer;
use Mschop\JsonSerializer\Normalizer\ObjectNormalizer;

new Field('myfield', new ObjectNormalizer(YourClass::class));

ClosureNormalizer

The ClosureNormalizer is the most special normalizer, because it gives you maximum flexibility. mschop/json-serializer supports polymorphic de-/serialization. You can do this by using the ClosureNormalizer:

use Mschop\JsonSerializer\Field;
use Mschop\JsonSerializer\Normalizer\ClosureNormalizer;
use Mschop\JsonSerializer\Normalizer\ObjectNormalizer;

new Field('myfield', new ClosureNormalizer(
    normalize: fn(A|B $obj) => $obj->normalize(),
    denormalize: fn(array $normalized, array $completeObj) => $completeObj['useAorB']
                                                                ? A::jsonDenormalize($normalized)
                                                                : B::jsonDenormalize($normalized)  
))

This gives you maximum flexibility. Don't forget, that you could also write a new Normalizer for the same result.

Further Normalizer

  • BackedEnumNormalizer
  • DateTimeNormalizer
  • ... more coming? -> not sure yet. Maybe. If you need some: Either create issue or create PR.