Search code examples
c#jsonjson-deserializationsystem.text.jsonreadonly-collection

How do you properly deserialize a class that has an IReadOnlyCollection<T> using System.Text.Json?


I have the following class:

public sealed class SomeClass
{
    [JsonConstructor()]
    public SomeClass(IEnumerable<string> myItems)
    {
        InternalMyItems = new Collection<string>(myItems.ToArray());
        MyItems = new ReadOnlyCollection<string>(InternalMyItems);
    }
    
    public IReadOnlyCollection<string> MyItems { get; }
    private Collection<string> InternalMyItems { get; }
}

Serialization seems to work fine:

{
  "MyItems": [
    "A",
    "B",
    "C"
  ]
}

Deserialization doesn't seem to work. Ideally, I'd like to stick to using ReadOnlyCollection<T> and Collection<T> and not have to change to some other types. This sample code throws an exception when attempting to deserialize:

var options = new JsonSerializerOptions()
{
    WriteIndented = true
};

var items = new[] { "A", "B", "C" };
var instance = new SomeClass(items);

var json = JsonSerializer.Serialize(instance, options);

var copy = JsonSerializer.Deserialize<SomeClass>(json, options);

InvalidOperationException: Each parameter in the deserialization constructor on type 'UserQuery+SomeClass' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. Fields are only considered when 'JsonSerializerOptions.IncludeFields' is enabled. The match can be case-insensitive.


Here is a .NET Fiddle example of the code running and giving an error: https://dotnetfiddle.net/vorOLX


Solution

  • The type and name of constructor parameters must match the properties in the class. System.Text.Json also knows how to construct a IReadOnlyCollection<T> directly, so just use that type. For example, you could simply do this:

    public sealed class SomeClass
    {
        [JsonConstructor]
        public SomeClass(IReadOnlyCollection<string> myItems)
                       // ^^^^ matching type         
        {
            MyItems = myItems;
        }
    
        public IReadOnlyCollection<string> MyItems { get; }
    }