2

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

myermian
  • 31,823
  • 24
  • 123
  • 215

2 Answers2

2

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; }
}
DavidG
  • 113,891
  • 12
  • 217
  • 223
  • This fixed it, but it looks like I'll have to create another constructor then, because I use this constructor with the `IEnumerable` all over the place. – myermian Apr 11 '23 at 17:18
1

From the docs:

The parameter names of a parameterized constructor must match the property names and types.

You can try using IReadOnlyCollection and match the types:

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


    public IReadOnlyCollection<string> MyItems { get; }
    private Collection<string> InternalMyItems { get; }
}

If needed you can keep the original ctor:

public sealed class SomeClass
{
    [System.Text.Json.Serialization.JsonConstructor()]
    public SomeClass(IReadOnlyCollection<string> myItems)
    {
        InternalMyItems = new Collection<string>(myItems.ToArray());
        this.MyItems = new ReadOnlyCollection<string>(InternalMyItems);
    }

    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; }
}
Guru Stron
  • 102,774
  • 10
  • 95
  • 132