mschop / json-serializer
A very simple, fast and configurable object serializer.
Requires
- php: ^8.2
Requires (Dev)
- crell/serde: ^1.3
- phpdocumentor/reflection-docblock: ^5.6
- phpunit/phpunit: ^11.5
- symfony/property-access: ^7.2
- symfony/serializer: ^7.2
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
orjsonDenormalize
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.