How to differentiate between null and non existing data in JSON in Asp.Net Core model binding?
Just to add another 2 cents, we went the similar way to the Ilya's answer, except that we're not calling SetHasProperty
from setter, but overriding DefaultContractResolver
:
public class PatchRequestContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
prop.SetIsSpecified += (o, o1) =>
{
if (o is PatchRequest patchRequest)
{
patchRequest.SetHasProperty(prop.PropertyName);
}
};
return prop;
}
}
And then register this resolver in Startup:
services
.AddControllers()
.AddNewtonsoftJson(settings =>
settings.SerializerSettings.ContractResolver = new PatchRequestContractResolver());```
Note, that we are still using JSON.Net and not the System.Text.Json (which is default for .Net 3+) for deserializing. As of now there's no way to do things similar to DefaultContractResolver
with System.Text.Json
This is what I ended up doing, as all other options seem to be too complicated (e.g. jsonpatch, model binding) or would not give the flexibility I want.
This solution means there is a bit of a boilerplate to write for each property, but not too much:
public class UpdateRequest : PatchRequest
{
[MaxLength(80)]
[NotNullOrWhiteSpaceIfSet]
public string Name
{
get => _name;
set { _name = value; SetHasProperty(nameof(Name)); }
}
}
public abstract class PatchRequest
{
private readonly HashSet<string> _properties = new HashSet<string>();
public bool HasProperty(string propertyName) => _properties.Contains(propertyName);
protected void SetHasProperty(string propertyName) => _properties.Add(propertyName);
}
The value can then be read like this:
if (request.HasProperty(nameof(request.Name)) { /* do something with request.Name */ }
and this is how it can be validated with a custom attribute:
var patchRequest = (PatchRequest) validationContext.ObjectInstance;
if (patchRequest.HasProperty(validationContext.MemberName) {/* do validation*/}