Reputation: 35913
This question is resurrected, because I was wrong in comment "My bad". This issue is there, or I miss something. For popular demand here is a minimal reproducable example:
I have an interface with a default property:
public interface INotification
{
// ...
Severity Severity { get; set; } // Severity is an Enum
// ...
public string SeverityName => Severity.ToString().ToLower();
}
I have an implementation class
public class Notification : INotification
{
// ...
public Severity Severity { get; set; }
// ...
// This class does not override default implementation of SeverityName
}
Question
The Notification class does not have a SeverityName
property... this was surprising, but I can live with that, accessing to the notification instance via the INotification
interface.
However, I would like to serialize this class with System.Text.Json
. How can I serialize SeverityName
property too?
Upvotes: 3
Views: 926
Reputation: 116980
This is not possible with System.Text.Json
in .NET Core 3.1 other than by writing a custom 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
Add support for virtual extension methods - methods in interfaces with concrete implementations. .
Only when the default interface property is overridden does an instance property actually get added to the implementing concrete type, and thereby become serializable.
For confirmation, if we modify the classes from your fiddle as follows:
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);
}
And print out the properties and methods of both types using reflection:
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)));
We get the following results for Notification
:
Properties of Notification: Severity, Message
Methods of Notification: get_Severity, set_Severity, get_Message, set_Message, GetType, MemberwiseClone, Finalize, ToString, Equals, GetHashCode
And for 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
Notice there's just no instance property or method corresponding to SeverityName
in Notification
-- but there is in NotificationWithOverride
. Lacking a property to serialize, the serializer does not output a SeverityName
value.
Notes:
System.Text.Json
is consistent with other serializers in this respect; both it and Json.NET generate exactly the same JSON:
{"Severity":0,"Message":"Message"}
for Notification
.{"SeverityName":"info","Severity":0,"Message":"Message"}
for NotificationWithOverride
.With Json.NET it might be possible to create a 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 System.Text.Json API is there something like IContractResolver to which the answer is, not currently.
In c# 8.0 it is not possible for to override a default interface method in a concrete class and call the base interface method. See How can I call the default method instead of the concrete implementation, Making member virtual prevents calling default interface implementation and causes StackOverflowException in C# 8 and Default interface implementations and base()
calls for details.
To work around this I added a static
interface method to INotification
that encapsulates the default logic, then called it from both the default interface instance method and the override class method.
A demo fiddle that shows the above can be found here.
Upvotes: 2