使用 System.Text.Json 序列化和反序列化 IReadOnlyCollection<T>

Serializing and deserializing IReadOnlyCollection<T> using System.Text.Json

我有一个 (C#) 对象,我想使用 .NET 6 System.Text.Json 序列化程序对其进行序列化。我的对象看起来像这样:

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

当我实例化并序列化它时,我得到一个看起来像预期的 JSON 字符串:

{
  "Name":"Foo Bar",
  "EmailAddress":"my-email@domain.com",
  "Substitutions":[
    {"Key":"Firstname","Value":"Foo"},
    {"Key":"Lastname","Value":"Bar"}
  ]
}

现在当我尝试将这个值反序列化回一个对象时,我得到了这个奇怪的错误:

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

我几乎已经发现 IReadOnlyCollection 是这里的问题所在。它列在 JSON 序列化/反序列化支持的数据类型中,所以我希望它能工作。该错误说明构造函数缺少接受所有序列化的字段。我玩了一下外壳,所以 JSON 中的字段与属性的名称相匹配,不幸的是没有成功。

该对象包含此构造函数:

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

我以为我在那里很好,但显然不是 ;) 我的想法是我想在对象中维护替换列表,因此公开只读副本是强制性行为。

我的问题是,如何正确反序列化这个对象? 非常感谢!

这是问题的答案,但功劳全部归功于 Paul Karam,因为他在评论中提出了解决方案。

我更改了 Recipient 对象的构造函数,所以它看起来像这样:

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

这有效,但是...我的对象有多个构造函数,现在序列化程序不知道要使用哪个构造函数并且结果不一致。要解决这个问题,您可以使用 JsonConstructor 属性提示正确的构造函数。我的对象现在看起来像这样并且工作起来很有魅力:

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>();
}

这实际上取决于您的业务需求,但您可以完全删除 _substitutions 并像这样声明 Recipient

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; }
}

Substitutions 上的私有 setter 使 System.Text.Json 有机会实际设置此 属性。

不需要特殊的构造函数,可以这样反序列化

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(); }
    }
}