Giter VIP home page Giter VIP logo

morcatko.aspnetcore.jsonmergepatch's People

Contributors

bhugot avatar eric-b avatar gcurb avatar jeffward01 avatar joerage avatar mfagadar avatar morcatko avatar mosteadman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

morcatko.aspnetcore.jsonmergepatch's Issues

Calling `.AddSystemTextJsonMergePatch()` adds the `application/merge-patch+json` as default option in Swagger

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:

image

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 ๐Ÿ˜„

Workaround

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
{
    // ...
}

ModelState reports error with internal property

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.

.NET Core 3.0

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.

  • Project structure to support both v2 and v3
  • New nuget names/versions
  • Update powershell script
  • Newtonsoft formatter
  • System.Text formatter
  • Update readme
  • ...anything else?

v3 is currently developed in net30

Newtonsoft version is available here - Morcatko.AspNetCore.JsonMergePatch.NewtonsoftJson

Using with DTO

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!

Removing property when set to null

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

Referencing this package in .NET 6 produces a warning

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:

  • Create an ASP.NET project with the following configuration:
<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:

  • Add the following snippet to the .csproj file to ignore the warning:
<PropertyGroup>
  <NoWarn>AD0001</NoWarn>
  <WarningsNotAsErrors>$(NoWarn);</WarningsNotAsErrors>
</PropertyGroup>

Use of JsonExtensionData

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?

Problem with dates

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.

System.Text.Json version not working

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.

DiffBuilder on JObject should return Empty JObject not null

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.

Add support for .Net Core 3.0

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)

Columns not in the model cause Patch based on NewtonsoftJson to crash

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?

Mapping JsonMergePatchDocument when working with DTO

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?

Option to ignore failed operations

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

Model vs Patched Value

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?

Create new instance of JsonMergePatchDocument within the code

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.

image

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?

.NET 5

It seems the current v3 works in .NET 5, but it needs a bit more testing. At least unit/integratiosn tests should be added

Fails to create patch document when dictionary key contains a slash

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

[Question] Throw missing member exception for JsonMergePatchDocument<T> only.

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?

application/json Header

Thanks for the great library. Is there anyway I can use this with an application/json header?

Update README.md

  • MvcCore
  • EnableDelete options (deletes dictionary keys, isntead of setting it to null)

Please add a licence

Hello Morcatko,

I'm interrested in using your code.
Can you add a licence to your repo (MIT, Apache, ?)
Many thanks.

NullReferenceException when patching from NULL to object

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

Cannot add new values to a dictionary

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:

  1. 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.

  2. 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.

When I make the patch request I get: {"":["Sequence contains no matching element"]}

{"":["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": {}
    }

ModelState.IsValid is false when a required property is missing

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.