如何序列化接口默认属性?

How to serialize interface default property?

这个问题复活了,因为我评论错了"My bad"。 这个问题是存在的,或者我错过了什么。对于大众需求,这里是一个最小的可复制示例:

minimal reproducable example


我有一个默认界面 属性:

public interface INotification
{
    // ...
    Severity Severity { get; set; } // Severity is an Enum
    // ...
    public string SeverityName => Severity.ToString().ToLower();
}

我有一个实现class

public class Notification : INotification
{
    // ...
    public Severity Severity { get; set; }
    // ...
    // This class does not override default implementation of SeverityName
} 

问题

通知 class 没有 SeverityName 属性...这很令人惊讶,但我可以接受,通过 [=14 访问通知实例=] 接口.

不过,我想用System.Text.Json连载这个class。我怎样才能序列化 SeverityName 属性 呢?

除了编写自定义 JsonConverter for Notification. The serializer serializes the public properties of a type, but default interface properties are not implemented as class properties, instead they're implemented through some sort of extension mechanism. See the spec: default interface methods

之外,.NET Core 3.1 中的 System.Text.Json 无法做到这一点

Add support for virtual extension methods - methods in interfaces with concrete implementations. .

只有当默认接口 属性 被 重写 时,实例 属性 才会真正添加到实现的具体类型中,从而成为可序列化的。

为了确认,如果我们从您的 fiddle 中修改 classes 如下:

public interface INotification
{
    Severity Severity { get; set; }
    string Message { get; set; }

    static string MakeDefaultSeverityName<TNotification>(TNotification notification) where TNotification : INotification => notification?.Severity.ToString().ToLower();

    public string SeverityName => MakeDefaultSeverityName(this);
}

public class Notification : INotification
{
    public Severity Severity { get; set; }
    public string Message { get; set; }
}

public class NotificationWithOverride : Notification
{
    public string SeverityName => INotification.MakeDefaultSeverityName(this);
}

并使用反射打印出两种类型的属性和方法:

Console.WriteLine("Properties of {0}: {1}", typeof(TNotification).Name, string.Join(", ", typeof(TNotification).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Select(p => p.Name)));
Console.WriteLine("Methods of {0}: {1}", typeof(TNotification).Name, string.Join(", ", typeof(TNotification).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Select(p => p.Name)));

我们得到以下 Notification 的结果:

Properties of Notification: Severity, Message
Methods of Notification: get_Severity, set_Severity, get_Message, set_Message, GetType, MemberwiseClone, Finalize, ToString, Equals, GetHashCode

对于NotificationWithOverride

Properties of NotificationWithOverride: SeverityName, Severity, Message
Methods of NotificationWithOverride: get_SeverityName, get_Severity, set_Severity, get_Message, set_Message, GetType, MemberwiseClone, Finalize, ToString, Equals, GetHashCode

请注意,Notification 中没有对应于 SeverityName 的实例 属性 或方法——但 NotificationWithOverride 中有。缺少要序列化的 属性,序列化程序不会输出 SeverityName 值。

备注:

  1. System.Text.Json 在这方面与其他序列化器一致;它和 Json.NET 都生成完全相同的 JSON:

    • {"Severity":0,"Message":"Message"} 对于 Notification
    • {"SeverityName":"info","Severity":0,"Message":"Message"} 对于 NotificationWithOverride
  2. 使用 Json.NET 可以创建一个 custom contract resolver that automatically adds in the missing default properties, but System.Text.Json does not have public access to its contract resolver; for confirmation see the question 答案是,目前没有.

  3. 在 c# 8.0 中,无法覆盖具体 class 中的默认接口方法并调用基接口方法。有关详细信息,请参阅 , and Default interface implementations and base() calls

    为了解决这个问题,我向 INotification 添加了一个 static 接口方法来封装默认逻辑,然后从默认接口实例调用它方法和覆盖 class 方法。

显示上述内容的演示 fiddle 可以找到 here