diff --git a/src/Microsoft.AspNetCore.JsonPatch/Internal/ObjectVisitor.cs b/src/Microsoft.AspNetCore.JsonPatch/Internal/ObjectVisitor.cs index a988afa300..6cb4d7450c 100644 --- a/src/Microsoft.AspNetCore.JsonPatch/Internal/ObjectVisitor.cs +++ b/src/Microsoft.AspNetCore.JsonPatch/Internal/ObjectVisitor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections; using Microsoft.AspNetCore.JsonPatch.Adapters; using Newtonsoft.Json.Serialization; @@ -56,6 +55,13 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal adapter = null; return false; } + + // If we hit a null on an interior segment then we need to stop traversing. + if (next == null) + { + adapter = null; + return false; + } target = next; adapter = SelectAdapter(target); diff --git a/test/Microsoft.AspNetCore.JsonPatch.Test/IntegrationTests/SimpleObjectIntegrationTest.cs b/test/Microsoft.AspNetCore.JsonPatch.Test/IntegrationTests/SimpleObjectIntegrationTest.cs index 669c6d7af4..4672d9c97b 100644 --- a/test/Microsoft.AspNetCore.JsonPatch.Test/IntegrationTests/SimpleObjectIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.JsonPatch.Test/IntegrationTests/SimpleObjectIntegrationTest.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Dynamic; +using Microsoft.AspNetCore.JsonPatch.Exceptions; using Xunit; namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests @@ -124,5 +126,30 @@ namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests Assert.Equal(newGuid, targetObject.GuidValue); } + // https://github.com/aspnet/AspNetCore/issues/3634 + [Fact] + public void Regression_AspNetCore3634() + { + // Assert + var document = new JsonPatchDocument(); + document.Move("/Object", "/Object/goodbye"); + + dynamic @object = new ExpandoObject(); + @object.hello = "world"; + + var target = new Regression_AspNetCore3634_Object(); + target.Object = @object; + + // Act + var ex = Assert.Throws(() => document.ApplyTo(target)); + + // Assert + Assert.Equal("For operation 'move', the target location specified by path '/Object/goodbye' was not found.", ex.Message); + } + + private class Regression_AspNetCore3634_Object + { + public dynamic Object { get; set; } + } } } diff --git a/test/Microsoft.AspNetCore.JsonPatch.Test/Internal/ObjectVisitorTest.cs b/test/Microsoft.AspNetCore.JsonPatch.Test/Internal/ObjectVisitorTest.cs index cb299cf0dc..44f6fc81ae 100644 --- a/test/Microsoft.AspNetCore.JsonPatch.Test/Internal/ObjectVisitorTest.cs +++ b/test/Microsoft.AspNetCore.JsonPatch.Test/Internal/ObjectVisitorTest.cs @@ -205,6 +205,22 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal Assert.IsType(adapter); } + [Fact] + public void Visit_NullInteriorTarget_ReturnsFalse() + { + // Arrange + var visitor = new ObjectVisitor(new ParsedPath("/States/0"), new DefaultContractResolver()); + + // Act + object target = new Class1() { States = null, }; + var visitStatus = visitor.TryVisit(ref target, out var adapter, out var message); + + // Assert + Assert.False(visitStatus); + Assert.Null(adapter); + Assert.Null(message); + } + [Fact] public void Visit_NullTarget_ReturnsNullAdapter() {