jlm/serializer-expression
Expression-driven serializer for PHP and Laravel: define how models and objects map to arrays using expressive rules, keeping API output, transformations, and serialization logic clean, reusable, and easy to maintain.
jlm/serializer-expression is a small add-on for JMS Serializer that lets you decide at runtime whether a property should be excluded, using a Symfony Expression Language expression instead of static groups/versions. It is archived and unmaintained (2 stars, 0 dependents), so treat it as a reference implementation or vendored helper rather than a long-term dependency.
Minimal steps:
symfony/expression-language constraint is likely pinned to an old major. Run:
composer require jlm/serializer-expression --dry-run
composer why-not symfony/expression-language
If constraints conflict (common on Symfony 5+/PHP 8), fork the repo, bump constraints, and require your fork via a VCS repository.src/ first — the package exposes either an exclusion strategy you register on SerializerBuilder or an annotation you enable via a metadata driver. Everything else follows from which of those it is.// Goal: never serialize $salary unless the viewer is an admin.
$context = SerializationContext::create()->setAttribute('is_admin', $currentUser->isAdmin());
$json = $serializer->serialize($employee, 'json', $context);
Strategy registration (builder-based). If the package ships a strategy class, register it where you build the serializer:
$serializer = SerializerBuilder::create()
->addExclusionStrategy(new ExpressionExclusionStrategy($expressionLanguage))
->build();
In Laravel, do this once inside a service provider and bind the configured serializer as a singleton — don't rebuild it per request.
Annotation/metadata-based. If it uses annotations, attach the expression to the property alongside existing JMS metadata:
/** @Serializer\Exclude(if="!is_admin") */
private $salary;
Keep the expression focused on one object and context variables; long expressions become untestable.
Pass state through the serialization context, not globals. The cleanest pattern is to feed the decision inputs as context attributes and reference them in expressions (is_admin, viewer_id, include_internal). This keeps controllers in control and makes the strategy stateless and testable.
Compose with groups. Use groups for coarse "which shape" decisions and this package only for the handful of fields that depend on runtime state (ownership, feature flags, tenancy). Order matters: exclusion strategies run per property, so an expression can only remove fields a group already allowed — it can't add them back.
Wrap it behind your own service. Expose something like ReportSerializer::forViewer($user) that builds the context and calls JMS Serializer. That isolates the archived dependency to one class and makes swapping to JMS's built-in Expression strategy a one-file change later.
Testing pattern. Assert absence, not just presence:
$this->assertArrayNotHasKey('salary', json_decode($json, true));
Expression exclusion strategy already covers your need — it likely does, and it's maintained. This package is best treated as prior art.symfony/expression-language pins are the most common install failure. Prefer forking with widened constraints over --ignore-platform-reqs hacks that break at runtime.is_admin is defined and is_admin) or always seed defaults.salary from output can also block it from being hydrated on input. Verify request parsing separately.object.isAdmin()) rather than raw properties where possible.ExpressionLanguage instance, register custom functions via a ExpressionFunctionProviderInterface rather than embedding complex logic in annotation strings.Expression or a custom ExclusionStrategy is trivial.How can I help you explore Laravel packages today?