skip to Main Content

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.

2

Answers


  1. 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.

    Login or Signup to reply.
  2. why just don’t use this

        string data = "{"Ints": [24, 43, 54, 23]}";
        
        var parsed = System.Text.Json.JsonSerializer.Deserialize<Data>(data)!;
    
       var ints = new Data { Ints = new List<int> { 24, 43, 54, 23 } };
    
    public class Data
    {
        public IReadOnlyCollection<int> Ints { get; init; }
    }
    

    but if you really want to deserialize json as a ReadOnlyCollection, you need to add a field to keep this data as ReadOnlyCollection

        var ints = System.Text.Json.JsonSerializer.Deserialize<Data>(data);
    
        var ints = new Data(new int[] { 24, 43, 54, 23 });
    
    public class Data
    {
        private  ReadOnlyCollection<int> _emptyCollection;
    
        [System.Text.Json.Serialization.JsonInclude]
        public IReadOnlyCollection<int> Ints
        {
            get { return _emptyCollection; }
            init { _emptyCollection = value.ToList().AsReadOnly(); }
        }
        
      public Data(IEnumerable<int> ints)
      {
            _emptyCollection = ints.ToList().AsReadOnly();
      }
    
      public Data()
      {
        _emptyCollection  = new Collection<int>().ToList().AsReadOnly();
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search