-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
Description
I'm trying to use the source generation capabilities of System.Text.Json to serialize and deserialize a type that contains an instance of the KeyedCollection<,> class. The documentation (https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/supported-collection-types) states that this is not a fully supported type (specifically, non-string key types), but it does appear to work without problem when not using source generation (so long as I use JsonObjectCreationHandling.Populate), and I get the expected results.
I then tried using the source generation using the same object, and this is failing with a "The given key '0' was not present in the dictionary" error. Looking at the generated code, it's iterating through the collection using a standard for loop, and accessing the items in the collection by index, which doesn't work with KeyedCollection<,> as the indexer requires a key.
Deserializing using source generation does seem to work (it doesn't throw an exception).
A few questions:
- Is
KeyedCollection<,>actually supported when not using source generation (contrary to what the documentation states)? - Is there any way of getting source generation to use
foreachinstead?
Reproduction Steps
Paste the following into a new project:
using System;
using System.Text.Json;
using System.Collections.ObjectModel;
using System.Text.Json.Serialization;
namespace STJTest;
class Program
{
static void Main(string[] args)
{
var employeeBob = new Employee() { Id = 1, Name = "Bob" };
var employeeJane = new Employee() { Id = 2, Name = "Jane" };
var department = new Department() { Id = 1 };
department.Employees.Add(employeeBob);
department.Employees.Add(employeeJane);
// Serialize without the serializer context.
var serializedWithoutContext = JsonSerializer.Serialize(department);
Console.WriteLine("Serialized without context:");
Console.WriteLine(serializedWithoutContext);
var jsonText = "{\"Id\":2,\"Employees\":[{\"Id\":3,\"Name\":\"Sally\"},{\"Id\":2,\"Name\":\"Gordon\"}]}";
// Deserialize without the serializer context.
var deserializedWithoutContext = (Department) JsonSerializer.Deserialize(jsonText, typeof(Department));
Console.WriteLine("Deserialized without context:");
foreach (var employee in deserializedWithoutContext.Employees)
{
Console.WriteLine("{0} - {1}", employee.Id, employee.Name);
}
// Serialize with the context. This will fail with "The given key '0' was not present in the dictionary".
var serializedWithContext = JsonSerializer.Serialize(department, typeof(Department), DepartmentContext.Default);
Console.WriteLine("Serialized with context:");
Console.WriteLine(serializedWithContext);
// Deserialize with the context. This will work correctly.
var deserializedWithContext = (Department) JsonSerializer.Deserialize(jsonText, typeof(Department), DepartmentContext.Default);
Console.WriteLine("Deserialized with context:");
foreach (var employee in deserializedWithContext.Employees)
{
Console.WriteLine("{0} - {1}", employee.Id, employee.Name);
}
Console.ReadKey();
}
}
[JsonSerializable(typeof(Department))]
internal partial class DepartmentContext : JsonSerializerContext
{
}
public class Department
{
public int Id { get; set; }
[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
public EmployeeCollection Employees { get; private set; }
public Department()
{
Employees = new EmployeeCollection();
}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
public class EmployeeCollection : KeyedCollection<int, Employee>
{
protected override int GetKeyForItem(Employee item)
{
return item.Id;
}
}
Expected behavior
The object is successfully serialized to JSON.
Actual behavior
The code throws an exception: "The given key '0' was not present in the dictionary".
Regression?
No response
Known Workarounds
No response
Configuration
.NET 8, Mac OS Ventura 13.6.4, x86_64
Other information
The code generating the source appears to be the GenerateFastPathFuncForEnumerable method:
| private void GenerateFastPathFuncForEnumerable(SourceWriter writer, string serializeMethodName, TypeGenerationSpec typeGenerationSpec) |