This is a known bug or limitation with System.Text.Json:
The workaround suggested by Microsoft is to mark the child property with the same [JsonIgnore]
attribute as the parent property:
public class Parent
{
[JsonIgnore] public virtual string ParentProperty { get; set; }
}
public class Child : Parent
{
[JsonIgnore] public override string ParentProperty { get; set; }
public string ChildProperty { get; set; }
}
Demo fiddle #1 here.
Alternatively, in .NET 7 and later, you can use contract customization to add a modifier that applies the JsonIgnoreAttribute
of base properties to inherited properties:
public static partial class JsonExtensions
{
public static Action<JsonTypeInfo> InheritJsonIgnoreAttributes { get; } =
static typeInfo =>
{
if (typeInfo.Kind != JsonTypeInfoKind.Object)
return;
foreach (var property in typeInfo.Properties)
{
if (property.ShouldSerialize != null)
continue;
var attr = property.GetPropertyInfo()?.GetCustomAttribute<JsonIgnoreAttribute>(true);
if (attr != null)
{
var shouldSerialize = attr.Condition switch
{
JsonIgnoreCondition.Always => static (_, _) => false,
JsonIgnoreCondition.WhenWritingNull => static (_, v) => v != null,
JsonIgnoreCondition.WhenWritingDefault => !(property.PropertyType.IsValueType || Nullable.GetUnderlyingType(property.PropertyType) != null)
? static (_, v) => v != null
: ((IShouldSerializeProvider)Activator.CreateInstance(typeof(DefaultIgnorer<>).MakeGenericType(property.PropertyType))!).ShouldSerialize,
JsonIgnoreCondition.Never => static (_, v) => true,
_ => null,
};
if (shouldSerialize != null)
property.ShouldSerialize = shouldSerialize;
}
}
};
public static PropertyInfo? GetPropertyInfo(this JsonPropertyInfo property) => (property.AttributeProvider as PropertyInfo);
interface IShouldSerializeProvider { Func<object, object?, bool> ShouldSerialize { get; } }
class DefaultIgnorer<T> : IShouldSerializeProvider
{
readonly IEqualityComparer<T> comparer = EqualityComparer<T>.Default;
public Func<object, object?, bool> ShouldSerialize { get => (obj, value) => value == null ? false : !comparer.Equals(default(T), (T)value); }
}
}
Then serialize as follows:
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { JsonExtensions.InheritJsonIgnoreAttributes },
},
};
var json = JsonSerializer.Serialize(child, options);
Demo fiddle #2 here.