Skip to main content
#[partial_type], #[omit_type(...)], #[pick_type(...)], and #[intersection_type] mirror NestJS’s @nestjs/mapped-types (PartialType, OmitType, PickType, IntersectionType). Partial/Omit/Pick emit the same derive set as #[dto] (Debug, Clone, Serialize, Deserialize, NestDto, JsonSchema, Validate) plus the default #[serde(deny_unknown_fields)]. Field-level validator markers (#[IsEmail], #[Length(...)], #[Min(...)], etc.) are preserved on every surviving field. #[intersection_type] omits deny_unknown_fields because serde forbids it on structs that contain #[serde(flatten)].

#[partial_type]

Wraps every non-optional field in Option<T>. Fields that are already Option<T> are left as-is — Option<Option<T>> would be a bug, and we avoid it. The result is the standard “PATCH body” shape: every field is independent, omitting a field skips its inner validator, and supplying null for a non-optional field is a deserialization error.
UpdateUserDto deserializes from any subset of the four fields. A request body with {"email": "ada@example.com"} succeeds; one with {"email": null} fails with 400 Bad Request because email is Option<String>, not Option<Option<String>>. An empty body {} succeeds because every field is optional.

Validation behavior on Option fields

When a field is Some(_), its inner validator runs. When it is None, the validator is skipped — the entire point of a partial type. This matches NestJS’s PartialType semantics and validator 0.21’s behavior on Option<T> fields.

#[omit_type(field_a, field_b, ...)]

Builds a new DTO by removing the named fields from the source struct. Surviving fields keep their type and their validators. This is the typed equivalent of writing a duplicate DTO with one fewer field — same derive set, same deny_unknown_fields behavior, but the field list lives in the macro arguments.
CreateUserNoNicknameDto has exactly email, name, and age. Listing a field name that does not exist on the source struct is a compile error — the macro matches against the field list of the struct it is applied to.

Common use

The most common case is a “registration without optional fields” DTO: a single source DTO defines all fields, and an omit variant is used at a stricter endpoint (e.g., admin onboarding) where the optional fields must be set later.

#[pick_type(field_a, field_b, ...)]

The inverse of #[omit_type]. Keeps only the named fields; everything else is dropped. Same derive set, same validator preservation.
UserIdentityDto has exactly email and name. Pick is useful for “view model” DTOs that project a subset of a wider input — for example, the public-safe fields of an internal entity.

Common use

#[intersection_type]

NestJS IntersectionType(A, B) analogue. Unlike Partial/Omit/Pick, this does not re-list every leaf field. Each named field is a parent DTO that is #[serde(flatten)]’d into one JSON object, so { ...A, ...B } deserializes as a single body. Nested Validate runs via #[validate(nested)]. Requires at least two named fields; takes no arguments. Parent DTOs must be declared with #[dto(allow_unknown_fields)]. Flattened siblings would otherwise fail each other’s deny_unknown_fields (each parent would treat the other’s keys as unknown).
CreateUserMergedDto deserializes from {"email": "ada@example.com", "name": "Ada"}. Constructing it in Rust still uses the nested fields (ident / profile); only the JSON shape is flat. Nested validator failures live under ValidationErrors::errors() (struct-level), not field_errors().

How the macros relate to #[dto]

Partial/Omit/Pick call the same internal code path as #[dto]:
  1. The struct is rewritten: PartialType wraps non-Option fields in Option<T>; OmitType filters the field list by name; PickType filters the field list by name in the opposite direction.
  2. The validator attributes (#[IsEmail], #[Length(...)], #[Min(...)], etc.) on every surviving field are translated to validator 0.21 #[validate(...)] attributes, exactly as #[dto] does.
  3. The macro emits #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, nestrs::NestDto, nestrs::schemars::JsonSchema, validator::Validate)] plus #[serde(deny_unknown_fields)].
#[intersection_type] emits the same derive set without deny_unknown_fields, and injects #[serde(flatten)] + #[validate(nested)] on each parent field instead of rewriting leaf types. This means ValidatedBody<T> / ValidatedQuery<T> / ValidationPipe accept mapped-type DTOs without any extra configuration, and the resulting types flow into nestrs-openapi components.schemas automatically through the JsonSchema derive.
Unlike NestJS, where @nestjs/mapped-types re-types the source class by reference, nestrs Partial/Omit/Pick take the source shape inline. You re-list the fields on the new struct. This is the standalone macro pattern, mirroring ts-rs and specta in the Rust ecosystem — it avoids cross-crate path resolution at proc-macro time and keeps the field-level validator markers editable per output struct. #[intersection_type] is the exception: parent DTOs are referenced by field type, not re-listed leaf by leaf.