diff --git a/src/Features/JsonPatch/src/Adapters/IObjectAdapter.cs b/src/Features/JsonPatch/src/Adapters/IObjectAdapter.cs
new file mode 100644
index 0000000000..e5206bfa0d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Adapters/IObjectAdapter.cs
@@ -0,0 +1,112 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Operations;
+
+namespace Microsoft.AspNetCore.JsonPatch.Adapters
+{
+ ///
+ /// Defines the operations that can be performed on a JSON patch document.
+ ///
+ public interface IObjectAdapter
+ {
+ ///
+ /// Using the "add" operation a new value is inserted into the root of the target
+ /// document, into the target array at the specified valid index, or to a target object at
+ /// the specified location.
+ ///
+ /// When adding to arrays, the specified index MUST NOT be greater than the number of elements in the array.
+ /// To append the value to the array, the index of "-" character is used (see [RFC6901]).
+ ///
+ /// When adding to an object, if an object member does not already exist, a new member is added to the object at the
+ /// specified location or if an object member does exist, that member's value is replaced.
+ ///
+ /// The operation object MUST contain a "value" member whose content
+ /// specifies the value to be added.
+ ///
+ /// For example:
+ ///
+ /// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-4
+ ///
+ /// The add operation.
+ /// Object to apply the operation to.
+ void Add(Operation operation, object objectToApplyTo);
+
+ ///
+ /// Using the "copy" operation, a value is copied from a specified location to the
+ /// target location.
+ ///
+ /// The operation object MUST contain a "from" member, which references the location in the
+ /// target document to copy the value from.
+ ///
+ /// The "from" location MUST exist for the operation to be successful.
+ ///
+ /// For example:
+ ///
+ /// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-7
+ ///
+ /// The copy operation.
+ /// Object to apply the operation to.
+ void Copy(Operation operation, object objectToApplyTo);
+
+ ///
+ /// Using the "move" operation the value at a specified location is removed and
+ /// added to the target location.
+ ///
+ /// The operation object MUST contain a "from" member, which references the location in the
+ /// target document to move the value from.
+ ///
+ /// The "from" location MUST exist for the operation to be successful.
+ ///
+ /// For example:
+ ///
+ /// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
+ ///
+ /// A location cannot be moved into one of its children.
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
+ ///
+ /// The move operation.
+ /// Object to apply the operation to.
+ void Move(Operation operation, object objectToApplyTo);
+
+ ///
+ /// Using the "remove" operation the value at the target location is removed.
+ ///
+ /// The target location MUST exist for the operation to be successful.
+ ///
+ /// For example:
+ ///
+ /// { "op": "remove", "path": "/a/b/c" }
+ ///
+ /// If removing an element from an array, any elements above the
+ /// specified index are shifted one position to the left.
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
+ ///
+ /// The remove operation.
+ /// Object to apply the operation to.
+ void Remove(Operation operation, object objectToApplyTo);
+
+ ///
+ /// Using the "replace" operation he value at the target location is replaced
+ /// with a new value. The operation object MUST contain a "value" member
+ /// which specifies the replacement value.
+ ///
+ /// The target location MUST exist for the operation to be successful.
+ ///
+ /// For example:
+ ///
+ /// { "op": "replace", "path": "/a/b/c", "value": 42 }
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
+ ///
+ /// The replace operation.
+ /// Object to apply the operation to.
+ void Replace(Operation operation, object objectToApplyTo);
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Adapters/IObjectAdapterWithTest.cs b/src/Features/JsonPatch/src/Adapters/IObjectAdapterWithTest.cs
new file mode 100644
index 0000000000..e1b4ce7950
--- /dev/null
+++ b/src/Features/JsonPatch/src/Adapters/IObjectAdapterWithTest.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Operations;
+
+namespace Microsoft.AspNetCore.JsonPatch.Adapters
+{
+ ///
+ /// Defines the operations that can be performed on a JSON patch document, including "test".
+ ///
+ public interface IObjectAdapterWithTest : IObjectAdapter
+ {
+ ///
+ /// Using the "test" operation a value at the target location is compared for
+ /// equality to a specified value.
+ ///
+ /// The operation object MUST contain a "value" member that specifies
+ /// value to be compared to the target location's value.
+ ///
+ /// The target location MUST be equal to the "value" value for the
+ /// operation to be considered successful.
+ ///
+ /// For example:
+ /// { "op": "test", "path": "/a/b/c", "value": "foo" }
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-7
+ ///
+ /// The test operation.
+ /// Object to apply the operation to.
+ void Test(Operation operation, object objectToApplyTo);
+ }
+}
diff --git a/src/Features/JsonPatch/src/Adapters/ObjectAdapter.cs b/src/Features/JsonPatch/src/Adapters/ObjectAdapter.cs
new file mode 100644
index 0000000000..73095b52c2
--- /dev/null
+++ b/src/Features/JsonPatch/src/Adapters/ObjectAdapter.cs
@@ -0,0 +1,328 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Internal;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Adapters
+{
+ ///
+ public class ObjectAdapter : IObjectAdapterWithTest
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The .
+ /// The for logging .
+ public ObjectAdapter(
+ IContractResolver contractResolver,
+ Action logErrorAction)
+ {
+ ContractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
+ LogErrorAction = logErrorAction;
+ }
+
+ ///
+ /// Gets or sets the .
+ ///
+ public IContractResolver ContractResolver { get; }
+
+ ///
+ /// Action for logging .
+ ///
+ public Action LogErrorAction { get; }
+
+ public void Add(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ Add(operation.path, operation.value, objectToApplyTo, operation);
+ }
+
+ ///
+ /// Add is used by various operations (eg: add, copy, ...), yet through different operations;
+ /// This method allows code reuse yet reporting the correct operation on error
+ ///
+ private void Add(
+ string path,
+ object value,
+ object objectToApplyTo,
+ Operation operation)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ var parsedPath = new ParsedPath(path);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver);
+
+ var target = objectToApplyTo;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToApplyTo, path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+
+ if (!adapter.TryAdd(target, parsedPath.LastSegment, ContractResolver, value, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+ }
+
+ public void Move(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ // Get value at 'from' location and add that value to the 'path' location
+ if (TryGetValue(operation.from, objectToApplyTo, operation, out var propertyValue))
+ {
+ // remove that value
+ Remove(operation.from, objectToApplyTo, operation);
+
+ // add that value to the path location
+ Add(operation.path,
+ propertyValue,
+ objectToApplyTo,
+ operation);
+ }
+ }
+
+ public void Remove(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ Remove(operation.path, objectToApplyTo, operation);
+ }
+
+ ///
+ /// Remove is used by various operations (eg: remove, move, ...), yet through different operations;
+ /// This method allows code reuse yet reporting the correct operation on error. The return value
+ /// contains the type of the item that has been removed (and a bool possibly signifying an error)
+ /// This can be used by other methods, like replace, to ensure that we can pass in the correctly
+ /// typed value to whatever method follows.
+ ///
+ private void Remove(string path, object objectToApplyTo, Operation operationToReport)
+ {
+ var parsedPath = new ParsedPath(path);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver);
+
+ var target = objectToApplyTo;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToApplyTo, path, operationToReport, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+
+ if (!adapter.TryRemove(target, parsedPath.LastSegment, ContractResolver, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, path, operationToReport, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+ }
+
+ public void Replace(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ var parsedPath = new ParsedPath(operation.path);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver);
+
+ var target = objectToApplyTo;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToApplyTo, operation.path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+
+ if (!adapter.TryReplace(target, parsedPath.LastSegment, ContractResolver, operation.value, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+ }
+
+ public void Copy(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ // Get value at 'from' location and add that value to the 'path' location
+ if (TryGetValue(operation.from, objectToApplyTo, operation, out var propertyValue))
+ {
+ // Create deep copy
+ var copyResult = ConversionResultProvider.CopyTo(propertyValue, propertyValue.GetType());
+ if (copyResult.CanBeConverted)
+ {
+ Add(operation.path,
+ copyResult.ConvertedInstance,
+ objectToApplyTo,
+ operation);
+ }
+ else
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, Resources.FormatCannotCopyProperty(operation.from));
+ ErrorReporter(error);
+ return;
+ }
+ }
+ }
+
+ public void Test(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ var parsedPath = new ParsedPath(operation.path);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver);
+
+ var target = objectToApplyTo;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToApplyTo, operation.path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+
+ if (!adapter.TryTest(target, parsedPath.LastSegment, ContractResolver, operation.value, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+ }
+
+ private bool TryGetValue(
+ string fromLocation,
+ object objectToGetValueFrom,
+ Operation operation,
+ out object propertyValue)
+ {
+ if (fromLocation == null)
+ {
+ throw new ArgumentNullException(nameof(fromLocation));
+ }
+
+ if (objectToGetValueFrom == null)
+ {
+ throw new ArgumentNullException(nameof(objectToGetValueFrom));
+ }
+
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ propertyValue = null;
+
+ var parsedPath = new ParsedPath(fromLocation);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver);
+
+ var target = objectToGetValueFrom;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToGetValueFrom, fromLocation, operation, errorMessage);
+ ErrorReporter(error);
+ return false;
+ }
+
+ if (!adapter.TryGet(target, parsedPath.LastSegment, ContractResolver, out propertyValue, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToGetValueFrom, fromLocation, operation, errorMessage);
+ ErrorReporter(error);
+ return false;
+ }
+
+ return true;
+ }
+
+ private Action ErrorReporter
+ {
+ get
+ {
+ return LogErrorAction ?? Internal.ErrorReporter.Default;
+ }
+ }
+
+ private JsonPatchError CreateOperationFailedError(object target, string path, Operation operation, string errorMessage)
+ {
+ return new JsonPatchError(
+ target,
+ operation,
+ errorMessage ?? Resources.FormatCannotPerformOperation(operation.op, path));
+ }
+
+ private JsonPatchError CreatePathNotFoundError(object target, string path, Operation operation, string errorMessage)
+ {
+ return new JsonPatchError(
+ target,
+ operation,
+ errorMessage ?? Resources.FormatTargetLocationNotFound(operation.op, path));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Converters/JsonPatchDocumentConverter.cs b/src/Features/JsonPatch/src/Converters/JsonPatchDocumentConverter.cs
new file mode 100644
index 0000000000..aed9c48474
--- /dev/null
+++ b/src/Features/JsonPatch/src/Converters/JsonPatchDocumentConverter.cs
@@ -0,0 +1,76 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Converters
+{
+ public class JsonPatchDocumentConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ return true;
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
+ JsonSerializer serializer)
+ {
+ if (objectType != typeof(JsonPatchDocument))
+ {
+ throw new ArgumentException(Resources.FormatParameterMustMatchType("objectType", "JsonPatchDocument"), "objectType");
+ }
+
+ try
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return null;
+ }
+
+ // load jObject
+ var jObject = JArray.Load(reader);
+
+ // Create target object for Json => list of operations
+ var targetOperations = new List();
+
+ // Create a new reader for this jObject, and set all properties
+ // to match the original reader.
+ var jObjectReader = jObject.CreateReader();
+ jObjectReader.Culture = reader.Culture;
+ jObjectReader.DateParseHandling = reader.DateParseHandling;
+ jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
+ jObjectReader.FloatParseHandling = reader.FloatParseHandling;
+
+ // Populate the object properties
+ serializer.Populate(jObjectReader, targetOperations);
+
+ // container target: the JsonPatchDocument.
+ var container = new JsonPatchDocument(targetOperations, new DefaultContractResolver());
+
+ return container;
+ }
+ catch (Exception ex)
+ {
+ throw new JsonSerializationException(Resources.InvalidJsonPatchDocument, ex);
+ }
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ if (value is IJsonPatchDocument)
+ {
+ var jsonPatchDoc = (IJsonPatchDocument)value;
+ var lst = jsonPatchDoc.GetOperations();
+
+ // write out the operations, no envelope
+ serializer.Serialize(writer, lst);
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Converters/TypedJsonPatchDocumentConverter.cs b/src/Features/JsonPatch/src/Converters/TypedJsonPatchDocumentConverter.cs
new file mode 100644
index 0000000000..fd779ba4ee
--- /dev/null
+++ b/src/Features/JsonPatch/src/Converters/TypedJsonPatchDocumentConverter.cs
@@ -0,0 +1,64 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Converters
+{
+ public class TypedJsonPatchDocumentConverter : JsonPatchDocumentConverter
+ {
+ public override object ReadJson(
+ JsonReader reader,
+ Type objectType,
+ object existingValue,
+ JsonSerializer serializer)
+ {
+ try
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return null;
+ }
+
+ var genericType = objectType.GetTypeInfo().GenericTypeArguments[0];
+
+ // load jObject
+ var jObject = JArray.Load(reader);
+
+ // Create target object for Json => list of operations, typed to genericType
+ var genericOperation = typeof(Operation<>);
+ var concreteOperationType = genericOperation.MakeGenericType(genericType);
+
+ var genericList = typeof(List<>);
+ var concreteList = genericList.MakeGenericType(concreteOperationType);
+
+ var targetOperations = Activator.CreateInstance(concreteList);
+
+ //Create a new reader for this jObject, and set all properties to match the original reader.
+ var jObjectReader = jObject.CreateReader();
+ jObjectReader.Culture = reader.Culture;
+ jObjectReader.DateParseHandling = reader.DateParseHandling;
+ jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
+ jObjectReader.FloatParseHandling = reader.FloatParseHandling;
+
+ // Populate the object properties
+ serializer.Populate(jObjectReader, targetOperations);
+
+ // container target: the typed JsonPatchDocument.
+ var container = Activator.CreateInstance(objectType, targetOperations, new DefaultContractResolver());
+
+ return container;
+ }
+ catch (Exception ex)
+ {
+ throw new JsonSerializationException(Resources.InvalidJsonPatchDocument, ex);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Exceptions/JsonPatchException.cs b/src/Features/JsonPatch/src/Exceptions/JsonPatchException.cs
new file mode 100644
index 0000000000..90e080575a
--- /dev/null
+++ b/src/Features/JsonPatch/src/Exceptions/JsonPatchException.cs
@@ -0,0 +1,38 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+
+namespace Microsoft.AspNetCore.JsonPatch.Exceptions
+{
+ public class JsonPatchException : Exception
+ {
+ public Operation FailedOperation { get; private set; }
+ public object AffectedObject { get; private set; }
+
+
+ public JsonPatchException()
+ {
+
+ }
+
+ public JsonPatchException(JsonPatchError jsonPatchError, Exception innerException)
+ : base(jsonPatchError.ErrorMessage, innerException)
+ {
+ FailedOperation = jsonPatchError.Operation;
+ AffectedObject = jsonPatchError.AffectedObject;
+ }
+
+ public JsonPatchException(JsonPatchError jsonPatchError)
+ : this(jsonPatchError, null)
+ {
+ }
+
+ public JsonPatchException(string message, Exception innerException)
+ : base (message, innerException)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Helpers/GetValueResult.cs b/src/Features/JsonPatch/src/Helpers/GetValueResult.cs
new file mode 100644
index 0000000000..e2e739a027
--- /dev/null
+++ b/src/Features/JsonPatch/src/Helpers/GetValueResult.cs
@@ -0,0 +1,30 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.JsonPatch.Helpers
+{
+ ///
+ /// Return value for the helper method used by Copy/Move. Needed to ensure we can make a different
+ /// decision in the calling method when the value is null because it cannot be fetched (HasError = true)
+ /// versus when it actually is null (much like why RemovedPropertyTypeResult is used for returning
+ /// type in the Remove operation).
+ ///
+ public class GetValueResult
+ {
+ public GetValueResult(object propertyValue, bool hasError)
+ {
+ PropertyValue = propertyValue;
+ HasError = hasError;
+ }
+
+ ///
+ /// The value of the property we're trying to get
+ ///
+ public object PropertyValue { get; private set; }
+
+ ///
+ /// HasError: true when an error occurred, the operation didn't complete succesfully
+ ///
+ public bool HasError { get; private set; }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Helpers/JsonPatchProperty.cs b/src/Features/JsonPatch/src/Helpers/JsonPatchProperty.cs
new file mode 100644
index 0000000000..041b0104ac
--- /dev/null
+++ b/src/Features/JsonPatch/src/Helpers/JsonPatchProperty.cs
@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ ///
+ /// Metadata for JsonProperty.
+ ///
+ public class JsonPatchProperty
+ {
+ ///
+ /// Initializes a new instance.
+ ///
+ public JsonPatchProperty(JsonProperty property, object parent)
+ {
+ if (property == null)
+ {
+ throw new ArgumentNullException(nameof(property));
+ }
+
+ if (parent == null)
+ {
+ throw new ArgumentNullException(nameof(parent));
+ }
+
+ Property = property;
+ Parent = parent;
+ }
+
+ ///
+ /// Gets or sets JsonProperty.
+ ///
+ public JsonProperty Property { get; set; }
+
+ ///
+ /// Gets or sets Parent.
+ ///
+ public object Parent { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/IJsonPatchDocument.cs b/src/Features/JsonPatch/src/IJsonPatchDocument.cs
new file mode 100644
index 0000000000..fc5f5bd4d1
--- /dev/null
+++ b/src/Features/JsonPatch/src/IJsonPatchDocument.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using System.Collections.Generic;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ public interface IJsonPatchDocument
+ {
+ IContractResolver ContractResolver { get; set; }
+
+ IList GetOperations();
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Internal/ConversionResult.cs b/src/Features/JsonPatch/src/Internal/ConversionResult.cs
new file mode 100644
index 0000000000..77181eb18d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ConversionResult.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class ConversionResult
+ {
+ public ConversionResult(bool canBeConverted, object convertedInstance)
+ {
+ CanBeConverted = canBeConverted;
+ ConvertedInstance = convertedInstance;
+ }
+
+ public bool CanBeConverted { get; }
+ public object ConvertedInstance { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Internal/ConversionResultProvider.cs b/src/Features/JsonPatch/src/Internal/ConversionResultProvider.cs
new file mode 100644
index 0000000000..71af0d27fc
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ConversionResultProvider.cs
@@ -0,0 +1,75 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Reflection;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public static class ConversionResultProvider
+ {
+ public static ConversionResult ConvertTo(object value, Type typeToConvertTo)
+ {
+ if (value == null)
+ {
+ return new ConversionResult(IsNullableType(typeToConvertTo), null);
+ }
+ else if (typeToConvertTo.IsAssignableFrom(value.GetType()))
+ {
+ // No need to convert
+ return new ConversionResult(true, value);
+ }
+ else
+ {
+ try
+ {
+ var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), typeToConvertTo);
+ return new ConversionResult(true, deserialized);
+ }
+ catch
+ {
+ return new ConversionResult(canBeConverted: false, convertedInstance: null);
+ }
+ }
+ }
+
+ public static ConversionResult CopyTo(object value, Type typeToConvertTo)
+ {
+ var targetType = typeToConvertTo;
+ if (value == null)
+ {
+ return new ConversionResult(IsNullableType(typeToConvertTo), null);
+ }
+ else if (typeToConvertTo.IsAssignableFrom(value.GetType()))
+ {
+ // Keep original type
+ targetType = value.GetType();
+ }
+ try
+ {
+ var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), targetType);
+ return new ConversionResult(true, deserialized);
+ }
+ catch
+ {
+ return new ConversionResult(canBeConverted: false, convertedInstance: null);
+ }
+ }
+
+ private static bool IsNullableType(Type type)
+ {
+ var typeInfo = type.GetTypeInfo();
+ if (typeInfo.IsValueType)
+ {
+ // value types are only nullable if they are Nullable
+ return typeInfo.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
+ }
+ else
+ {
+ // reference types are always nullable
+ return true;
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/DictionaryAdapterOfTU.cs b/src/Features/JsonPatch/src/Internal/DictionaryAdapterOfTU.cs
new file mode 100644
index 0000000000..8a344e24ee
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/DictionaryAdapterOfTU.cs
@@ -0,0 +1,245 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class DictionaryAdapter : IAdapter
+ {
+ public bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ // As per JsonPatch spec, if a key already exists, adding should replace the existing value
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ dictionary[convertedKey] = convertedValue;
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ value = null;
+ return false;
+ }
+
+ if (!dictionary.ContainsKey(convertedKey))
+ {
+ value = null;
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ value = dictionary[convertedKey];
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ return false;
+ }
+
+ // As per JsonPatch spec, the target location must exist for remove to be successful
+ if (!dictionary.ContainsKey(convertedKey))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ dictionary.Remove(convertedKey);
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ return false;
+ }
+
+ // As per JsonPatch spec, the target location must exist for remove to be successful
+ if (!dictionary.ContainsKey(convertedKey))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ dictionary[convertedKey] = convertedValue;
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryTest(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ return false;
+ }
+
+ // As per JsonPatch spec, the target location must exist for test to be successful
+ if (!dictionary.ContainsKey(convertedKey))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ var currentValue = dictionary[convertedKey];
+
+ // The target segment does not have an assigned value to compare the test value with
+ if (currentValue == null || string.IsNullOrEmpty(currentValue.ToString()))
+ {
+ errorMessage = Resources.FormatValueForTargetSegmentCannotBeNullOrEmpty(segment);
+ return false;
+ }
+
+ if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
+ {
+ errorMessage = Resources.FormatValueNotEqualToTestValue(currentValue, value, segment);
+ return false;
+ }
+ else
+ {
+ errorMessage = null;
+ return true;
+ }
+ }
+
+ public bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object nextTarget,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ nextTarget = null;
+ return false;
+ }
+
+ if (dictionary.ContainsKey(convertedKey))
+ {
+ nextTarget = dictionary[convertedKey];
+ errorMessage = null;
+ return true;
+ }
+ else
+ {
+ nextTarget = null;
+ errorMessage = null;
+ return false;
+ }
+ }
+
+ private bool TryConvertKey(string key, out TKey convertedKey, out string errorMessage)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(key, typeof(TKey));
+ if (conversionResult.CanBeConverted)
+ {
+ errorMessage = null;
+ convertedKey = (TKey)conversionResult.ConvertedInstance;
+ return true;
+ }
+ else
+ {
+ errorMessage = Resources.FormatInvalidPathSegment(key);
+ convertedKey = default(TKey);
+ return false;
+ }
+ }
+
+ private bool TryConvertValue(object value, out TValue convertedValue, out string errorMessage)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(value, typeof(TValue));
+ if (conversionResult.CanBeConverted)
+ {
+ errorMessage = null;
+ convertedValue = (TValue)conversionResult.ConvertedInstance;
+ return true;
+ }
+ else
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ convertedValue = default(TValue);
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/DynamicObjectAdapter.cs b/src/Features/JsonPatch/src/Internal/DynamicObjectAdapter.cs
new file mode 100644
index 0000000000..fb4adeb1f2
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/DynamicObjectAdapter.cs
@@ -0,0 +1,243 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using Microsoft.CSharp.RuntimeBinder;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+using CSharpBinder = Microsoft.CSharp.RuntimeBinder;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class DynamicObjectAdapter : IAdapter
+ {
+ public bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TrySetDynamicObjectProperty(target, contractResolver, segment, value, out errorMessage))
+ {
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out value, out errorMessage))
+ {
+ value = null;
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
+ {
+ return false;
+ }
+
+ // Setting the value to "null" will use the default value in case of value types, and
+ // null in case of reference types
+ object value = null;
+ if (property.GetType().GetTypeInfo().IsValueType
+ && Nullable.GetUnderlyingType(property.GetType()) == null)
+ {
+ value = Activator.CreateInstance(property.GetType());
+ }
+
+ if (!TrySetDynamicObjectProperty(target, contractResolver, segment, value, out errorMessage))
+ {
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+
+ }
+
+ public bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, property.GetType(), out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ if (!TrySetDynamicObjectProperty(target, contractResolver, segment, convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryTest(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, property.GetType(), out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ if (!JToken.DeepEquals(JsonConvert.SerializeObject(property), JsonConvert.SerializeObject(convertedValue)))
+ {
+ errorMessage = Resources.FormatValueNotEqualToTestValue(property, value, segment);
+ return false;
+ }
+ else
+ {
+ errorMessage = null;
+ return true;
+ }
+ }
+
+ public bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object nextTarget,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
+ {
+ nextTarget = null;
+ return false;
+ }
+ else
+ {
+ nextTarget = property;
+ errorMessage = null;
+ return true;
+ }
+ }
+
+ private bool TryGetDynamicObjectProperty(
+ object target,
+ IContractResolver contractResolver,
+ string segment,
+ out object value,
+ out string errorMessage)
+ {
+ var jsonDynamicContract = (JsonDynamicContract)contractResolver.ResolveContract(target.GetType());
+
+ var propertyName = jsonDynamicContract.PropertyNameResolver(segment);
+
+ var binder = CSharpBinder.Binder.GetMember(
+ CSharpBinderFlags.None,
+ propertyName,
+ target.GetType(),
+ new List
+ {
+ CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
+ });
+
+ var callsite = CallSite>.Create(binder);
+
+ try
+ {
+ value = callsite.Target(callsite, target);
+ errorMessage = null;
+ return true;
+ }
+ catch (RuntimeBinderException)
+ {
+ value = null;
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+ }
+
+ private bool TrySetDynamicObjectProperty(
+ object target,
+ IContractResolver contractResolver,
+ string segment,
+ object value,
+ out string errorMessage)
+ {
+ var jsonDynamicContract = (JsonDynamicContract)contractResolver.ResolveContract(target.GetType());
+
+ var propertyName = jsonDynamicContract.PropertyNameResolver(segment);
+
+ var binder = CSharpBinder.Binder.SetMember(
+ CSharpBinderFlags.None,
+ propertyName,
+ target.GetType(),
+ new List
+ {
+ CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
+ CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
+ });
+
+ var callsite = CallSite>.Create(binder);
+
+ try
+ {
+ callsite.Target(callsite, target, value);
+ errorMessage = null;
+ return true;
+ }
+ catch (RuntimeBinderException)
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+ }
+
+ private bool TryConvertValue(object value, Type propertyType, out object convertedValue)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(value, propertyType);
+ if (!conversionResult.CanBeConverted)
+ {
+ convertedValue = null;
+ return false;
+ }
+
+ convertedValue = conversionResult.ConvertedInstance;
+ return true;
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/ErrorReporter.cs b/src/Features/JsonPatch/src/Internal/ErrorReporter.cs
new file mode 100644
index 0000000000..76b55a6144
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ErrorReporter.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ internal static class ErrorReporter
+ {
+ public static readonly Action Default = (error) =>
+ {
+ throw new JsonPatchException(error);
+ };
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/IAdapter.cs b/src/Features/JsonPatch/src/Internal/IAdapter.cs
new file mode 100644
index 0000000000..ec28131f7d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/IAdapter.cs
@@ -0,0 +1,51 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public interface IAdapter
+ {
+ bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object nextTarget,
+ out string errorMessage);
+
+ bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage);
+
+ bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage);
+
+ bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage);
+
+ bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage);
+
+ bool TryTest(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage);
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/ListAdapter.cs b/src/Features/JsonPatch/src/Internal/ListAdapter.cs
new file mode 100644
index 0000000000..d1348fd5c6
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ListAdapter.cs
@@ -0,0 +1,349 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Microsoft.Extensions.Internal;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class ListAdapter : IAdapter
+ {
+ public bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Add, out var positionInfo, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ if (positionInfo.Type == PositionType.EndOfList)
+ {
+ list.Add(convertedValue);
+ }
+ else
+ {
+ list.Insert(positionInfo.Index, convertedValue);
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ value = null;
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Get, out var positionInfo, out errorMessage))
+ {
+ value = null;
+ return false;
+ }
+
+ if (positionInfo.Type == PositionType.EndOfList)
+ {
+ value = list[list.Count - 1];
+ }
+ else
+ {
+ value = list[positionInfo.Index];
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Remove, out var positionInfo, out errorMessage))
+ {
+ return false;
+ }
+
+ if (positionInfo.Type == PositionType.EndOfList)
+ {
+ list.RemoveAt(list.Count - 1);
+ }
+ else
+ {
+ list.RemoveAt(positionInfo.Index);
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Replace, out var positionInfo, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ if (positionInfo.Type == PositionType.EndOfList)
+ {
+ list[list.Count - 1] = convertedValue;
+ }
+ else
+ {
+ list[positionInfo.Index] = convertedValue;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryTest(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Replace, out var positionInfo, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ var currentValue = list[positionInfo.Index];
+ if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
+ {
+ errorMessage = Resources.FormatValueAtListPositionNotEqualToTestValue(currentValue, value, positionInfo.Index);
+ return false;
+ }
+ else
+ {
+ errorMessage = null;
+ return true;
+ }
+ }
+
+ public bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ var list = target as IList;
+ if (list == null)
+ {
+ value = null;
+ errorMessage = null;
+ return false;
+ }
+
+ var index = -1;
+ if (!int.TryParse(segment, out index))
+ {
+ value = null;
+ errorMessage = Resources.FormatInvalidIndexValue(segment);
+ return false;
+ }
+
+ if (index < 0 || index >= list.Count)
+ {
+ value = null;
+ errorMessage = Resources.FormatIndexOutOfBounds(segment);
+ return false;
+ }
+
+ value = list[index];
+ errorMessage = null;
+ return true;
+ }
+
+ private bool TryConvertValue(
+ object originalValue,
+ Type listTypeArgument,
+ string segment,
+ out object convertedValue,
+ out string errorMessage)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(originalValue, listTypeArgument);
+ if (!conversionResult.CanBeConverted)
+ {
+ convertedValue = null;
+ errorMessage = Resources.FormatInvalidValueForProperty(originalValue);
+ return false;
+ }
+
+ convertedValue = conversionResult.ConvertedInstance;
+ errorMessage = null;
+ return true;
+ }
+
+ private bool TryGetListTypeArgument(IList list, out Type listTypeArgument, out string errorMessage)
+ {
+ // Arrays are not supported as they have fixed size and operations like Add, Insert do not make sense
+ var listType = list.GetType();
+ if (listType.IsArray)
+ {
+ errorMessage = Resources.FormatPatchNotSupportedForArrays(listType.FullName);
+ listTypeArgument = null;
+ return false;
+ }
+ else
+ {
+ var genericList = ClosedGenericMatcher.ExtractGenericInterface(listType, typeof(IList<>));
+ if (genericList == null)
+ {
+ errorMessage = Resources.FormatPatchNotSupportedForNonGenericLists(listType.FullName);
+ listTypeArgument = null;
+ return false;
+ }
+ else
+ {
+ listTypeArgument = genericList.GenericTypeArguments[0];
+ errorMessage = null;
+ return true;
+ }
+ }
+ }
+
+ private bool TryGetPositionInfo(
+ IList list,
+ string segment,
+ OperationType operationType,
+ out PositionInfo positionInfo,
+ out string errorMessage)
+ {
+ if (segment == "-")
+ {
+ positionInfo = new PositionInfo(PositionType.EndOfList, -1);
+ errorMessage = null;
+ return true;
+ }
+
+ var position = -1;
+ if (int.TryParse(segment, out position))
+ {
+ if (position >= 0 && position < list.Count)
+ {
+ positionInfo = new PositionInfo(PositionType.Index, position);
+ errorMessage = null;
+ return true;
+ }
+ // As per JSON Patch spec, for Add operation the index value representing the number of elements is valid,
+ // where as for other operations like Remove, Replace, Move and Copy the target index MUST exist.
+ else if (position == list.Count && operationType == OperationType.Add)
+ {
+ positionInfo = new PositionInfo(PositionType.EndOfList, -1);
+ errorMessage = null;
+ return true;
+ }
+ else
+ {
+ positionInfo = new PositionInfo(PositionType.OutOfBounds, position);
+ errorMessage = Resources.FormatIndexOutOfBounds(segment);
+ return false;
+ }
+ }
+ else
+ {
+ positionInfo = new PositionInfo(PositionType.Invalid, -1);
+ errorMessage = Resources.FormatInvalidIndexValue(segment);
+ return false;
+ }
+ }
+
+ private struct PositionInfo
+ {
+ public PositionInfo(PositionType type, int index)
+ {
+ Type = type;
+ Index = index;
+ }
+
+ public PositionType Type { get; }
+ public int Index { get; }
+ }
+
+ private enum PositionType
+ {
+ Index, // valid index
+ EndOfList, // '-'
+ Invalid, // Ex: not an integer
+ OutOfBounds
+ }
+
+ private enum OperationType
+ {
+ Add,
+ Remove,
+ Get,
+ Replace
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/ObjectVisitor.cs b/src/Features/JsonPatch/src/Internal/ObjectVisitor.cs
new file mode 100644
index 0000000000..8994f0aa52
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ObjectVisitor.cs
@@ -0,0 +1,72 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class ObjectVisitor
+ {
+ private readonly IContractResolver _contractResolver;
+ private readonly ParsedPath _path;
+
+ public ObjectVisitor(ParsedPath path, IContractResolver contractResolver)
+ {
+ _path = path;
+ _contractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
+ }
+
+ public bool TryVisit(ref object target, out IAdapter adapter, out string errorMessage)
+ {
+ if (target == null)
+ {
+ adapter = null;
+ errorMessage = null;
+ return false;
+ }
+
+ adapter = SelectAdapter(target);
+
+ // Traverse until the penultimate segment to get the target object and adapter
+ for (var i = 0; i < _path.Segments.Count - 1; i++)
+ {
+ if (!adapter.TryTraverse(target, _path.Segments[i], _contractResolver, out var next, out errorMessage))
+ {
+ adapter = null;
+ return false;
+ }
+
+ target = next;
+ adapter = SelectAdapter(target);
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ private IAdapter SelectAdapter(object targetObject)
+ {
+ var jsonContract = _contractResolver.ResolveContract(targetObject.GetType());
+
+ if (targetObject is IList)
+ {
+ return new ListAdapter();
+ }
+ else if (jsonContract is JsonDictionaryContract jsonDictionaryContract)
+ {
+ var type = typeof(DictionaryAdapter<,>).MakeGenericType(jsonDictionaryContract.DictionaryKeyType, jsonDictionaryContract.DictionaryValueType);
+ return (IAdapter)Activator.CreateInstance(type);
+ }
+ else if (jsonContract is JsonDynamicContract)
+ {
+ return new DynamicObjectAdapter();
+ }
+ else
+ {
+ return new PocoAdapter();
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/ParsedPath.cs b/src/Features/JsonPatch/src/Internal/ParsedPath.cs
new file mode 100644
index 0000000000..8d0e69aa4d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ParsedPath.cs
@@ -0,0 +1,92 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public struct ParsedPath
+ {
+ private static readonly string[] Empty = null;
+
+ private readonly string[] _segments;
+
+ public ParsedPath(string path)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ _segments = ParsePath(path);
+ }
+
+ public string LastSegment
+ {
+ get
+ {
+ if (_segments == null || _segments.Length == 0)
+ {
+ return null;
+ }
+
+ return _segments[_segments.Length - 1];
+ }
+ }
+
+ public IReadOnlyList Segments => _segments ?? Empty;
+
+ private static string[] ParsePath(string path)
+ {
+ var strings = new List();
+ var sb = new StringBuilder(path.Length);
+
+ for (var i = 0; i < path.Length; i++)
+ {
+ if (path[i] == '/')
+ {
+ if (sb.Length > 0)
+ {
+ strings.Add(sb.ToString());
+ sb.Length = 0;
+ }
+ }
+ else if (path[i] == '~')
+ {
+ ++i;
+ if (i >= path.Length)
+ {
+ throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
+ }
+
+ if (path[i] == '0')
+ {
+ sb.Append('~');
+ }
+ else if (path[i] == '1')
+ {
+ sb.Append('/');
+ }
+ else
+ {
+ throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
+ }
+ }
+ else
+ {
+ sb.Append(path[i]);
+ }
+ }
+
+ if (sb.Length > 0)
+ {
+ strings.Add(sb.ToString());
+ }
+
+ return strings.ToArray();
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/PathHelpers.cs b/src/Features/JsonPatch/src/Internal/PathHelpers.cs
new file mode 100644
index 0000000000..f0afedb60e
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/PathHelpers.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using System;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ internal static class PathHelpers
+ {
+ internal static string ValidateAndNormalizePath(string path)
+ {
+ // check for most common path errors on create. This is not
+ // absolutely necessary, but it allows us to already catch mistakes
+ // on creation of the patch document rather than on execute.
+
+ if (path.Contains("//"))
+ {
+ throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
+ }
+
+ if (!path.StartsWith("/", StringComparison.Ordinal))
+ {
+ return "/" + path;
+ }
+ else
+ {
+ return path;
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/PocoAdapter.cs b/src/Features/JsonPatch/src/Internal/PocoAdapter.cs
new file mode 100644
index 0000000000..0eee0fc889
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/PocoAdapter.cs
@@ -0,0 +1,236 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Reflection;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class PocoAdapter : IAdapter
+ {
+ public bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!jsonProperty.Writable)
+ {
+ errorMessage = Resources.FormatCannotUpdateProperty(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, jsonProperty.PropertyType, out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ jsonProperty.ValueProvider.SetValue(target, convertedValue);
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ value = null;
+ return false;
+ }
+
+ if (!jsonProperty.Readable)
+ {
+ errorMessage = Resources.FormatCannotReadProperty(segment);
+ value = null;
+ return false;
+ }
+
+ value = jsonProperty.ValueProvider.GetValue(target);
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!jsonProperty.Writable)
+ {
+ errorMessage = Resources.FormatCannotUpdateProperty(segment);
+ return false;
+ }
+
+ // Setting the value to "null" will use the default value in case of value types, and
+ // null in case of reference types
+ object value = null;
+ if (jsonProperty.PropertyType.GetTypeInfo().IsValueType
+ && Nullable.GetUnderlyingType(jsonProperty.PropertyType) == null)
+ {
+ value = Activator.CreateInstance(jsonProperty.PropertyType);
+ }
+
+ jsonProperty.ValueProvider.SetValue(target, value);
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver
+ contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!jsonProperty.Writable)
+ {
+ errorMessage = Resources.FormatCannotUpdateProperty(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, jsonProperty.PropertyType, out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ jsonProperty.ValueProvider.SetValue(target, convertedValue);
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryTest(
+ object target,
+ string segment,
+ IContractResolver
+ contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!jsonProperty.Readable)
+ {
+ errorMessage = Resources.FormatCannotReadProperty(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, jsonProperty.PropertyType, out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ var currentValue = jsonProperty.ValueProvider.GetValue(target);
+ if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
+ {
+ errorMessage = Resources.FormatValueNotEqualToTestValue(currentValue, value, segment);
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ if (target == null)
+ {
+ value = null;
+ errorMessage = null;
+ return false;
+ }
+
+ if (TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ value = jsonProperty.ValueProvider.GetValue(target);
+ errorMessage = null;
+ return true;
+ }
+
+ value = null;
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ private bool TryGetJsonProperty(
+ object target,
+ IContractResolver contractResolver,
+ string segment,
+ out JsonProperty jsonProperty)
+ {
+ if (contractResolver.ResolveContract(target.GetType()) is JsonObjectContract jsonObjectContract)
+ {
+ var pocoProperty = jsonObjectContract
+ .Properties
+ .FirstOrDefault(p => string.Equals(p.PropertyName, segment, StringComparison.OrdinalIgnoreCase));
+
+ if (pocoProperty != null)
+ {
+ jsonProperty = pocoProperty;
+ return true;
+ }
+ }
+
+ jsonProperty = null;
+ return false;
+ }
+
+ private bool TryConvertValue(object value, Type propertyType, out object convertedValue)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(value, propertyType);
+ if (!conversionResult.CanBeConverted)
+ {
+ convertedValue = null;
+ return false;
+ }
+
+ convertedValue = conversionResult.ConvertedInstance;
+ return true;
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/JsonPatchDocument.cs b/src/Features/JsonPatch/src/JsonPatchDocument.cs
new file mode 100644
index 0000000000..b6539caae8
--- /dev/null
+++ b/src/Features/JsonPatch/src/JsonPatchDocument.cs
@@ -0,0 +1,256 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.JsonPatch.Adapters;
+using Microsoft.AspNetCore.JsonPatch.Converters;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using Microsoft.AspNetCore.JsonPatch.Internal;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ // Implementation details: the purpose of this type of patch document is to allow creation of such
+ // documents for cases where there's no class/DTO to work on. Typical use case: backend not built in
+ // .NET or architecture doesn't contain a shared DTO layer.
+ [JsonConverter(typeof(JsonPatchDocumentConverter))]
+ public class JsonPatchDocument : IJsonPatchDocument
+ {
+ public List Operations { get; private set; }
+
+ [JsonIgnore]
+ public IContractResolver ContractResolver { get; set; }
+
+ public JsonPatchDocument()
+ {
+ Operations = new List();
+ ContractResolver = new DefaultContractResolver();
+ }
+
+ public JsonPatchDocument(List operations, IContractResolver contractResolver)
+ {
+ if (operations == null)
+ {
+ throw new ArgumentNullException(nameof(operations));
+ }
+
+ if (contractResolver == null)
+ {
+ throw new ArgumentNullException(nameof(contractResolver));
+ }
+
+ Operations = operations;
+ ContractResolver = contractResolver;
+ }
+
+ ///
+ /// Add operation. Will result in, for example,
+ /// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Add(string path, object value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("add", PathHelpers.ValidateAndNormalizePath(path), null, value));
+ return this;
+ }
+
+ ///
+ /// Remove value at target location. Will result in, for example,
+ /// { "op": "remove", "path": "/a/b/c" }
+ ///
+ /// target location
+ ///
+ public JsonPatchDocument Remove(string path)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("remove", PathHelpers.ValidateAndNormalizePath(path), null, null));
+ return this;
+ }
+
+ ///
+ /// Replace value. Will result in, for example,
+ /// { "op": "replace", "path": "/a/b/c", "value": 42 }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Replace(string path, object value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("replace", PathHelpers.ValidateAndNormalizePath(path), null, value));
+ return this;
+ }
+
+ ///
+ /// Test value. Will result in, for example,
+ /// { "op": "test", "path": "/a/b/c", "value": 42 }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Test(string path, object value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("test", PathHelpers.ValidateAndNormalizePath(path), null, value));
+ return this;
+ }
+
+ ///
+ /// Removes value at specified location and add it to the target location. Will result in, for example:
+ /// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Move(string from, string path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("move", PathHelpers.ValidateAndNormalizePath(path), PathHelpers.ValidateAndNormalizePath(from)));
+ return this;
+ }
+
+ ///
+ /// Copy the value at specified location to the target location. Will result in, for example:
+ /// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Copy(string from, string path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("copy", PathHelpers.ValidateAndNormalizePath(path), PathHelpers.ValidateAndNormalizePath(from)));
+ return this;
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ public void ApplyTo(object objectToApplyTo)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction: null));
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ /// Action to log errors
+ public void ApplyTo(object objectToApplyTo, Action logErrorAction)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ var adapter = new ObjectAdapter(ContractResolver, logErrorAction);
+ foreach (var op in Operations)
+ {
+ try
+ {
+ op.Apply(objectToApplyTo, adapter);
+ }
+ catch (JsonPatchException jsonPatchException)
+ {
+ var errorReporter = logErrorAction ?? ErrorReporter.Default;
+ errorReporter(new JsonPatchError(objectToApplyTo, op, jsonPatchException.Message));
+
+ // As per JSON Patch spec if an operation results in error, further operations should not be executed.
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ /// IObjectAdapter instance to use when applying
+ public void ApplyTo(object objectToApplyTo, IObjectAdapter adapter)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (adapter == null)
+ {
+ throw new ArgumentNullException(nameof(adapter));
+ }
+
+ // apply each operation in order
+ foreach (var op in Operations)
+ {
+ op.Apply(objectToApplyTo, adapter);
+ }
+ }
+
+ IList IJsonPatchDocument.GetOperations()
+ {
+ var allOps = new List();
+
+ if (Operations != null)
+ {
+ foreach (var op in Operations)
+ {
+ var untypedOp = new Operation();
+
+ untypedOp.op = op.op;
+ untypedOp.value = op.value;
+ untypedOp.path = op.path;
+ untypedOp.from = op.from;
+
+ allOps.Add(untypedOp);
+ }
+ }
+
+ return allOps;
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs b/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs
new file mode 100644
index 0000000000..8ae1430185
--- /dev/null
+++ b/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs
@@ -0,0 +1,869 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq.Expressions;
+using Microsoft.AspNetCore.JsonPatch.Adapters;
+using Microsoft.AspNetCore.JsonPatch.Converters;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using Microsoft.AspNetCore.JsonPatch.Internal;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ // Implementation details: the purpose of this type of patch document is to ensure we can do type-checking
+ // when producing a JsonPatchDocument. However, we cannot send this "typed" over the wire, as that would require
+ // including type data in the JsonPatchDocument serialized as JSON (to allow for correct deserialization) - that's
+ // not according to RFC 6902, and would thus break cross-platform compatibility.
+ [JsonConverter(typeof(TypedJsonPatchDocumentConverter))]
+ public class JsonPatchDocument : IJsonPatchDocument where TModel : class
+ {
+ public List> Operations { get; private set; }
+
+ [JsonIgnore]
+ public IContractResolver ContractResolver { get; set; }
+
+ public JsonPatchDocument()
+ {
+ Operations = new List>();
+ ContractResolver = new DefaultContractResolver();
+ }
+
+ // Create from list of operations
+ public JsonPatchDocument(List> operations, IContractResolver contractResolver)
+ {
+ Operations = operations ?? throw new ArgumentNullException(nameof(operations));
+ ContractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
+ }
+
+ ///
+ /// Add operation. Will result in, for example,
+ /// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
+ ///
+ /// value type
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Add(Expression> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "add",
+ GetPath(path, null),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Add value to list at given position
+ ///
+ /// value type
+ /// target location
+ /// value
+ /// position
+ ///
+ public JsonPatchDocument Add(
+ Expression>> path,
+ TProp value,
+ int position)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "add",
+ GetPath(path, position.ToString()),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Add value to the end of the list
+ ///
+ /// value type
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Add(Expression>> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "add",
+ GetPath(path, "-"),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Remove value at target location. Will result in, for example,
+ /// { "op": "remove", "path": "/a/b/c" }
+ ///
+ /// target location
+ ///
+ public JsonPatchDocument Remove(Expression> path)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("remove", GetPath(path, null), from: null));
+
+ return this;
+ }
+
+ ///
+ /// Remove value from list at given position
+ ///
+ /// value type
+ /// target location
+ /// position
+ ///
+ public JsonPatchDocument Remove(Expression>> path, int position)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "remove",
+ GetPath(path, position.ToString()),
+ from: null));
+
+ return this;
+ }
+
+ ///
+ /// Remove value from end of list
+ ///
+ /// value type
+ /// target location
+ ///
+ public JsonPatchDocument Remove(Expression>> path)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "remove",
+ GetPath(path, "-"),
+ from: null));
+
+ return this;
+ }
+
+ ///
+ /// Replace value. Will result in, for example,
+ /// { "op": "replace", "path": "/a/b/c", "value": 42 }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Replace(Expression> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "replace",
+ GetPath(path, null),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Replace value in a list at given position
+ ///
+ /// value type
+ /// target location
+ /// value
+ /// position
+ ///
+ public JsonPatchDocument Replace(Expression>> path,
+ TProp value, int position)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "replace",
+ GetPath(path, position.ToString()),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Replace value at end of a list
+ ///
+ /// value type
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Replace(Expression>> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "replace",
+ GetPath(path, "-"),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Test value. Will result in, for example,
+ /// { "op": "test", "path": "/a/b/c", "value": 42 }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Test(Expression> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "test",
+ GetPath(path, null),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Test value in a list at given position
+ ///
+ /// value type
+ /// target location
+ /// value
+ /// position
+ ///
+ public JsonPatchDocument Test(Expression>> path,
+ TProp value, int position)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "test",
+ GetPath(path, position.ToString()),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Test value at end of a list
+ ///
+ /// value type
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Test(Expression>> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "test",
+ GetPath(path, "-"),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Removes value at specified location and add it to the target location. Will result in, for example:
+ /// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Move(
+ Expression> from,
+ Expression> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, null),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Move from a position in a list to a new location
+ ///
+ ///
+ /// source location
+ /// position
+ /// target location
+ ///
+ public JsonPatchDocument Move(
+ Expression>> from,
+ int positionFrom,
+ Expression> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, null),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Move from a property to a location in a list
+ ///
+ ///
+ /// source location
+ /// target location
+ /// position
+ ///
+ public JsonPatchDocument Move(
+ Expression> from,
+ Expression>> path,
+ int positionTo)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, positionTo.ToString()),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Move from a position in a list to another location in a list
+ ///
+ ///
+ /// source location
+ /// position (source)
+ /// target location
+ /// position (target)
+ ///
+ public JsonPatchDocument Move(
+ Expression>> from,
+ int positionFrom,
+ Expression>> path,
+ int positionTo)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, positionTo.ToString()),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Move from a position in a list to the end of another list
+ ///
+ ///
+ /// source location
+ /// position
+ /// target location
+ ///
+ public JsonPatchDocument Move(
+ Expression>> from,
+ int positionFrom,
+ Expression>> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, "-"),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Move to the end of a list
+ ///
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Move(
+ Expression> from,
+ Expression>> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, "-"),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Copy the value at specified location to the target location. Willr esult in, for example:
+ /// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Copy(
+ Expression> from,
+ Expression> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, null),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Copy from a position in a list to a new location
+ ///
+ ///
+ /// source location
+ /// position
+ /// target location
+ ///
+ public JsonPatchDocument Copy(
+ Expression>> from,
+ int positionFrom,
+ Expression> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, null),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Copy from a property to a location in a list
+ ///
+ ///
+ /// source location
+ /// target location
+ /// position
+ ///
+ public JsonPatchDocument Copy(
+ Expression> from,
+ Expression>> path,
+ int positionTo)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, positionTo.ToString()),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Copy from a position in a list to a new location in a list
+ ///
+ ///
+ /// source location
+ /// position (source)
+ /// target location
+ /// position (target)
+ ///
+ public JsonPatchDocument Copy(
+ Expression>> from,
+ int positionFrom,
+ Expression>> path,
+ int positionTo)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, positionTo.ToString()),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Copy from a position in a list to the end of another list
+ ///
+ ///
+ /// source location
+ /// position
+ /// target location
+ ///
+ public JsonPatchDocument Copy(
+ Expression>> from,
+ int positionFrom,
+ Expression>> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, "-"),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Copy to the end of a list
+ ///
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Copy(
+ Expression> from,
+ Expression>> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, "-"),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ public void ApplyTo(TModel objectToApplyTo)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction: null));
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ /// Action to log errors
+ public void ApplyTo(TModel objectToApplyTo, Action logErrorAction)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ var adapter = new ObjectAdapter(ContractResolver, logErrorAction);
+ foreach (var op in Operations)
+ {
+ try
+ {
+ op.Apply(objectToApplyTo, adapter);
+ }
+ catch (JsonPatchException jsonPatchException)
+ {
+ var errorReporter = logErrorAction ?? ErrorReporter.Default;
+ errorReporter(new JsonPatchError(objectToApplyTo, op, jsonPatchException.Message));
+
+ // As per JSON Patch spec if an operation results in error, further operations should not be executed.
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ /// IObjectAdapter instance to use when applying
+ public void ApplyTo(TModel objectToApplyTo, IObjectAdapter adapter)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (adapter == null)
+ {
+ throw new ArgumentNullException(nameof(adapter));
+ }
+
+ // apply each operation in order
+ foreach (var op in Operations)
+ {
+ op.Apply(objectToApplyTo, adapter);
+ }
+ }
+
+ IList IJsonPatchDocument.GetOperations()
+ {
+ var allOps = new List();
+
+ if (Operations != null)
+ {
+ foreach (var op in Operations)
+ {
+ var untypedOp = new Operation
+ {
+ op = op.op,
+ value = op.value,
+ path = op.path,
+ from = op.from
+ };
+
+ allOps.Add(untypedOp);
+ }
+ }
+
+ return allOps;
+ }
+
+ // Internal for testing
+ internal string GetPath(Expression> expr, string position)
+ {
+ var segments = GetPathSegments(expr.Body);
+ var path = String.Join("/", segments);
+ if (position != null)
+ {
+ path += "/" + position;
+ if (segments.Count == 0)
+ {
+ return path;
+ }
+ }
+
+ return "/" + path;
+ }
+
+ private List GetPathSegments(Expression expr)
+ {
+ var listOfSegments = new List();
+ switch (expr.NodeType)
+ {
+ case ExpressionType.ArrayIndex:
+ var binaryExpression = (BinaryExpression)expr;
+ listOfSegments.AddRange(GetPathSegments(binaryExpression.Left));
+ listOfSegments.Add(binaryExpression.Right.ToString());
+ return listOfSegments;
+
+ case ExpressionType.Call:
+ var methodCallExpression = (MethodCallExpression)expr;
+ listOfSegments.AddRange(GetPathSegments(methodCallExpression.Object));
+ listOfSegments.Add(EvaluateExpression(methodCallExpression.Arguments[0]));
+ return listOfSegments;
+
+ case ExpressionType.Convert:
+ listOfSegments.AddRange(GetPathSegments(((UnaryExpression)expr).Operand));
+ return listOfSegments;
+
+ case ExpressionType.MemberAccess:
+ var memberExpression = expr as MemberExpression;
+ listOfSegments.AddRange(GetPathSegments(memberExpression.Expression));
+ // Get property name, respecting JsonProperty attribute
+ listOfSegments.Add(GetPropertyNameFromMemberExpression(memberExpression));
+ return listOfSegments;
+
+ case ExpressionType.Parameter:
+ // Fits "x => x" (the whole document which is "" as JSON pointer)
+ return listOfSegments;
+
+ default:
+ throw new InvalidOperationException(Resources.FormatExpressionTypeNotSupported(expr));
+ }
+ }
+
+ private string GetPropertyNameFromMemberExpression(MemberExpression memberExpression)
+ {
+ var jsonObjectContract = ContractResolver.ResolveContract(memberExpression.Expression.Type) as JsonObjectContract;
+ if (jsonObjectContract != null)
+ {
+ return jsonObjectContract.Properties
+ .First(jsonProperty => jsonProperty.UnderlyingName == memberExpression.Member.Name)
+ .PropertyName;
+ }
+
+ return null;
+ }
+
+ private static bool ContinueWithSubPath(ExpressionType expressionType)
+ {
+ return (expressionType == ExpressionType.ArrayIndex
+ || expressionType == ExpressionType.Call
+ || expressionType == ExpressionType.Convert
+ || expressionType == ExpressionType.MemberAccess);
+
+ }
+
+ // Evaluates the value of the key or index which may be an int or a string,
+ // or some other expression type.
+ // The expression is converted to a delegate and the result of executing the delegate is returned as a string.
+ private static string EvaluateExpression(Expression expression)
+ {
+ var converted = Expression.Convert(expression, typeof(object));
+ var fakeParameter = Expression.Parameter(typeof(object), null);
+ var lambda = Expression.Lambda>(converted, fakeParameter);
+ var func = lambda.Compile();
+
+ return Convert.ToString(func(null), CultureInfo.InvariantCulture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/JsonPatchError.cs b/src/Features/JsonPatch/src/JsonPatchError.cs
new file mode 100644
index 0000000000..a49af7a4e2
--- /dev/null
+++ b/src/Features/JsonPatch/src/JsonPatchError.cs
@@ -0,0 +1,50 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ ///
+ /// Captures error message and the related entity and the operation that caused it.
+ ///
+ public class JsonPatchError
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The object that is affected by the error.
+ /// The that caused the error.
+ /// The error message.
+ public JsonPatchError(
+ object affectedObject,
+ Operation operation,
+ string errorMessage)
+ {
+ if (errorMessage == null)
+ {
+ throw new ArgumentNullException(nameof(errorMessage));
+ }
+
+ AffectedObject = affectedObject;
+ Operation = operation;
+ ErrorMessage = errorMessage;
+ }
+
+ ///
+ /// Gets the object that is affected by the error.
+ ///
+ public object AffectedObject { get; }
+
+ ///
+ /// Gets the that caused the error.
+ ///
+ public Operation Operation { get; }
+
+ ///
+ /// Gets the error message.
+ ///
+ public string ErrorMessage { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj
new file mode 100644
index 0000000000..3708e92927
--- /dev/null
+++ b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj
@@ -0,0 +1,17 @@
+
+
+
+ ASP.NET Core support for JSON PATCH.
+ netstandard2.0
+ $(NoWarn);CS1591
+ true
+ aspnetcore;json;jsonpatch
+
+
+
+
+
+
+
+
+
diff --git a/src/Features/JsonPatch/src/Operations/Operation.cs b/src/Features/JsonPatch/src/Operations/Operation.cs
new file mode 100644
index 0000000000..690ade4776
--- /dev/null
+++ b/src/Features/JsonPatch/src/Operations/Operation.cs
@@ -0,0 +1,82 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Adapters;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNetCore.JsonPatch.Operations
+{
+ public class Operation : OperationBase
+ {
+ [JsonProperty("value")]
+ public object value { get; set; }
+
+ public Operation()
+ {
+
+ }
+
+ public Operation(string op, string path, string from, object value)
+ : base(op, path, from)
+ {
+ this.value = value;
+ }
+
+ public Operation(string op, string path, string from)
+ : base(op, path, from)
+ {
+ }
+
+ public void Apply(object objectToApplyTo, IObjectAdapter adapter)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (adapter == null)
+ {
+ throw new ArgumentNullException(nameof(adapter));
+ }
+
+ switch (OperationType)
+ {
+ case OperationType.Add:
+ adapter.Add(this, objectToApplyTo);
+ break;
+ case OperationType.Remove:
+ adapter.Remove(this, objectToApplyTo);
+ break;
+ case OperationType.Replace:
+ adapter.Replace(this, objectToApplyTo);
+ break;
+ case OperationType.Move:
+ adapter.Move(this, objectToApplyTo);
+ break;
+ case OperationType.Copy:
+ adapter.Copy(this, objectToApplyTo);
+ break;
+ case OperationType.Test:
+ if (adapter is IObjectAdapterWithTest adapterWithTest)
+ {
+ adapterWithTest.Test(this, objectToApplyTo);
+ break;
+ }
+ else
+ {
+ throw new NotSupportedException(Resources.TestOperationNotSupported);
+ }
+ default:
+ break;
+ }
+ }
+
+ public bool ShouldSerializevalue()
+ {
+ return (OperationType == OperationType.Add
+ || OperationType == OperationType.Replace
+ || OperationType == OperationType.Test);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Operations/OperationBase.cs b/src/Features/JsonPatch/src/Operations/OperationBase.cs
new file mode 100644
index 0000000000..e629e2308d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Operations/OperationBase.cs
@@ -0,0 +1,76 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNetCore.JsonPatch.Operations
+{
+ public class OperationBase
+ {
+ private string _op;
+ private OperationType _operationType;
+
+ [JsonIgnore]
+ public OperationType OperationType
+ {
+ get
+ {
+ return _operationType;
+ }
+ }
+
+ [JsonProperty("path")]
+ public string path { get; set; }
+
+ [JsonProperty("op")]
+ public string op
+ {
+ get
+ {
+ return _op;
+ }
+ set
+ {
+ OperationType result;
+ if (!Enum.TryParse(value, ignoreCase: true, result: out result))
+ {
+ result = OperationType.Invalid;
+ }
+ _operationType = result;
+ _op = value;
+ }
+ }
+
+ [JsonProperty("from")]
+ public string from { get; set; }
+
+ public OperationBase()
+ {
+
+ }
+
+ public OperationBase(string op, string path, string from)
+ {
+ if (op == null)
+ {
+ throw new ArgumentNullException(nameof(op));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ this.op = op;
+ this.path = path;
+ this.from = from;
+ }
+
+ public bool ShouldSerializefrom()
+ {
+ return (OperationType == OperationType.Move
+ || OperationType == OperationType.Copy);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Operations/OperationOfT.cs b/src/Features/JsonPatch/src/Operations/OperationOfT.cs
new file mode 100644
index 0000000000..bd13528775
--- /dev/null
+++ b/src/Features/JsonPatch/src/Operations/OperationOfT.cs
@@ -0,0 +1,94 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Adapters;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+
+namespace Microsoft.AspNetCore.JsonPatch.Operations
+{
+ public class Operation : Operation where TModel : class
+ {
+ public Operation()
+ {
+
+ }
+
+ public Operation(string op, string path, string from, object value)
+ : base(op, path, from)
+ {
+ if (op == null)
+ {
+ throw new ArgumentNullException(nameof(op));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ this.value = value;
+ }
+
+ public Operation(string op, string path, string from)
+ : base(op, path, from)
+ {
+ if (op == null)
+ {
+ throw new ArgumentNullException(nameof(op));
+ }
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ }
+
+ public void Apply(TModel objectToApplyTo, IObjectAdapter adapter)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (adapter == null)
+ {
+ throw new ArgumentNullException(nameof(adapter));
+ }
+
+ switch (OperationType)
+ {
+ case OperationType.Add:
+ adapter.Add(this, objectToApplyTo);
+ break;
+ case OperationType.Remove:
+ adapter.Remove(this, objectToApplyTo);
+ break;
+ case OperationType.Replace:
+ adapter.Replace(this, objectToApplyTo);
+ break;
+ case OperationType.Move:
+ adapter.Move(this, objectToApplyTo);
+ break;
+ case OperationType.Copy:
+ adapter.Copy(this, objectToApplyTo);
+ break;
+ case OperationType.Test:
+ if (adapter is IObjectAdapterWithTest adapterWithTest)
+ {
+ adapterWithTest.Test(this, objectToApplyTo);
+ break;
+ }
+ else
+ {
+ throw new JsonPatchException(new JsonPatchError(objectToApplyTo, this, Resources.TestOperationNotSupported));
+ }
+ case OperationType.Invalid:
+ throw new JsonPatchException(
+ Resources.FormatInvalidJsonPatchOperation(op), innerException: null);
+ default:
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Operations/OperationType.cs b/src/Features/JsonPatch/src/Operations/OperationType.cs
new file mode 100644
index 0000000000..725646df3a
--- /dev/null
+++ b/src/Features/JsonPatch/src/Operations/OperationType.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.JsonPatch.Operations
+{
+ public enum OperationType
+ {
+ Add,
+ Remove,
+ Replace,
+ Move,
+ Copy,
+ Test,
+ Invalid
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Properties/AssemblyInfo.cs b/src/Features/JsonPatch/src/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..11fa956b64
--- /dev/null
+++ b/src/Features/JsonPatch/src/Properties/AssemblyInfo.cs
@@ -0,0 +1,7 @@
+
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.JsonPatch.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Features/JsonPatch/src/Properties/Resources.Designer.cs b/src/Features/JsonPatch/src/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..c314465238
--- /dev/null
+++ b/src/Features/JsonPatch/src/Properties/Resources.Designer.cs
@@ -0,0 +1,338 @@
+//
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.JsonPatch.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// The property at '{0}' could not be copied.
+ ///
+ internal static string CannotCopyProperty
+ {
+ get => GetString("CannotCopyProperty");
+ }
+
+ ///
+ /// The property at '{0}' could not be copied.
+ ///
+ internal static string FormatCannotCopyProperty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotCopyProperty"), p0);
+
+ ///
+ /// The type of the property at path '{0}' could not be determined.
+ ///
+ internal static string CannotDeterminePropertyType
+ {
+ get => GetString("CannotDeterminePropertyType");
+ }
+
+ ///
+ /// The type of the property at path '{0}' could not be determined.
+ ///
+ internal static string FormatCannotDeterminePropertyType(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotDeterminePropertyType"), p0);
+
+ ///
+ /// The '{0}' operation at path '{1}' could not be performed.
+ ///
+ internal static string CannotPerformOperation
+ {
+ get => GetString("CannotPerformOperation");
+ }
+
+ ///
+ /// The '{0}' operation at path '{1}' could not be performed.
+ ///
+ internal static string FormatCannotPerformOperation(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotPerformOperation"), p0, p1);
+
+ ///
+ /// The property at '{0}' could not be read.
+ ///
+ internal static string CannotReadProperty
+ {
+ get => GetString("CannotReadProperty");
+ }
+
+ ///
+ /// The property at '{0}' could not be read.
+ ///
+ internal static string FormatCannotReadProperty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotReadProperty"), p0);
+
+ ///
+ /// The property at path '{0}' could not be updated.
+ ///
+ internal static string CannotUpdateProperty
+ {
+ get => GetString("CannotUpdateProperty");
+ }
+
+ ///
+ /// The property at path '{0}' could not be updated.
+ ///
+ internal static string FormatCannotUpdateProperty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotUpdateProperty"), p0);
+
+ ///
+ /// The expression '{0}' is not supported. Supported expressions include member access and indexer expressions.
+ ///
+ internal static string ExpressionTypeNotSupported
+ {
+ get => GetString("ExpressionTypeNotSupported");
+ }
+
+ ///
+ /// The expression '{0}' is not supported. Supported expressions include member access and indexer expressions.
+ ///
+ internal static string FormatExpressionTypeNotSupported(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ExpressionTypeNotSupported"), p0);
+
+ ///
+ /// The index value provided by path segment '{0}' is out of bounds of the array size.
+ ///
+ internal static string IndexOutOfBounds
+ {
+ get => GetString("IndexOutOfBounds");
+ }
+
+ ///
+ /// The index value provided by path segment '{0}' is out of bounds of the array size.
+ ///
+ internal static string FormatIndexOutOfBounds(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("IndexOutOfBounds"), p0);
+
+ ///
+ /// The path segment '{0}' is invalid for an array index.
+ ///
+ internal static string InvalidIndexValue
+ {
+ get => GetString("InvalidIndexValue");
+ }
+
+ ///
+ /// The path segment '{0}' is invalid for an array index.
+ ///
+ internal static string FormatInvalidIndexValue(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidIndexValue"), p0);
+
+ ///
+ /// The JSON patch document was malformed and could not be parsed.
+ ///
+ internal static string InvalidJsonPatchDocument
+ {
+ get => GetString("InvalidJsonPatchDocument");
+ }
+
+ ///
+ /// The JSON patch document was malformed and could not be parsed.
+ ///
+ internal static string FormatInvalidJsonPatchDocument()
+ => GetString("InvalidJsonPatchDocument");
+
+ ///
+ /// Invalid JsonPatch operation '{0}'.
+ ///
+ internal static string InvalidJsonPatchOperation
+ {
+ get => GetString("InvalidJsonPatchOperation");
+ }
+
+ ///
+ /// Invalid JsonPatch operation '{0}'.
+ ///
+ internal static string FormatInvalidJsonPatchOperation(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidJsonPatchOperation"), p0);
+
+ ///
+ /// The provided path segment '{0}' cannot be converted to the target type.
+ ///
+ internal static string InvalidPathSegment
+ {
+ get => GetString("InvalidPathSegment");
+ }
+
+ ///
+ /// The provided path segment '{0}' cannot be converted to the target type.
+ ///
+ internal static string FormatInvalidPathSegment(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidPathSegment"), p0);
+
+ ///
+ /// The provided string '{0}' is an invalid path.
+ ///
+ internal static string InvalidValueForPath
+ {
+ get => GetString("InvalidValueForPath");
+ }
+
+ ///
+ /// The provided string '{0}' is an invalid path.
+ ///
+ internal static string FormatInvalidValueForPath(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidValueForPath"), p0);
+
+ ///
+ /// The value '{0}' is invalid for target location.
+ ///
+ internal static string InvalidValueForProperty
+ {
+ get => GetString("InvalidValueForProperty");
+ }
+
+ ///
+ /// The value '{0}' is invalid for target location.
+ ///
+ internal static string FormatInvalidValueForProperty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidValueForProperty"), p0);
+
+ ///
+ /// '{0}' must be of type '{1}'.
+ ///
+ internal static string ParameterMustMatchType
+ {
+ get => GetString("ParameterMustMatchType");
+ }
+
+ ///
+ /// '{0}' must be of type '{1}'.
+ ///
+ internal static string FormatParameterMustMatchType(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ParameterMustMatchType"), p0, p1);
+
+ ///
+ /// The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.
+ ///
+ internal static string PatchNotSupportedForArrays
+ {
+ get => GetString("PatchNotSupportedForArrays");
+ }
+
+ ///
+ /// The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.
+ ///
+ internal static string FormatPatchNotSupportedForArrays(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("PatchNotSupportedForArrays"), p0);
+
+ ///
+ /// The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.
+ ///
+ internal static string PatchNotSupportedForNonGenericLists
+ {
+ get => GetString("PatchNotSupportedForNonGenericLists");
+ }
+
+ ///
+ /// The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.
+ ///
+ internal static string FormatPatchNotSupportedForNonGenericLists(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("PatchNotSupportedForNonGenericLists"), p0);
+
+ ///
+ /// The target location specified by path segment '{0}' was not found.
+ ///
+ internal static string TargetLocationAtPathSegmentNotFound
+ {
+ get => GetString("TargetLocationAtPathSegmentNotFound");
+ }
+
+ ///
+ /// The target location specified by path segment '{0}' was not found.
+ ///
+ internal static string FormatTargetLocationAtPathSegmentNotFound(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("TargetLocationAtPathSegmentNotFound"), p0);
+
+ ///
+ /// For operation '{0}', the target location specified by path '{1}' was not found.
+ ///
+ internal static string TargetLocationNotFound
+ {
+ get => GetString("TargetLocationNotFound");
+ }
+
+ ///
+ /// For operation '{0}', the target location specified by path '{1}' was not found.
+ ///
+ internal static string FormatTargetLocationNotFound(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("TargetLocationNotFound"), p0, p1);
+
+ ///
+ /// The test operation is not supported.
+ ///
+ internal static string TestOperationNotSupported
+ {
+ get => GetString("TestOperationNotSupported");
+ }
+
+ ///
+ /// The test operation is not supported.
+ ///
+ internal static string FormatTestOperationNotSupported()
+ => GetString("TestOperationNotSupported");
+
+ ///
+ /// The current value '{0}' at position '{2}' is not equal to the test value '{1}'.
+ ///
+ internal static string ValueAtListPositionNotEqualToTestValue
+ {
+ get => GetString("ValueAtListPositionNotEqualToTestValue");
+ }
+
+ ///
+ /// The current value '{0}' at position '{2}' is not equal to the test value '{1}'.
+ ///
+ internal static string FormatValueAtListPositionNotEqualToTestValue(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ValueAtListPositionNotEqualToTestValue"), p0, p1, p2);
+
+ ///
+ /// The value at '{0}' cannot be null or empty to perform the test operation.
+ ///
+ internal static string ValueForTargetSegmentCannotBeNullOrEmpty
+ {
+ get => GetString("ValueForTargetSegmentCannotBeNullOrEmpty");
+ }
+
+ ///
+ /// The value at '{0}' cannot be null or empty to perform the test operation.
+ ///
+ internal static string FormatValueForTargetSegmentCannotBeNullOrEmpty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ValueForTargetSegmentCannotBeNullOrEmpty"), p0);
+
+ ///
+ /// The current value '{0}' at path '{2}' is not equal to the test value '{1}'.
+ ///
+ internal static string ValueNotEqualToTestValue
+ {
+ get => GetString("ValueNotEqualToTestValue");
+ }
+
+ ///
+ /// The current value '{0}' at path '{2}' is not equal to the test value '{1}'.
+ ///
+ internal static string FormatValueNotEqualToTestValue(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ValueNotEqualToTestValue"), p0, p1, p2);
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Resources.resx b/src/Features/JsonPatch/src/Resources.resx
new file mode 100644
index 0000000000..87cc399c62
--- /dev/null
+++ b/src/Features/JsonPatch/src/Resources.resx
@@ -0,0 +1,186 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The property at '{0}' could not be copied.
+
+
+ The type of the property at path '{0}' could not be determined.
+
+
+ The '{0}' operation at path '{1}' could not be performed.
+
+
+ The property at '{0}' could not be read.
+
+
+ The property at path '{0}' could not be updated.
+
+
+ The expression '{0}' is not supported. Supported expressions include member access and indexer expressions.
+
+
+ The index value provided by path segment '{0}' is out of bounds of the array size.
+
+
+ The path segment '{0}' is invalid for an array index.
+
+
+ The JSON patch document was malformed and could not be parsed.
+
+
+ Invalid JsonPatch operation '{0}'.
+
+
+ The provided path segment '{0}' cannot be converted to the target type.
+
+
+ The provided string '{0}' is an invalid path.
+
+
+ The value '{0}' is invalid for target location.
+
+
+ '{0}' must be of type '{1}'.
+
+
+ The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.
+
+
+ The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.
+
+
+ The target location specified by path segment '{0}' was not found.
+
+
+ For operation '{0}', the target location specified by path '{1}' was not found.
+
+
+ The test operation is not supported.
+
+
+ The current value '{0}' at position '{2}' is not equal to the test value '{1}'.
+
+
+ The value at '{0}' cannot be null or empty to perform the test operation.
+
+
+ The current value '{0}' at path '{2}' is not equal to the test value '{1}'.
+
+
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/baseline.netcore.json b/src/Features/JsonPatch/src/baseline.netcore.json
new file mode 100644
index 0000000000..3d90a8f017
--- /dev/null
+++ b/src/Features/JsonPatch/src/baseline.netcore.json
@@ -0,0 +1,1985 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.JsonPatch, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.JsonPatchProperty",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Property",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.JsonProperty",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Property",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Newtonsoft.Json.Serialization.JsonProperty"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Parent",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Parent",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "property",
+ "Type": "Newtonsoft.Json.Serialization.JsonProperty"
+ },
+ {
+ "Name": "parent",
+ "Type": "System.Object"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_ContractResolver",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.IContractResolver",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_ContractResolver",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetOperations",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IList",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Operations",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.List",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ContractResolver",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.IContractResolver",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_ContractResolver",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "logErrorAction",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "adapter",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "operations",
+ "Type": "System.Collections.Generic.List"
+ },
+ {
+ "Name": "contractResolver",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Operations",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.List>",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ContractResolver",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.IContractResolver",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_ContractResolver",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ },
+ {
+ "Name": "position",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "position",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ },
+ {
+ "Name": "position",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ },
+ {
+ "Name": "position",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionTo",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionTo",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionTo",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionTo",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "T0"
+ },
+ {
+ "Name": "logErrorAction",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "T0"
+ },
+ {
+ "Name": "adapter",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "operations",
+ "Type": "System.Collections.Generic.List>"
+ },
+ {
+ "Name": "contractResolver",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": [
+ {
+ "ParameterName": "TModel",
+ "ParameterPosition": 0,
+ "Class": true,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.JsonPatchError",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_AffectedObject",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Operation",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ErrorMessage",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "affectedObject",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "errorMessage",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.JsonPatch.Operations.OperationBase",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_value",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_value",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Apply",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "adapter",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ShouldSerializevalue",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Operations.OperationBase",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_OperationType",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.Operations.OperationType",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_path",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_path",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_op",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_op",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_from",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_from",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ShouldSerializefrom",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Apply",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "T0"
+ },
+ {
+ "Name": "adapter",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": [
+ {
+ "ParameterName": "TModel",
+ "ParameterPosition": 0,
+ "Class": true,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Operations.OperationType",
+ "Visibility": "Public",
+ "Kind": "Enumeration",
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Field",
+ "Name": "Add",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "0"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Remove",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "1"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Replace",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "2"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Move",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "3"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Copy",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "4"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Test",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "5"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Invalid",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "6"
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Helpers.GetValueResult",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_PropertyValue",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_HasError",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "propertyValue",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "hasError",
+ "Type": "System.Boolean"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Exceptions.JsonPatchException",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "System.Exception",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_FailedOperation",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_AffectedObject",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "jsonPatchError",
+ "Type": "Microsoft.AspNetCore.JsonPatch.JsonPatchError"
+ },
+ {
+ "Name": "innerException",
+ "Type": "System.Exception"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "jsonPatchError",
+ "Type": "Microsoft.AspNetCore.JsonPatch.JsonPatchError"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "message",
+ "Type": "System.String"
+ },
+ {
+ "Name": "innerException",
+ "Type": "System.Exception"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Converters.JsonPatchDocumentConverter",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Newtonsoft.Json.JsonConverter",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "CanConvert",
+ "Parameters": [
+ {
+ "Name": "objectType",
+ "Type": "System.Type"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ReadJson",
+ "Parameters": [
+ {
+ "Name": "reader",
+ "Type": "Newtonsoft.Json.JsonReader"
+ },
+ {
+ "Name": "objectType",
+ "Type": "System.Type"
+ },
+ {
+ "Name": "existingValue",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "serializer",
+ "Type": "Newtonsoft.Json.JsonSerializer"
+ }
+ ],
+ "ReturnType": "System.Object",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "WriteJson",
+ "Parameters": [
+ {
+ "Name": "writer",
+ "Type": "Newtonsoft.Json.JsonWriter"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "serializer",
+ "Type": "Newtonsoft.Json.JsonSerializer"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Converters.TypedJsonPatchDocumentConverter",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.JsonPatch.Converters.JsonPatchDocumentConverter",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "ReadJson",
+ "Parameters": [
+ {
+ "Name": "reader",
+ "Type": "Newtonsoft.Json.JsonReader"
+ },
+ {
+ "Name": "objectType",
+ "Type": "System.Type"
+ },
+ {
+ "Name": "existingValue",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "serializer",
+ "Type": "Newtonsoft.Json.JsonSerializer"
+ }
+ ],
+ "ReturnType": "System.Object",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapterWithTest",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Adapters.ObjectAdapter",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapterWithTest"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ContractResolver",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.IContractResolver",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_LogErrorAction",
+ "Parameters": [],
+ "ReturnType": "System.Action",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapterWithTest",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "contractResolver",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ },
+ {
+ "Name": "logErrorAction",
+ "Type": "System.Action"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/test/CustomNamingStrategyTests.cs b/src/Features/JsonPatch/test/CustomNamingStrategyTests.cs
new file mode 100644
index 0000000000..ebc45874d9
--- /dev/null
+++ b/src/Features/JsonPatch/test/CustomNamingStrategyTests.cs
@@ -0,0 +1,153 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Dynamic;
+using Newtonsoft.Json.Serialization;
+using Xunit;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ public class CustomNamingStrategyTests
+ {
+ [Fact]
+ public void AddProperty_ToDynamicTestObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ dynamic targetObject = new DynamicTestObject();
+ targetObject.Test = 1;
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Add("NewInt", 1);
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+
+ // Assert
+ Assert.Equal(1, targetObject.customNewInt);
+ Assert.Equal(1, targetObject.Test);
+ }
+
+ [Fact]
+ public void CopyPropertyValue_ToDynamicTestObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ dynamic targetObject = new DynamicTestObject();
+ targetObject.customStringProperty = "A";
+ targetObject.customAnotherStringProperty = "B";
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Copy("StringProperty", "AnotherStringProperty");
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+
+ // Assert
+ Assert.Equal("A", targetObject.customAnotherStringProperty);
+ }
+
+ [Fact]
+ public void MovePropertyValue_ForExpandoObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ dynamic targetObject = new ExpandoObject();
+ targetObject.customStringProperty = "A";
+ targetObject.customAnotherStringProperty = "B";
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Move("StringProperty", "AnotherStringProperty");
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+ var cont = targetObject as IDictionary;
+ cont.TryGetValue("customStringProperty", out var valueFromDictionary);
+
+ // Assert
+ Assert.Equal("A", targetObject.customAnotherStringProperty);
+ Assert.Null(valueFromDictionary);
+ }
+
+ [Fact]
+ public void RemoveProperty_FromDictionaryObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ var targetObject = new Dictionary()
+ {
+ { "customTest", 1},
+ };
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Remove("Test");
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+ var cont = targetObject as IDictionary;
+ cont.TryGetValue("customTest", out var valueFromDictionary);
+
+ // Assert
+ Assert.Equal(0, valueFromDictionary);
+ }
+
+ [Fact]
+ public void ReplacePropertyValue_ForExpandoObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ dynamic targetObject = new ExpandoObject();
+ targetObject.customTest = 1;
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Replace("Test", 2);
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+
+ // Assert
+ Assert.Equal(2, targetObject.customTest);
+ }
+
+ private class TestNamingStrategy : NamingStrategy
+ {
+ public new bool ProcessDictionaryKeys => true;
+
+ public override string GetDictionaryKey(string key)
+ {
+ return "custom" + key;
+ }
+
+ protected override string ResolvePropertyName(string name)
+ {
+ return name;
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/test/IntegrationTests/AnonymousObjectIntegrationTest.cs b/src/Features/JsonPatch/test/IntegrationTests/AnonymousObjectIntegrationTest.cs
new file mode 100644
index 0000000000..4f290aae2f
--- /dev/null
+++ b/src/Features/JsonPatch/test/IntegrationTests/AnonymousObjectIntegrationTest.cs
@@ -0,0 +1,190 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests
+{
+ public class AnonymousObjectIntegrationTest
+ {
+ [Fact]
+ public void AddNewProperty_ShouldFail()
+ {
+ // Arrange
+ var targetObject = new { };
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Add("NewProperty", 4);
+
+ // Act
+ var exception = Assert.Throws(() =>
+ {
+ patchDocument.ApplyTo(targetObject);
+ });
+
+ // Assert
+ Assert.Equal("The target location specified by path segment 'NewProperty' was not found.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void AddNewProperty_ToNestedAnonymousObject_ShouldFail()
+ {
+ // Arrange
+ dynamic targetObject = new
+ {
+ Test = 1,
+ nested = new { }
+ };
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Add("Nested/NewInt", 1);
+
+ // Act
+ var exception = Assert.Throws(() =>
+ {
+ patchDocument.ApplyTo(targetObject);
+ });
+
+ // Assert
+ Assert.Equal("The target location specified by path segment 'NewInt' was not found.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void AddDoesNotReplace()
+ {
+ // Arrange
+ var targetObject = new
+ {
+ StringProperty = "A"
+ };
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Add("StringProperty", "B");
+
+ // Act
+ var exception = Assert.Throws(() =>
+ {
+ patchDocument.ApplyTo(targetObject);
+ });
+
+ // Assert
+ Assert.Equal("The property at path 'StringProperty' could not be updated.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void RemoveProperty_ShouldFail()
+ {
+ // Arrange
+ dynamic targetObject = new
+ {
+ Test = 1
+ };
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Remove("Test");
+
+ // Act
+ var exception = Assert.Throws(() =>
+ {
+ patchDocument.ApplyTo(targetObject);
+ });
+
+ // Assert
+ Assert.Equal("The property at path 'Test' could not be updated.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void ReplaceProperty_ShouldFail()
+ {
+ // Arrange
+ var targetObject = new
+ {
+ StringProperty = "A",
+ AnotherStringProperty = "B"
+ };
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Replace("StringProperty", "AnotherStringProperty");
+
+ // Act
+ var exception = Assert.Throws(() =>
+ {
+ patchDocument.ApplyTo(targetObject);
+ });
+
+ // Assert
+ Assert.Equal("The property at path 'StringProperty' could not be updated.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void MoveProperty_ShouldFail()
+ {
+ // Arrange
+ var targetObject = new
+ {
+ StringProperty = "A",
+ AnotherStringProperty = "B"
+ };
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Move("StringProperty", "AnotherStringProperty");
+
+ // Act
+ var exception = Assert.Throws(() =>
+ {
+ patchDocument.ApplyTo(targetObject);
+ });
+
+ // Assert
+ Assert.Equal("The property at path 'StringProperty' could not be updated.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void TestStringProperty_IsSucessful()
+ {
+ // Arrange
+ var targetObject = new
+ {
+ StringProperty = "A",
+ AnotherStringProperty = "B"
+ };
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Test("StringProperty", "A");
+
+ // Act & Assert
+ patchDocument.ApplyTo(targetObject);
+ }
+
+ [Fact]
+ public void TestStringProperty_Fails()
+ {
+ // Arrange
+ var targetObject = new
+ {
+ StringProperty = "A",
+ AnotherStringProperty = "B"
+ };
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Test("StringProperty", "B");
+
+ // Act
+ var exception = Assert.Throws(() =>
+ {
+ patchDocument.ApplyTo(targetObject);
+ });
+
+ // Assert
+ Assert.Equal("The current value 'A' at path 'StringProperty' is not equal to the test value 'B'.",
+ exception.Message);
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/test/IntegrationTests/DictionaryIntegrationTest.cs b/src/Features/JsonPatch/test/IntegrationTests/DictionaryIntegrationTest.cs
new file mode 100644
index 0000000000..da990e3e8c
--- /dev/null
+++ b/src/Features/JsonPatch/test/IntegrationTests/DictionaryIntegrationTest.cs
@@ -0,0 +1,319 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests
+{
+ public class DictionaryTest
+ {
+ [Fact]
+ public void TestIntegerValue_IsSuccessful()
+ {
+ // Arrange
+ var model = new IntDictionary();
+ model.DictionaryOfStringToInteger["one"] = 1;
+ model.DictionaryOfStringToInteger["two"] = 2;
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Test("/DictionaryOfStringToInteger/two", 2);
+
+ // Act & Assert
+ patchDocument.ApplyTo(model);
+ }
+
+ [Fact]
+ public void AddIntegerValue_Succeeds()
+ {
+ // Arrange
+ var model = new IntDictionary();
+ model.DictionaryOfStringToInteger["one"] = 1;
+ model.DictionaryOfStringToInteger["two"] = 2;
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Add("/DictionaryOfStringToInteger/three", 3);
+
+ // Act
+ patchDocument.ApplyTo(model);
+
+ // Assert
+ Assert.Equal(3, model.DictionaryOfStringToInteger.Count);
+ Assert.Equal(1, model.DictionaryOfStringToInteger["one"]);
+ Assert.Equal(2, model.DictionaryOfStringToInteger["two"]);
+ Assert.Equal(3, model.DictionaryOfStringToInteger["three"]);
+ }
+
+ [Fact]
+ public void RemoveIntegerValue_Succeeds()
+ {
+ // Arrange
+ var model = new IntDictionary();
+ model.DictionaryOfStringToInteger["one"] = 1;
+ model.DictionaryOfStringToInteger["two"] = 2;
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Remove("/DictionaryOfStringToInteger/two");
+
+ // Act
+ patchDocument.ApplyTo(model);
+
+ // Assert
+ Assert.Equal(1, model.DictionaryOfStringToInteger.Count);
+ Assert.Equal(1, model.DictionaryOfStringToInteger["one"]);
+ }
+
+ [Fact]
+ public void MoveIntegerValue_Succeeds()
+ {
+ // Arrange
+ var model = new IntDictionary();
+ model.DictionaryOfStringToInteger["one"] = 1;
+ model.DictionaryOfStringToInteger["two"] = 2;
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Move("/DictionaryOfStringToInteger/one", "/DictionaryOfStringToInteger/two");
+
+ // Act
+ patchDocument.ApplyTo(model);
+
+ // Assert
+ Assert.Equal(1, model.DictionaryOfStringToInteger.Count);
+ Assert.Equal(1, model.DictionaryOfStringToInteger["two"]);
+ }
+
+ [Fact]
+ public void ReplaceIntegerValue_Succeeds()
+ {
+ // Arrange
+ var model = new IntDictionary();
+ model.DictionaryOfStringToInteger["one"] = 1;
+ model.DictionaryOfStringToInteger["two"] = 2;
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Replace("/DictionaryOfStringToInteger/two", 20);
+
+ // Act
+ patchDocument.ApplyTo(model);
+
+ // Assert
+ Assert.Equal(2, model.DictionaryOfStringToInteger.Count);
+ Assert.Equal(1, model.DictionaryOfStringToInteger["one"]);
+ Assert.Equal(20, model.DictionaryOfStringToInteger["two"]);
+ }
+
+ [Fact]
+ public void CopyIntegerValue_Succeeds()
+ {
+ // Arrange
+ var model = new IntDictionary();
+ model.DictionaryOfStringToInteger["one"] = 1;
+ model.DictionaryOfStringToInteger["two"] = 2;
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Copy("/DictionaryOfStringToInteger/one", "/DictionaryOfStringToInteger/two");
+
+ // Act
+ patchDocument.ApplyTo(model);
+
+ // Assert
+ Assert.Equal(2, model.DictionaryOfStringToInteger.Count);
+ Assert.Equal(1, model.DictionaryOfStringToInteger["one"]);
+ Assert.Equal(1, model.DictionaryOfStringToInteger["two"]);
+ }
+
+ private class Customer
+ {
+ public string Name { get; set; }
+ public Address Address { get; set; }
+ }
+
+ private class Address
+ {
+ public string City { get; set; }
+ }
+
+ private class IntDictionary
+ {
+ public IDictionary DictionaryOfStringToInteger { get; } = new Dictionary();
+ }
+
+ private class CustomerDictionary
+ {
+ public IDictionary