Skill serialization-groups
Configura contextos de serialización y desnormalización mediante grupos para controlar la visibilidad de campos y la inclusión de relaciones en API Platform.
- Estrellas en GitHub
- 29
- Archivos
- 1
- Último commit
- 22 oct 2026
- Licencia
- —
- Herramientas permitidas
- —
¿Qué hace la skill serialization-groups?
serialization-groups es una agent skill. Configura contextos de serialización y desnormalización mediante grupos para controlar la visibilidad de campos y la inclusión de relaciones en API Platform.
Resumen generado a partir del SKILL.md de la skill.
Sobre la skill serialization-groups
Gestionar la visibilidad de campos y la inclusión de relaciones en API Platform es lo que este SKILL permite a los desarrolladores que trabajan con Symfony o Laravel. Proporciona la lógica para configurar contextos de normalización y desnormalización, permitiéndote controlar qué propiedades aparecen en las salidas JSON durante las lecturas o en las entradas durante las escrituras. Al usar el atributo #[Groups], puedes definir reglas de visibilidad específicas para diferentes operaciones, como mostrar más detalles en la vista de un solo elemento que en una lista de colección.
Un agente debe cargar este SKILL cuando un desarrollador necesite depurar campos JSON faltantes, implementar propiedades de solo escritura como contraseñas o gestionar cómo las entidades relacionadas se incluyen como objetos frente a IRIs. El SKILL maneja escenarios complejos como la sobrescritura por propiedad usando el atributo #[Context] para cambiar formatos de fecha o grupos de serialización anidados. También proporciona orientación específica para usuarios de Laravel que deben usar #[ApiProperty] debido a que los modelos Eloquent carecen de propiedades tipadas. Este enfoque está pensado para mantener la consistencia en bases de código existentes donde una división por DTO podría no ser necesaria.
Lo que un agente puede hacer con ella
- 1Controlar la visibilidad de campos por operaciónConfigura una vista de colección ligera mientras proporcionas un conjunto de datos más completo para las solicitudes de elementos individuales.
- 2Gestionar propiedades de solo escrituraAsegura que los datos sensibles, como las contraseñas, puedan recibirse durante la entrada pero nunca se expongan en las respuestas de la API.
- 3Alternar entre objetos embebidos e IRIsDecide si una entidad relacionada debe aparecer como un objeto anidado completo o como una referencia de cadena simple.
- 4Aplicar formato específico a una propiedadSobrescribe la configuración de serialización por defecto para un solo campo, como cambiar un formato de fecha.
Escrito a partir del SKILL.md de la skill. Cada caso de uso se verifica con una cita del mismo.
Cómo instalar la skill serialization-groups
Cada comando copia la carpeta de la skill al directorio de skills del cliente. Reinicia el cliente o inicia una nueva sesión para cargarla.Instalar la skill serialization-groups en Plugin
/plugin marketplace add api-platform/skillset /plugin install api-platform@api-platform-skillset
Instalar la skill serialization-groups en Claude Code
~/.claude/skills/serialization-groups/SKILL.md
$ npx degit api-platform/skillset/skills/serialization-groups ~/.claude/skills/serialization-groupsInstalar la skill serialization-groups en Claude Code (project)
.claude/skills/serialization-groups/SKILL.md
$ npx degit api-platform/skillset/skills/serialization-groups .claude/skills/serialization-groupsInstalar la skill serialization-groups en Codex
~/.agents/skills/serialization-groups/SKILL.md
$ npx degit api-platform/skillset/skills/serialization-groups ~/.agents/skills/serialization-groupsInstalar la skill serialization-groups en Gemini CLI
~/.gemini/skills/serialization-groups/SKILL.md
$ npx degit api-platform/skillset/skills/serialization-groups ~/.gemini/skills/serialization-groupsInstalar la skill serialization-groups en Claude.ai
npx degit api-platform/skillset/skills/serialization-groups serialization-groups && zip -r serialization-groups.zip serialization-groups # claude.ai → Customize → Skills → + → Create skill → Upload a skill → serialization-groups.zip
SKILL.md
Ver en GitHubSerialization Groups
Prefer one resource class per representation over groups for new designs. Before reaching for groups, consider a DTO split: one class per output shape and per input shape, each with a flat, explicit set of properties (see api-resource). Reasons groups are a poor default:
- The public shape becomes implicit and scattered. To know what
GET /booksreturns you must mentally union every#[Groups]tag across the class hierarchy and cross-reference each operation'snormalizationContext. A dedicatedBookOutputDTO states the shape in one place.- They complicate the metadata. Per-operation contexts, per-property
#[Context], and group inheritance interact in ways that are hard to predict and hard to test.- They block streaming serialization. API Platform's JsonStreamer path needs one static shape per class; a class whose output varies by group can't be streamed and falls back to the slower object normalizer.
Use groups when: you're working in an existing codebase already built on them (stay consistent), or a DTO split is genuinely overkill (one tiny read/write asymmetry on an internal resource). Otherwise, split the class.
The two contexts
normalizationContext governs reading (entity → JSON). denormalizationContext
governs writing (JSON → entity). A property is only serialized/deserialized if
one of its #[Groups] is listed in the active context.
use ApiPlatform\Metadata\ApiResource;
use Symfony\Component\Serializer\Attribute\Groups;
#[ApiResource(
normalizationContext: ['groups' => ['book:read']],
denormalizationContext: ['groups' => ['book:write']],
)]
class Book
{
#[Groups(['book:read'])] // appears in output, ignored on input
public ?int $id = null;
#[Groups(['book:read', 'book:write'])] // read and write
public string $title = '';
#[Groups(['book:write'])] // write-only, e.g. a password
public ?string $secret = null;
}
A property with no group in the active context is invisible: a field present on the entity but absent from the response is almost always a missing read group.
Naming convention
Use resource:operation-style group names — book:read, book:write,
book:item:read. This keeps groups namespaced per resource so a read group on
one class never accidentally pulls properties on another. Pairing a broad
book:read with a narrower book:item:read lets item operations expose more than
collections (see next section).
Per-operation contexts
Override the resource-level context on a single operation. Common pattern: a lean collection, a richer item view.
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
#[ApiResource(normalizationContext: ['groups' => ['book:read']])]
#[GetCollection] // book:read only
#[Get(normalizationContext: ['groups' => ['book:read', 'book:item:read']])]
#[Patch(denormalizationContext: ['groups' => ['book:write']])]
class Book
{
#[Groups(['book:read'])]
public string $title = '';
#[Groups(['book:item:read'])] // only on the single-item GET
public ?string $description = null;
}
Controlling embedded relations
A relation is embedded as a full object when the related class has a property in a group shared with the parent's context; otherwise it serializes as an IRI/string. This is how you choose embed-vs-reference:
#[ApiResource(normalizationContext: ['groups' => ['book:read']])]
class Book
{
#[Groups(['book:read'])]
public ?Author $author = null; // embedded only if Author has a 'book:read' property
}
class Author
{
#[Groups(['book:read'])]
public string $name = ''; // this property pulls Author inline
}
Drop the book:read group from Author::$name and author collapses back to an
IRI. This implicit coupling is exactly the kind of thing a DTO split makes explicit.
Per-property context override with #[Context]
#[Context] overrides serializer options for one property without a new group —
useful for date formats or switching the normalization group on a single relation
(embed the same related type at two depths):
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
class Book
{
#[Groups(['book:read'])]
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
public ?\DateTimeInterface $publishedAt = null;
#[Groups(['book:read'])]
#[Context(['normalization' => ['groups' => ['author:summary']]])]
public ?Author $author = null; // serialized with author:summary, not book:read
}
The normalization/denormalization keys scope the override to one direction. See
the parallel example in state-provider (Per-Property Serialization Context).
Interaction with other features
- Validation groups are separate from serialization groups — don't reuse the
same names expecting them to align. See operations for
validationContext. securityexpressions on properties (security: "is_granted(...)") gate fields independently of groups; both must pass.
Laravel (Eloquent)
API Platform uses the Symfony Serializer on Laravel too, so normalizationContext /
denormalizationContext, the two-context model, per-operation contexts and embed-vs-IRI
behaviour are identical. The one difference: Eloquent models have no typed
properties, so #[Groups] can't be attached per property. Declare groups at the
class level via #[ApiProperty(serialize: …, property: …)]:
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use Symfony\Component\Serializer\Attribute\Groups;
#[ApiResource(normalizationContext: ['groups' => ['book:read']])]
#[ApiProperty(serialize: new Groups(['book:read']), property: 'title')]
#[ApiProperty(serialize: new Groups(['book:read']), property: 'author')]
class Book extends Model {}
DTO/ApiResource classes (not extending Model) use property-level #[Groups] as
shown above. The name_converter (default SnakeCaseToCamelCaseNameConverter) is set
in config/api-platform.php.
Checklist
- Considered a DTO-per-shape split before adding groups (see api-resource)
- Group names namespaced per resource (
book:read, not bareread) - Write-only secrets carry only the write group
- Embedded-vs-IRI relations verified against actual JSON, not assumed
-
#[Context]used for one-off format/group tweaks instead of a whole new group
Archivos de esta skill
1Preguntas sobre la skill serialization-groups
¿Cómo instalo la skill serialization-groups en Claude Code?
Ejecuta npx degit api-platform/skillset/skills/serialization-groups ~/.claude/skills/serialization-groups. Claude Code carga las skills desde ~/.claude/skills (todos los proyectos) o .claude/skills (un proyecto) y usa una skill cuando su descripción coincide con la tarea.
¿Qué clientes pueden usar la skill serialization-groups?
Cualquier cliente que lea el formato Agent Skills: Claude Code, Codex, Gemini CLI y claude.ai (subir como ZIP). Los scripts de la skill se ejecutan solo donde el cliente permite la ejecución de código.
¿Cuál es la diferencia entre normalizationContext y denormalizationContext?
normalizationContext rige la lectura (de entidad a JSON), mientras que denormalizationContext rige la escritura (de JSON a entidad).
¿Cómo puedo controlar si una relación se incluye como un objeto o se devuelve como un IRI?
Una relación se incluye como un objeto completo si la clase relacionada tiene una propiedad en un grupo compartido con el contexto del padre; de lo contrario, se serializa como un IRI.
¿Debería usar siempre grupos de serialización para nuevos diseños?
No, para nuevos diseños es preferible usar una clase de recurso por cada representación (división por DTO) en lugar de grupos, para evitar formas implícitas y complejidad en los metadatos.
¿Cómo aplico configuraciones de serialización específicas a una sola propiedad?
Puedes usar el atributo #[Context] para sobrescribir las opciones del serializador en una propiedad individual, como formatos de fecha o grupos de normalización específicos.