-
Notifications
You must be signed in to change notification settings - Fork 343
Description
Describe the bug
The C# generator crashes with a TypeInitializationException when processing TypeSpec specs that contain OData query parameters (e.g. $filter, $orderby) whose names appear as dictionary keys in example values within the serialized code model (tspCodeModel.json).
The crash occurs during code model deserialization, before any code generation begins. The trigger is this line in the spec, which defines a $filter property that ends up as a $-prefixed dictionary key in the serialized code model.
Error output
Emitter "@azure-typespec/http-client-csharp-mgmt" crashed! This is a bug.
Error: Failed to generate the library. Exit code: 1.
The type initializer for
'Microsoft.TypeSpec.Generator.ClientModel.Providers.ModelReaderWriterContextDefinition'
threw an exception.
at Microsoft.TypeSpec.Generator.ClientModel.ScmCodeModelGenerator.Configure()
at Azure.Generator.AzureClientGenerator.Configure()
at Azure.Generator.Management.ManagementClientGenerator.Configure()
at Microsoft.TypeSpec.Generator.GeneratorHandler.SelectGenerator(CommandLineOptions options)
The inner exception (hidden by TypeInitializationException) is:
System.Text.Json.JsonException: Properties that start with '$' are not allowed
in types that support metadata. Either replace the character or disable reference
preservation and polymorphic deserialization.
Path: $ | LineNumber: 98083 | BytePositionInLine: 42.
Root cause
The emitter serializes OData parameter names as JSON property keys in the code model. For example, the BillingBenefits spec produces:
"value": {
"$filter": {
"$id": "9016",
"kind": "string",
"type": { "$ref": "1498" },
"value": "properties/status ne 'Canceled'"
},
"billingAccountResourceId": { ... }
}The generator deserializes tspCodeModel.json using System.Text.Json with a TypeSpecReferenceHandler (which enables $id/$ref reference preservation). When ReferenceHandler is active, System.Text.Json rejects any property name starting with $ that is not $id or $ref.
The Utf8JsonReaderExtensions class has a TryReadComplexType overload for IReadOnlyList<T> that manually reads arrays via Utf8JsonReader (bypassing the default converter), but no equivalent overload for IReadOnlyDictionary<string, T>. The dictionary deserialization in TypeSpecInputExampleValueConverter.CreateObjectExample() falls through to the generic TryReadComplexType<T>, which delegates to the default System.Text.Json dictionary converter — and that converter enforces the $-prefix restriction.
Proposed fix
Add a TryReadComplexType overload for IReadOnlyDictionary<string, T> in Utf8JsonReaderExtensions.cs that manually reads dictionary entries using Utf8JsonReader, consistent with the existing IReadOnlyList<T> overload:
public static bool TryReadComplexType<T>(
this ref Utf8JsonReader reader,
string propertyName,
JsonSerializerOptions options,
ref IReadOnlyDictionary<string, T>? value)
{
if (reader.TokenType != JsonTokenType.PropertyName)
throw new JsonException();
if (reader.GetString() != propertyName)
return false;
reader.Read();
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException();
reader.Read();
var result = new Dictionary<string, T>();
while (reader.TokenType != JsonTokenType.EndObject)
{
var key = reader.GetString()
?? throw new JsonException("Dictionary key cannot be null");
reader.Read();
var item = reader.ReadWithConverter<T>(options);
result[key] = item ?? throw new JsonException();
}
reader.Read();
value = result;
return true;
}C# overload resolution automatically selects this for IReadOnlyDictionary<string, T> callers (like CreateObjectExample), so no changes needed in the converter code.
Impact
Any TypeSpec spec with OData query parameters ($filter, $orderby, $top, $skip, etc.) that appear as dictionary keys in example values will crash the generator. Known affected spec: BillingBenefits.Management.
Environment
@typespec/http-client-csharp(root generator)- .NET 10.0.103
- TypeSpec Compiler 1.9.0