Home > Back-end >  Serializing and deserializing IReadOnlyCollection<T> using System.Text.Json
Serializing and deserializing IReadOnlyCollection<T> using System.Text.Json

Time:03-08

I have a (C#) object that I want to serialize using the .NET 6 System.Text.Json serializer. My object looks like so:

private List<Substitution> _substitutions;
public string? Name { get; private set; }
public string EmailAddress { get; private set; }
public IReadOnlyCollection<Substitution> Substitutions => _substitutions.AsReadOnly();

When I instanciate is and serialize it, I get a JSON string that looks like expected:

{
  "Name":"Foo Bar",
  "EmailAddress":"[email protected]",
  "Substitutions":[
    {"Key":"Firstname","Value":"Foo"},
    {"Key":"Lastname","Value":"Bar"}
  ]
}

Now when I try to deserialize this value back to an object, I get this fancy error:

Each parameter in the deserialization constructor on type 'objectname' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. The match can be case-insensitive.'

I already pretty much found out the IReadOnlyCollection is the problem here. It is listed in the supported data types for JSON Serialization / Deserialization so I expected this to work. The error describes that a constructor is missing accepting all the fields serialized. I played a little bit with the casing so the fields in JSON match the names of the properties, unfortunately without success.

The object contains this constructor:

public Recipient(string name, string emailAddress, List<Substitution> substitutions)
{
    EmailAddress = emailAddress;
    Name = name;
    _substitutions = substitutions;
}

I thought I was fine there, but apparently not ;) The idea is that I want to maintain the list of substitutions within the object so exposing a ready-only copy is mandatory behavior.

My question is, how to deserialize this object correctly? Thanks a bunch!

CodePudding user response:

You could use https://docs.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonconstructorattribute?view=net-6.0 with a private empty constructor to tell the deserializer to use the empty constructor instead, and let it deserialize based on the properties. Should work!

CodePudding user response:

It really depends on your business needs, but you could remove _substitutions at all and declare Recipient like this:

public class Recipient
{
    public Recipient(string name, string emailAddress, IReadOnlyCollection<Substitution> substitutions)
    {
        EmailAddress = emailAddress;
        Name = name;
        Substitutions = substitutions;
    }

    public string? Name { get; private set; }
    public string EmailAddress { get; private set; }
    public IReadOnlyCollection<Substitution> Substitutions { get; private set; }
}

The private setter on Substitutions gives System.Text.Json the chance to actually set this property.

CodePudding user response:

you don' t need a special constructor, you can deserialize this way

public class Recipient
{
    private List<Substitution>? _substitutions;
    private string? _name;
    private string? _emailAddress;

    public string? Name
    {
        get { return _name; }
        set { if (_name == null) _name = value; }
    }
    public string? EmailAddress
    {
        get { return _emailAddress; }
        set { if (_emailAddress == null) _emailAddress = value; }
    }
    public IReadOnlyCollection<Substitution> Substitutions
    {
        get { return _substitutions.AsReadOnly(); }
        set { if(_substitutions==null) _substitutions = value.ToList(); }
    }
}

CodePudding user response:

This is the answer to the question, but the credits are all on Paul Karam since he came up with the solution in the comments.

I changed the constructor of the Recipient object so it looks like so:

public Recipient(string name, string emailAddress, IReadOnlyCollection<Substitution>? substitutions)
{
    EmailAddress = emailAddress;
    Name = name;
    _substitutions = substitutions != null ? substitutions.ToList() : new List<Substitution>();
}

This works, however... My object has multiple constructors and now the serializer doesn't know which constructor to use and the outcome is inconsistent. To solve this, you can hint to the correct constructor with the JsonConstructor attribute. My object now looks like so and works like a charm:

public Recipient(string emailAddress)
{
    _substitutions = new List<Substitution>();
    SetEmailAddress(emailAddress);
}

[JsonConstructor]
public Recipient(string name, string emailAddress, IReadOnlyCollection<Substitution>? substitutions)
{
    EmailAddress = emailAddress;
    Name = name;
    _substitutions = substitutions != null ? substitutions.ToList() : new List<Substitution>();
}
  • Related