Thanks to @dbc's comment, I've got something like this in my Program.Main
builder.Services.AddMvcCore().AddJsonOptions(options => options.JsonSerializerOptions.TypeInfoResolver
= (options.JsonSerializerOptions.TypeInfoResolver ?? new DefaultJsonTypeInfoResolver())
.WithAddedModifier(ti => {
if (ti.Kind != JsonTypeInfoKind.Object) {
return;
}
foreach (JsonPropertyInfo p in ti.Properties) {
if (p.AttributeProvider?.GetCustomAttributes(
typeof(JsonIgnoreForSerializationAttribute), false).Length > 0) {
p.ShouldSerialize = (_, _) => false;
}
}
})
);
Along with a basic JsonIgnoreForSerializationAttribute class
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class JsonIgnoreForSerializationAttribute : JsonAttribute;
(Comment/Rant: I think Microsoft are wrong (https://github.com/dotnet/runtime/issues/82879) here, Serialization and Deserialization are different operations, and I should be able to setup the contract for one without needing to validate the contract for the other. But that's not going to get fixed any time soon, so this work around will do fine, even if it's a touch heavier than I'd like).