Search code examples
c#json.netdeserializationsystem.text.json

Deserialization using System.Text.Json fails when constructor parameter doesn't have the same type as the matching property


What would be the best way of making STJ deserialize correctly for cases where the type of the constructor parameter does not match the type of the property with a matching name?

Consider the following code sample:

using System.Text.Json;

string data = """
              {
                "Ints": [24, 43, 54, 23]
              }
              """;

var parsed = JsonSerializer.Deserialize<Data>(data)!;

class Data
{
    public Data(IEnumerable<int> ints)
    {
        Ints = ints.ToList().AsReadOnly();
    }

    public IReadOnlyCollection<int> Ints { get; }
}

The type of the constructor parameter ints does not match the type of the property Ints. This makes deserialization fail although this use case looks very reasonable.

Deserialization works as expected with Newtonsoft but I'd like to use STJ if possible.

The exception message:

System.InvalidOperationException:
Each parameter in the deserialization constructor on type 'Data' 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.

Solution

  • Check out the How to use immutable types and non-public accessors with System.Text.Json docs. One way to handle is to introduce JsonConstructor:

    class Data
    {
        [JsonConstructor()]
        public Data(IReadOnlyCollection<int> ints)
        {
            Ints = ints; // or create the copy.
        }
    
        public Data(IEnumerable<int> ints)
        {
            Ints = ints.ToList().AsReadOnly();
        }
    
        public IReadOnlyCollection<int> Ints { get; }
    }
    

    Or using property with private setter and parameterless ctor:

    class Data
    {
        public Data()
        {
            Ints = new int[] { };
        }
    
        public Data(IEnumerable<int> ints)
        {
            Ints = ints.ToList().AsReadOnly();
        }
    
        [JsonInclude]
        public IReadOnlyCollection<int> Ints { get; private set; }
    }
    

    Other than that you can look into creating a custom converter.