Skip to content

[http-client-csharp] Date-time normalization breaks MRW deserialization with back-compat constructor parameters #11770

Description

@live1206

Description

C# date-time property normalization can generate invalid MRW JSON deserialization when the model's constructor parameter name is preserved from the last contract.

This was found while validating Azure/azure-sdk-for-net#62490. That PR updates @typespec/http-client-csharp from 1.0.0-alpha.20260819.14 to 1.0.0-alpha.20260825.9, whose source is commit 210f84c / #11751.

For existing management models, properties such as StartOn and EndOn are normalized to StartsOn and EndsOn, while constructor back-compat preserves the previously generated parameter names startOn and endOn. The MRW deserializer then inconsistently uses the restored constructor parameter names and the renamed property variable expressions.

The resulting generated code can:

  1. assign JSON values to undeclared startOn/endOn locals, causing compilation errors; and
  2. pass default to the full constructor instead of the parsed values.

Example from AzureReachabilityReportContent.Serialization.cs:

// The startOn/endOn declarations are missing.
foreach (var prop in element.EnumerateObject())
{
    if (prop.NameEquals("startTime"u8))
    {
        startOn = prop.Value.GetDateTimeOffset("O");
        continue;
    }
    if (prop.NameEquals("endTime"u8))
    {
        endOn = prop.Value.GetDateTimeOffset("O");
        continue;
    }
}

return new AzureReachabilityReportContent(
    providerLocation,
    providers ?? new ChangeTrackingList<string>(),
    azureLocations ?? new ChangeTrackingList<AzureLocation>(),
    default,
    default,
    additionalBinaryDataProperties);

The model constructor itself still has the expected slots:

internal AzureReachabilityReportContent(
    AzureReachabilityReportLocation providerLocation,
    IList<string> providers,
    IList<AzureLocation> azureLocations,
    DateTimeOffset startOn,
    DateTimeOffset endOn,
    IDictionary<string, BinaryData> additionalBinaryDataProperties)

Therefore this is not missing input-model information. It is a mismatch between the normalized property identity and the back-compat-restored constructor parameter used by deserialization.

Suspected area

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs, particularly:

  • GetPropertyVariableDeclarations
  • BuildDeserializePropertiesStatements
  • GetSerializationCtorParameterValues

These paths should use one stable local/parameter identity when a constructor parameter name differs from its associated property's generated name.

A regression test should combine:

  • a last-contract constructor parameter named startOn or endOn;
  • a current property normalized to StartsOn or EndsOn; and
  • MRW JSON deserialization.

The generated deserializer should declare, assign, and pass the same local without replacing the parsed value with default.

The separate question of whether the prior StartOn/EndOn property API should be preserved can be handled independently; regardless of that policy, generated deserialization must compile and retain the parsed values.

Affected generated files

Azure/azure-sdk-for-net#62490 contains the following 11 affected files:

  • sdk/avs/Azure.ResourceManager.Avs/src/Generated/Models/VMwareFirewallLicenseProperties.Serialization.cs (endOn)
  • sdk/avs/Azure.ResourceManager.Avs/src/Generated/Models/Vcf5License.Serialization.cs (endOn)
  • sdk/consumption/Azure.ResourceManager.Consumption/src/Generated/Models/BudgetTimePeriod.Serialization.cs (startOn)
  • sdk/costmanagement/Azure.ResourceManager.CostManagement/src/Generated/Models/BenefitUtilizationSummariesContent.Serialization.cs (startOn, endOn)
  • sdk/costmanagement/Azure.ResourceManager.CostManagement/src/Generated/Models/BudgetTimePeriod.Serialization.cs (startOn)
  • sdk/costmanagement/Azure.ResourceManager.CostManagement/src/Generated/Models/ScheduleProperties.Serialization.cs (startOn, endOn)
  • sdk/databox/Azure.ResourceManager.DataBox/src/Generated/Models/ShipmentPickUpContent.Serialization.cs (startOn, endOn)
  • sdk/databoxedge/Azure.ResourceManager.DataBoxEdge/src/Generated/Models/PeriodicTimerSourceInfo.Serialization.cs (startOn)
  • sdk/dataprotection/Azure.ResourceManager.DataProtectionBackup/src/Generated/Models/DataProtectionBackupJobProperties.Serialization.cs (startOn)
  • sdk/dataprotection/Azure.ResourceManager.DataProtectionBackup/src/Generated/Models/RestorableTimeRange.Serialization.cs (startOn, endOn)
  • sdk/network/Azure.ResourceManager.Network/src/Generated/Models/AzureReachabilityReportContent.Serialization.cs (startOn, endOn)

Inline examples are also visible in these review threads:

Package/source chain

  • Previous management emitter: @azure-typespec/http-client-csharp-mgmt@1.0.0-alpha.20260824.1
    • @typespec/http-client-csharp@1.0.0-alpha.20260819.14
    • source commit 365ec52b50b82cd9e1e037de4c6fcd5de7e32e90
  • Failing management emitter: @azure-typespec/http-client-csharp-mgmt@1.0.0-alpha.20260826.4
    • @typespec/http-client-csharp@1.0.0-alpha.20260825.9
    • source commit 210f84c0dee6cfb8226ce95700562c9bf0922822

- by copilot

Metadata

Metadata

Labels

bugSomething isn't workingemitter:client:csharpIssue for the C# client emitter: @typespec/http-client-csharp

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions