Skip to content

Add support for custom deserializers on model fields#841

Draft
jhendrixMSFT wants to merge 1 commit intomainfrom
client-option
Draft

Add support for custom deserializers on model fields#841
jhendrixMSFT wants to merge 1 commit intomainfrom
client-option

Conversation

@jhendrixMSFT
Copy link
Member

This uses the @clientOption decorator to specify a custom deserializer function. Note that this will override any deserialize_with that would have been created by default.

Copilot AI review requested due to automatic review settings February 6, 2026 21:01
This uses the `@clientOption` decorator to specify a custom deserializer
function.  Note that this will override any deserialize_with that would
have been created by default.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support in the TypeSpec Rust emitter for specifying a custom serde deserialize_with function on individual model fields via the @clientOption decorator.

Changes:

  • Introduces a model-field customization pipeline (customizations) and wires it through the code model.
  • Updates the tcgc adapter to read @clientOption on model properties and record deserialize_with.
  • Updates model codegen to emit a custom #[serde(deserialize_with = "...")] and adds a new end-to-end test fixture/workspace crate.

Reviewed changes

Copilot reviewed 12 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/typespec-rust/src/tcgcadapter/adapter.ts Reads @clientOption from property decorators and stores a deserializeWith customization.
packages/typespec-rust/src/codemodel/customizations.ts Adds a customization type/class (DeserializeWith) for model fields.
packages/typespec-rust/src/codemodel/types.ts Adds customizations to ModelField and initializes it.
packages/typespec-rust/src/codemodel/index.ts Re-exports customization types.
packages/typespec-rust/src/codegen/models.ts Emits serde deserialize_with based on stored customization.
packages/typespec-rust/test/tsp/ClientOption/main.tsp New TypeSpec test input applying @@clientOption(..., "deserialize_with", ..., "rust").
packages/typespec-rust/test/other/client_option/src/lib.rs Test crate providing the referenced deserializer function.
packages/typespec-rust/test/other/client_option/src/generated/models/models.rs Expected generated model with #[serde(deserialize_with = "...")].
packages/typespec-rust/test/other/client_option/src/generated/models/mod.rs Generated models module plumbing for the test crate.
packages/typespec-rust/test/other/client_option/src/generated/mod.rs Generated top-level module plumbing for the test crate.
packages/typespec-rust/test/other/client_option/Cargo.toml New test crate manifest.
packages/typespec-rust/test/Cargo.toml Adds the new test crate to the workspace members.
packages/typespec-rust/test/Cargo.lock Adds lock entry for the new test crate.
packages/typespec-rust/.scripts/tspcompile.js Adds compilation/generation step for the new ClientOption test.
packages/typespec-rust/package.json Bumps @azure-tools/typespec-client-generator-core dependency and peer range.
packages/typespec-rust/pnpm-lock.yaml Lockfile update for the dependency bump.
packages/typespec-rust/CHANGELOG.md Documents the new @clientOption support.
Files not reviewed (1)
  • packages/typespec-rust/pnpm-lock.yaml: Language not supported

Comment on lines +135 to 138
if (deserializeWith) {
serdeParams.add(`deserialize_with = "${deserializeWith.name}"`);
} else if (unwrappedType.kind === 'encodedBytes' || unwrappedType.kind === 'enumValue' || unwrappedType.kind === 'literal' || unwrappedType.kind === 'offsetDateTime' || encodeAsString(unwrappedType)) {
addSerDeHelper(field, serdeParams, bodyFormat, use);
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new override skips addSerDeHelper entirely when a custom deserialize_with is present. addSerDeHelper also emits required non-deserialization serde params (e.g. serialize_with for base64/literal helpers, with = "azure_core::time::..." for date-times). Skipping it can break compilation or serialization; instead, keep the helper generation and only override the deserialize portion.

Suggested change
if (deserializeWith) {
serdeParams.add(`deserialize_with = "${deserializeWith.name}"`);
} else if (unwrappedType.kind === 'encodedBytes' || unwrappedType.kind === 'enumValue' || unwrappedType.kind === 'literal' || unwrappedType.kind === 'offsetDateTime' || encodeAsString(unwrappedType)) {
addSerDeHelper(field, serdeParams, bodyFormat, use);
// always generate serde helpers for supported types; custom deserialize_with will override only the
// deserialize_with portion while preserving other helper-generated serde params
if (unwrappedType.kind === 'encodedBytes' || unwrappedType.kind === 'enumValue' || unwrappedType.kind === 'literal' || unwrappedType.kind === 'offsetDateTime' || encodeAsString(unwrappedType)) {
addSerDeHelper(field, serdeParams, bodyFormat, use);
}
if (deserializeWith) {
// remove any existing deserialize_with param emitted by addSerDeHelper so the custom one takes precedence
for (const param of Array.from(serdeParams)) {
if (param.startsWith('deserialize_with')) {
serdeParams.delete(param);
}
}
serdeParams.add(`deserialize_with = "${deserializeWith.name}"`);

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I might need to retool this a bit.

@jhendrixMSFT jhendrixMSFT marked this pull request as draft February 6, 2026 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant