I found a library that works: https://github.com/Morcatko/Morcatko.AspNetCore.JsonMergePatch
[HttpPatch]
[Consumes(JsonMergePatchDocument.ContentType)]
public void Patch([FromBody] JsonMergePatchDocument<Model> patch)
{
...
patch.ApplyTo(backendModel);
...
}
Or use patch.JsonPatchDocument.Operations to walk through patch request fields manually.
I found a library that works: https://github.com/Morcatko/Morcatko.AspNetCore.JsonMergePatch
[HttpPatch]
[Consumes(JsonMergePatchDocument.ContentType)]
public void Patch([FromBody] JsonMergePatchDocument<Model> patch)
{
...
patch.ApplyTo(backendModel);
...
}
Or use patch.JsonPatchDocument.Operations to walk through patch request fields manually.
for simple types, I found a very simple solution using Newtonsoft.Json merge of JObjects:
public static T Patched<T>(T source, JObject patch) where T : class
{
var sourceObject = JObject.FromObject(source);
sourceObject.Merge(patch, new JsonMergeSettings() {MergeArrayHandling = MergeArrayHandling.Union});
return sourceObject.ToObject<T>();
}
public static T Patched<T>(T source, string patchCode) where T : class
{
return Patched<T>(source, JObject.Parse(patchCode));
}
Hope this helps someone searching for this topic and looking for a simple solution without external packages.
You need 3 different states for email value here:
- Filled value for update (e.g.
[email protected]) nullvalue if email should be removed- Missing value if email should not be touched.
So the problem actually is how to express these 3 states in string property of your model. You can't do this with just raw string property because null value and missing value will conflict as you correctly described.
Solution is to use some flag that indicates whether the value was provided in the request. You could either have this flag as another property in your model or create a simple wrapper over string, very similar to Nullable<T> class.
I suggest creation of simple generic OptionalValue<T> class:
public class OptionalValue<T>
{
private T value;
public T Value
{
get => value;
set
{
HasValue = true;
this.value = value;
}
}
public bool HasValue { get; set; }
}
Then you need custom JsonConverter that could deserialize usual json value to OptionalValue<T>:
class OptionalValueConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(OptionalValue<T>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return new OptionalValue<T>
{
Value = (T) reader.Value,
};
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Your model will look something like this:
public class SomeModel
{
public string Surname { get; set; }
[JsonConverter(typeof(OptionalValueConverter<string>))]
public OptionalValue<string> Email { get; set; } = new OptionalValue<string>();
}
Note that you assign Email with empty OptionalValue<string>(). If input json does not contains email value than Email property will keep it OptionalValue with HasValue set to false.
If input json contains some email, even null, then OptionalValueConverter will create instance of OptionalValue with HasValue set to true.
Now in controller action you could determine any of 3 states for email:
[HttpPatch]
public void Patch([FromBody]SomeModel data)
{
if (data.Email.HasValue)
{
// Email presents in Json
if (data.Email.Value == null)
{
// Email should be removed
}
else
{
// Email should be updated
}
}
else
{
// Email does not present in Json and should not be affected
}
}
Could you use the JsonMergePatch library? https://github.com/Morcatko/Morcatko.AspNetCore.JsonMergePatch
The usage is very simple:
[HttpPatch]
[Consumes(JsonMergePatchDocument.ContentType)]
public void Patch([FromBody] JsonMergePatchDocument<Model> patch)
{
...
patch.ApplyTo(backendModel);
...
}
It appears to support setting some properties to null, and leaving other properties untouched. Internally, the JsonMergePatchDocument creates a JsonPatch document, with one OperationType.Replace for each item in the request. https://github.com/Morcatko/Morcatko.AspNetCore.JsonMergePatch/blob/master/src/Morcatko.AspNetCore.JsonMergePatch/Formatters/JsonMergePatchInputFormatter.cs
» npm install json-merge-patch