morcatko / morcatko.aspnetcore.jsonmergepatch Goto Github PK
View Code? Open in Web Editor NEWJsonMergePatch support for ASP.NET Core
License: MIT License
JsonMergePatch support for ASP.NET Core
License: MIT License
Can you use converters in combination with JsonMergePatch?
I found that using a converter which trims incoming string fields (very similar to https://stackoverflow.com/a/19272300/732846) breaks the expected behavior.
The Model property of the JsonMergePatchDocument contains the trimmed value for the patch but the PatchDocument contains the non trimmed value which is used to "patch" the object.
Is this the expected behavior? Can I get around this and somehow get the converter to play nice with the JsonMergePatch?
When I add this to a AspNet Core 3.0 project, it will fail on startup with:
TypeLoadException: Could not load type 'Microsoft.Extensions.DependencyInjection.MvcJsonMvcCoreBuilderExtensions' from assembly 'Microsoft.AspNetCore.Mvc.Formatters.Json, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.
Morcatko.AspNetCore.JsonMergePatch.MvcBuilderExtensions.AddJsonMergePatch(IMvcCoreBuilder builder, Action<JsonMergePatchOptions> configure)
It seems the current v3 works in .NET 5, but it needs a bit more testing. At least unit/integratiosn tests should be added
If you try to build a jsonpatch with no change in it, because the values are the same then the DiffBuilder is returning a null and PatchBuilder is throwing a null ref exception.
Or the DiffBuilder should return Empty JObject or the PatchBuilder should replace null JObject by an empty one.
Repro steps:
Class:
public class Model
{
public Dictionary<string, string> Properties { get; set; }
}
Payload:
{
"properties": {
"newKey": "newValue"
}
}
Expected result:
The new key is added to the dictionary.
Actual result:
An exception is thrown saying the path properties.newKey
does not exist.
Cause:
JsonMergePatch will process that patch document as a "replace" operation for "newKey". JsonPatchDocument.ApplyTo eventually throws here: https://github.com/dotnet/aspnetcore/blob/main/src/Features/JsonPatch/src/Internal/DictionaryAdapterOfTU.cs#L116.
As per JsonPatch spec, the target location must exist for remove [sic] to be successful
Possible solutions:
Process this operation as an add.
+ Adheres to JsonPatch spec. // As per JsonPatch spec, if a key already exists, adding should replace the existing value
- Doesn't solve the "remove" issue.
Supply a custom IAdapter
as part of the call to JsonPatchDocument.ApplyTo
, which would permit the replace without the key existing. Can also update remove to allow that to happen without the key existing. This can be done via the overload of JsonPatchDocument.ApplyTo
with a custom IObjectAdapter
/ IAdapterFactory
/ IAdapter
for when the Json contract is a dictionary contract.
+ Can also make "remove" (ie: { "properties": { "removedKey": null }}
) not throw if "removedKey" does not exist.
- Violates JsonPatch spec - kind of? The spec is arbitrary here because the customer never defined the JsonPatch operations themselves.
I have the code for option 2 in my project. I can port it to over and make a PR in the next day or two. Option 1 would require more work.
Hello,
I am using JsonMergePatch in combination with Newtonsoft.Json and have come across a scenario where I need to remove an already set property from a DTO. According to Section 3 of RFC7396, this should be possible by setting the property to null in the JSON merge patch.
Here is a simplified version of the code I am using for patching the object, where MyClass
is the DTO. Note that I cannot change the DTO JSON serialization attributes, since the DTO is generated based on an OpenAPI specification that is a given in my project.
using Newtonsoft.Json;
namespace Sample
{
public class MyClass
{
[JsonProperty("attributes", Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)]
public virtual MyAttributes Attributes { get; set; }
}
public class MyAttributes
{
[JsonProperty("name", Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)]
public virtual string Name { get; set; }
}
public class MyClassExtensions
{
public static MyClass Update(this MyClass myObj, string jsonPatch) {
var jsonMergePatch = Morcatko.AspNetCore.JsonMergePatch.NewtonsoftJson.Builders.PatchBuilder.Build<MyClass>(jsonPatch);
return jsonMergePatch.ApplyTo(myObj);
}
}
}
First, I create an empty MyClass instance, with the Attributes property left to null. This will be correctly serialized and de-serialized from the empty JSON object, { }
.
Next, I set the Attributes property to a non-null value. Everything still works as expected, the JSON equivalent now contains the "attributes" property and value.
Last, I attempt to unset Attributes from the MyClass instance, by calling the Update
extension method on it and providing the JSON patch { "attributes": null }
as argument. At this moment I receive the following exception when invoking the PatchBuilder.Build<>
method:
Newtonsoft.Json.JsonSerializationException : Required property 'attributes' expects a non-null value.
This error dissapears when setting Required = Required.AllowNull on the Attributes property, but as mentioned before, the DTO code is auto-generated and I cannot change it.
How can I apply a patch that sets the Attributes property back to null in the MyClass instance and removes the "attributes" key from the resulting JSON object (basically reverting the instance to the initial state)?
Thank you,
Mihai
After calling .AddSystemTextJsonMergePatch()
in the startup, it adds the application/merge-patch+json
content type to Swagger.
However, this means that other endpoints will default to this type, even if they're not PATCH
:
[HttpPut("broken/{id:int}")]
public IActionResult BrokenUpdateCustomer([FromRoute] int id, [FromBody] BrokenCustomerPutDto brokenCustomerPutDto)
{
// ...
}
Probably it defaults to this because the list is in alphabetical order:
I think this is more of an issue on Swashbuckle's side. I'm not expecting this to be fixed (probably it can't be fixed here) - I just wanted this to be documented ๐
Put [Consumes(MediaTypeNames.Application.Json)]
on either every other method that accepts a body, or just apply it to the class:
[Consumes(MediaTypeNames.Application.Json)]
public class CustomersController : ControllerBase
{
// ...
}
Hi,
I've had a look at issue #16, but I'm still unable to make it only patch values in my DTO. For example, I have the following code:
PatchBuilder.Build<TDto>(values).ApplyTo(model);
On passing a field not in the DTO, it still attempts to merge it and gives me the following error:
The target location specified by path segment 'confirmPassword' was not found.
For reference, my complete controller method is:
public virtual async Task<TDto> Update(Guid id,
[FromBody] JObject values)
{
var entity =
await _modelRepository.Update(id,
model =>
{
BeforeUpdatePatch(model, values);
PatchBuilder.Build<TDto>(values).ApplyTo(model);
BeforeUpdatePatch(model, values);
});
return _mapper.Map<TDto>(entity);
}
Hopefully I'm missing something simple!
{"":["Sequence contains no matching element"]}
The problem is when I try to patch an object:
"polygon": {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-56.152577307075276,
-34.90050773051789
],
[
-56.15264168009163,
-34.90207837638036
],
[
-56.15155270323157,
-34.901682418070315
],
[
-56.15216424688697,
-34.90130405612371
],
[
-56.15138640627265,
-34.9010048849784
],
[
-56.15100016817451,
-34.901748411254566
],
[
-56.151778008788824,
-34.902131170677464
],
[
-56.15045299753547,
-34.9018979956092
],
[
-56.151477601379156,
-34.90040213980253
],
[
-56.152577307075276,
-34.90050773051789
]
]
]
},
"properties": {}
}
Hello !
Thanks a lot about your package ! We tried to switch from Newtonsoft.Json to System.Text.Json and noticed that some of the requests started to fail in the tests. Looks like arrays work in Newtonsoft.Json impl but don't work in System.Text.Json.
Rather than copy and pasting the classes into my code and writing unit tests for them, I would love to have them as their own nuget package.
If I have a Contract that allows extension Data similar to the below:
[JsonExtensionData(ReadData = true, WriteData = true)]
public IDictionary<string, JToken>? AdditionalData { get; set; }
Then attempting to apply a patch where the data is within this extension I get a BadRequest. Is the a way that I can configure this library to support this Extension Data?
The PatchBuilder.Build method doesn't exist at all. Not sure if I can't find it or if it's internal, but it is very difficult to test this package right now.
This concerns the 6.0.0 (Asp.net core 3+) version of this package
Side note: many people still use this package, and it does exactly what and how you'd expect it to do. I sincerely hope that this is maintained, at least to the degree that issues like this might be addressed.
Problem:
Referencing this package in .NET 6 causes a warning when compiling. When projects are configured to treat warnings as errors, such a project is unable to compile.
AD0001 Analyzer 'Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzer' threw an exception of type 'System.NullReferenceException' with message 'Object reference not set to an instance of an object.'
To Reproduce:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningLevel>5</WarningLevel>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Morcatko.AspNetCore.JsonMergePatch.SystemText" Version="3.0.7" />
</ItemGroup>
</Project>
Known workarounds:
<PropertyGroup>
<NoWarn>AD0001</NoWarn>
<WarningsNotAsErrors>$(NoWarn);</WarningsNotAsErrors>
</PropertyGroup>
Are there currently any plans to remove the Newtonsoft dependency for the SystemTextJson implementation? It'd be nice to see this dependency removed if it we're opting to go with the SystemTextJson package.
As this could be considered a breaking change, maybe initially this behaviour should be opt-in.
builder.Add***JsonMergePatch(o => o.InheritRequirementsFromModel = false)
Setting this to false should result in ModelState.IsValue to be true when the only issue is that not all required properties are present in the request body.
To me it would be more logical that eventually this is the default, although the current behaviour could be useful to have in some specific situations, so the option switch seems the best way.
I'm getting an Unsupported ValueKind - Array when trying to pass an array with System.Text.Json. Any way to fix this?
Hello,
There's an issue with the DiffBuilder not handling added keys in dictionaries.
Basically I'm looking for the best way to achieve this:
We want to prevent our users to call the patch with incorrect payloads. What happened was that our tester made a small mistake with his payload (typo in a property-name) and of course the default behaviour is to ignore that property. When he did not found his change back in the database he filled a bug :-(
So we want to help our users to prevent these simple mistakes, but only when we use the JsonMergePatchDocument<T>
because we do not want to change the behaviour of other existing parts of the code-base.
Any ideas to what would be the best solution for this?
Example: You have a model with Id: int, Name: string, Telephone: string and DateCreated: DateTime.
If you send a PATCH with { Name: "John" } (with no changes for DateCreated), the date get modified to: 0001-01-01T00:00:00.
Would be useful with an option to ignore operations that cannot be applied on the target.
Suggestion:
patch.ApplyTo(backendModel, ignoreErrors: true);
or
services
.AddMvc()
.AddJsonMergePatch(o => o.IgnoreErrors = true)
Not sure IgnoreErrors
is the best name.
This is useful when the payload contains too many fields. In my specific scenario I'm dealing with polymorphism and the payload has a type
field that is not present on the target model.
But it gets worse :( the payload doesn't care about polymorphism so it'll just send null
values when a field isn't relevant. This isn't ideal, but it's what I have to deal with :/
Hope you'll consider it
Thanks for the great library. Is there anyway I can use this with an application/json header?
When you send a json to patch, when the json has fields missing from the model, when it is a class, the exception "System.InvalidOperationException : Sequence contains no matching element" is thrown.
To reproduce it, go to the unit test Morcatko.AspNetCore.JsonMergePatch.Tests.NewtonsoftJson.Patching.Attributes.CannotPatchSubObject and change
"{'obj_property': { 'id': 3 }}");
to
"{'obj_other_property': { 'id': 3 }}");
I tracked it down to Morcatko.AspNetCore.JsonMergePatch.Internal.ReflectionHelper.GetJsonProperty which calls Single on properties list and the list is missing the specified property.
I think it would be useful to be able to patch a json with a document that has a random json inside - as when you have a document DB as a storage, you don't always need to have a strongly typed structure to store it.
Maybe, there are alternatives in how to do it?
Gvien the following patch and model, the Patch.Builder.Build fails with System.InvalidOperationException: 'Sequence contains no matching element'.
class Model3
{
public IDictionary<string, Model1> Foo { get; set; }
}
var patchString = @"{ ""Foo"": {""rg/id2"": {} } }";
Exception stacktrace:
at System.Linq.ThrowHelper.ThrowNoMatchException()
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source, Func`2 predicate)
at Morcatko.AspNetCore.JsonMergePatch.Internal.ReflectionHelper.GetJsonProperty(JsonObjectContract jsonObjectContract, String propertyName) in C:\Users\jraj\Source\Repos\Morcatko.AspNetCore.JsonMergePatch2\src\3.0-JsonMergePatch.Document\Internal\ReflectionHelper.cs:line 18
at Morcatko.AspNetCore.JsonMergePatch.Internal.ReflectionHelper.GetPropertyTypeFromPath(Type type, String path, IContractResolver contractResolver) in C:\Users\jraj\Source\Repos\Morcatko.AspNetCore.JsonMergePatch2\src\3.0-JsonMergePatch.Document\Internal\ReflectionHelper.cs:line 33
at Morcatko.AspNetCore.JsonMergePatch.Internal.InternalJsonMergePatchDocument`1.AddOperation_Add(String path) in C:\Users\jraj\Source\Repos\Morcatko.AspNetCore.JsonMergePatch2\src\3.0-JsonMergePatch.Document\Internal\InternalJsonMergePatchDocumentOfT.cs:line 35
at Morcatko.AspNetCore.JsonMergePatch.NewtonsoftJson.Builders.PatchBuilder.AddOperation(IInternalJsonMergePatchDocument jsonMergePatchDocument, String pathPrefix, JObject patchObject, JsonMergePatchOptions options) in C:\Users\jraj\Source\Repos\Morcatko.AspNetCore.JsonMergePatch2\src\3.0-JsonMergePatch.NewtonsoftJson\Builders\PatchBuilder.cs:line 61
at Morcatko.AspNetCore.JsonMergePatch.NewtonsoftJson.Builders.PatchBuilder.AddOperation(IInternalJsonMergePatchDocument jsonMergePatchDocument, String pathPrefix, JObject patchObject, JsonMergePatchOptions options) in C:\Users\jraj\Source\Repos\Morcatko.AspNetCore.JsonMergePatch2\src\3.0-JsonMergePatch.NewtonsoftJson\Builders\PatchBuilder.cs:line 62
at Morcatko.AspNetCore.JsonMergePatch.NewtonsoftJson.Builders.PatchBuilder.CreatePatchDocument(Type modelType, JObject patchObject, JsonSerializer jsonSerializer, JsonMergePatchOptions options) in C:\Users\jraj\Source\Repos\Morcatko.AspNetCore.JsonMergePatch2\src\3.0-JsonMergePatch.NewtonsoftJson\Builders\PatchBuilder.cs:line 74
at Morcatko.AspNetCore.JsonMergePatch.NewtonsoftJson.Builders.PatchBuilder.CreatePatchDocument[TModel](JObject patchObject, JsonSerializer jsonSerializer, JsonMergePatchOptions options) in C:\Users\jraj\Source\Repos\Morcatko.AspNetCore.JsonMergePatch2\src\3.0-JsonMergePatch.NewtonsoftJson\Builders\PatchBuilder.cs:line 41
at Morcatko.AspNetCore.JsonMergePatch.NewtonsoftJson.Builders.PatchBuilder.Build[TModel](String jsonObjectPatch, JsonSerializerSettings serializerSettings, JsonMergePatchOptions options) in C:\Users\jraj\Source\Repos\Morcatko.AspNetCore.JsonMergePatch2\src\3.0-JsonMergePatch.NewtonsoftJson\Builders\PatchBuilder.cs:line 26
at Morcatko.AspNetCore.JsonMergePatch.Tests.NewtonsoftJson.Patching.PatchTests.PatchDictionnaryAddKeyWithSlash() in C:\Users\jraj\Source\Repos\Morcatko.AspNetCore.JsonMergePatch2\src\3.0-JsonMergePatch.Tests\NewtonsoftJson\Patching\PatchTest.cs:line 81
I am trying to use this in aspnet core minimal api. But I could not get it working. Is there a sample available for minimal api?
For a objects that contains properties of other class types (not inbuilt .net types) I am receiving the following error:
System.InvalidOperationException : Sequence contains no matching element
Gist to repro is here: https://gist.github.com/AnsonSmith/c81b042231d4031485f249f2a49bf9ee
Not sure if I'm doing something wrong, or if there is a real issue here.
Thanks,
Anson
Thank you for this project!
I want to implement a partial update for my domain model, but I don't want to expose this model to the outside world and restrict what fields could be updated. It could be achieved with DTO.
As described in this stackoverflow answer, it is possible to use automapper to map JsonPatchDocument<AccountDTO>
to JsonPatchDocument<Account>
It works when map JsonMergePatchDocument.JsonPatchDocument
in such way - all operations are mapped.
But when I map JsonMergePatchDocument
, operations are not mapped and lost.
I guess, I cant use JsonPatchDocument
directly, because it needs to go through ClearAddOperation
procedure.
Please, suggest how can I use JsonMergePatchDocument with DTOs and automapper?
Morcatko.AspNetCore.JsonMergePatch.SystemText (ASP.NET Core 3.x) does not appear to be working. We switched over to using the Newton version of the library which works nicely, but our preference is to use the System.Text.Json version since we have a client sdk built using System.Text.Json and it makes it cumbersome to pass Patch documents to our webapi, also built on System.Text.Json.
I am having trouble even compiling tests with the builder as demonstrated in your examples. The same test code works fine with the Newtonsoft version.
MS has rewritten input formatters + MVC a bit in ASP.NET Core 3 so it is unlikely that JsonMergePatch v3 will be backward compatible.
There are 2 JSON serializers now - Newtonsoft + System.Text - both should be supported eventually.
v3 is currently developed in net30
Newtonsoft version is available here - Morcatko.AspNetCore.JsonMergePatch.NewtonsoftJson
How to create an instance of this class for unit testing? Using the v6 document package
One of the identified items was in an invalid format
Hi, thank you for this repo.
It's really save a lot of time.
I have an issue. I need creat new instance of JsonMergePatchDocument for unit test.
But when i create a class list with operation is empty.
Is it possible to add some new class constructor to JsonMergePatchDocument to init class from code in right way?
Or there is another way to create class?
Hello Morcatko,
I'm interrested in using your code.
Can you add a licence to your repo (MIT, Apache, ?)
Many thanks.
I can't seem to get this scenario to work:
Model {"a":"b"}
Patch {"x":{"y":"z"}}
Result {"a":"b", "x":{"y":"z"}}
To illustrate, in PatchTest.PatchObject() replace this line:
await server.PostAsync("api/data/0", GetTestModel());
with this line:
await server.PostAsync("api/data/0", new TestModel());
The error occurs when applying the underlying JsonPatch:
_jsonPatchDocument.ApplyTo(objectToApplyTo);
The issue seems to be that there are operations for the subModel properties, but not for the subModel itself.
To test, I changed JsonMergePatchInputFormatter.AddOperation()
to instead add a replace operation for the object property instead of recursing and it worked for this case, however it breaks the ability to patch nested properties.
-Ed
I have a 2.1 project I would like to use this in, so I added support for the latest in a pull request #5.
We have in our models some DataAnnotations.
[StringLength(40)]
public string PhoneNumber { get; set; }
When we use the normal Mvc Binder, we get an error in the model-state that looks like this:
"PhoneNumber": [
"The field PhoneNumber must be a string with a maximum length of 40."
]
However, when I change the signature of the action from [FromBody] Profile profile
to [FromBody] Morcatko.AspNetCore.JsonMergePatch.JsonMergePatchDocument<Profile> profile
we get the following result:
"Model.PhoneNumber": [
"The field PhoneNumber must be a string with a maximum length of 40."
]
I think that the extra 'Model.'-prefix is from an internal property. But our users don't know of this property, so we would like a change that this extra property isn't reported in the ModelState.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.