diff --git a/src/Microsoft.AspNet.JsonPatch/Adapters/ObjectAdapter.cs b/src/Microsoft.AspNet.JsonPatch/Adapters/ObjectAdapter.cs
index cfaaebb96c..d9523d3a0b 100644
--- a/src/Microsoft.AspNet.JsonPatch/Adapters/ObjectAdapter.cs
+++ b/src/Microsoft.AspNet.JsonPatch/Adapters/ObjectAdapter.cs
@@ -359,73 +359,30 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
/// Object to apply the operation to.
public void Move([NotNull] Operation operation, [NotNull] object objectToApplyTo)
{
- // get value at from location
- object valueAtFromLocation = null;
- var positionAsInteger = -1;
- var actualFromProperty = operation.from;
+ var valueAtFromLocationResult = GetValueAtLocation(operation.from, objectToApplyTo, operation);
- positionAsInteger = GetNumericEnd(operation.from);
-
- if (positionAsInteger > -1)
- {
- actualFromProperty = operation.from.Substring(0,
- operation.from.IndexOf('/' + positionAsInteger.ToString()));
- }
-
- var patchProperty = FindPropertyAndParent(objectToApplyTo, actualFromProperty);
-
- // does property at from exist?
- if (!CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.from))
+ if (valueAtFromLocationResult.HasError)
{
+ // Error has already been logged in GetValueAtLocation. We
+ // must return, because remove / add should not be allowed to continue
return;
}
- // is the path an array (but not a string (= char[]))? In this case,
- // the path must end with "/position" or "/-", which we already determined before.
- if (positionAsInteger > -1)
- {
- if (IsNonStringArray(patchProperty.Property.PropertyType))
- {
- // now, get the generic type of the IList<> from Property type.
- var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType);
-
- // get value (it can be cast, we just checked that)
- var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
-
- if (array.Count <= positionAsInteger)
- {
- LogError(new JsonPatchError(
- objectToApplyTo,
- operation,
- Resources.FormatInvalidIndexForArrayProperty(operation.op, operation.from)));
-
- return;
- }
-
- valueAtFromLocation = array[positionAsInteger];
- }
- else
- {
- LogError(new JsonPatchError(
- objectToApplyTo,
- operation,
- Resources.FormatInvalidPathForArrayProperty(operation.op, operation.from)));
-
- return;
- }
- }
- else
- {
- // no list, just get the value
- // set the new value
- valueAtFromLocation = patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
- }
-
// remove that value
- Remove(operation.from, objectToApplyTo, operation);
+ var removeResult = Remove(operation.from, objectToApplyTo, operation);
+
+ if (removeResult.HasError)
+ {
+ // Return => error has already been logged in remove method. We must
+ // return, because add should not be allowed to continue
+ return;
+ }
// add that value to the path location
- Add(operation.path, valueAtFromLocation, objectToApplyTo, operation);
+ Add(operation.path,
+ valueAtFromLocationResult.PropertyValue,
+ objectToApplyTo,
+ operation);
}
///
@@ -648,7 +605,7 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
Resources.FormatInvalidIndexForArrayProperty(
operationToReport.op,
path)));
- return null;
+ return new RemovedPropertyTypeResult(null, true);
}
array.RemoveAt(positionAsInteger);
@@ -706,8 +663,37 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
/// Object to apply the operation to.
public void Replace([NotNull] Operation operation, [NotNull] object objectToApplyTo)
{
- Remove(operation.path, objectToApplyTo, operation);
- Add(operation.path, operation.value, objectToApplyTo, operation);
+ var removeResult = Remove(operation.path, objectToApplyTo, operation);
+
+ if (removeResult.HasError)
+ {
+ // return => error has already been logged in remove method
+ return;
+ }
+
+ if (!removeResult.HasError && removeResult.ActualType == null)
+ {
+ // the remove operation completed succesfully, but we could not determine the type.
+ LogError(new JsonPatchError(
+ objectToApplyTo,
+ operation,
+ Resources.FormatCannotDeterminePropertyType(operation.from)));
+ return;
+ }
+
+ var conversionResult = ConvertToActualType(removeResult.ActualType, operation.value);
+
+ if (!conversionResult.CanBeConverted)
+ {
+ // invalid value for path
+ LogError(new JsonPatchError(
+ objectToApplyTo,
+ operation,
+ Resources.FormatInvalidValueForProperty(operation.value, operation.path)));
+ return;
+ }
+
+ Add(operation.path, conversionResult.ConvertedInstance, objectToApplyTo, operation);
}
///
@@ -734,100 +720,183 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
/// Object to apply the operation to.
public void Copy([NotNull] Operation operation, [NotNull] object objectToApplyTo)
{
- // get value at from location
- object valueAtFromLocation = null;
- var positionAsInteger = -1;
- var actualFromProperty = operation.from;
+ // get value at from location and add that value to the path location
+ var valueAtFromLocationResult = GetValueAtLocation(operation.from, objectToApplyTo, operation);
- positionAsInteger = GetNumericEnd(operation.from);
-
- if (positionAsInteger > -1)
- {
- actualFromProperty = operation.from.Substring(0,
- operation.from.IndexOf('/' + positionAsInteger.ToString()));
- }
-
- var patchProperty = FindPropertyAndParent(objectToApplyTo, actualFromProperty);
-
- // does property at from exist?
- if (!CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.from))
+ if (valueAtFromLocationResult.HasError)
{
+ // Return, error has already been logged in GetValueAtLocation
return;
}
- // get the property path
- // is the path an array (but not a string (= char[]))? In this case,
- // the path must end with "/position" or "/-", which we already determined before.
- if (positionAsInteger > -1)
+ Add(operation.path,
+ valueAtFromLocationResult.PropertyValue,
+ objectToApplyTo,
+ operation);
+ }
+
+ ///
+ /// Method is used by Copy and Move to avoid duplicate code
+ ///
+ /// Location where value should be
+ /// Object to inspect for the desired value
+ /// Operation to report in case of an error
+ /// GetValueResult containing value and a bool signifying a possible error
+ private GetValueResult GetValueAtLocation(
+ [NotNull] string location,
+ [NotNull] object objectToGetValueFrom,
+ [NotNull] Operation operationToReport)
+ {
+ // get path result
+ var pathResult = GetActualPropertyPath(
+ location,
+ objectToGetValueFrom,
+ operationToReport);
+
+ if (pathResult == null)
{
- if (IsNonStringArray(patchProperty.Property.PropertyType))
+ return new GetValueResult(null, true);
+ }
+
+ var getAtEndOfList = pathResult.ExecuteAtEnd;
+ var positionAsInteger = pathResult.NumericEnd;
+ var actualPathToProperty = pathResult.PathToProperty;
+
+ var treeAnalysisResult = new ObjectTreeAnalysisResult(
+ objectToGetValueFrom,
+ actualPathToProperty,
+ ContractResolver);
+
+ if (treeAnalysisResult.UseDynamicLogic)
+ {
+ // if it's not an array, we can remove the property from
+ // the dictionary. If it's an array, we need to check the position first.
+ if (getAtEndOfList || positionAsInteger > -1)
{
- // now, get the generic type of the IList<> from Property type.
- var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType);
+ var propertyValue = treeAnalysisResult.Container
+ .GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent);
- // get value (it can be cast, we just checked that)
- var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
-
- if (array.Count <= positionAsInteger)
+ // we cannot continue when the value is null, because to be able to
+ // continue we need to be able to check if the array is a non-string array
+ if (propertyValue == null)
{
LogError(new JsonPatchError(
- objectToApplyTo,
- operation,
- Resources.FormatInvalidIndexForArrayProperty(operation.op, operation.from)));
-
- return;
+ objectToGetValueFrom,
+ operationToReport,
+ Resources.FormatCannotDeterminePropertyType(location)));
+ return new GetValueResult(null, true);
}
- valueAtFromLocation = array[positionAsInteger];
+ var typeOfPathProperty = propertyValue.GetType();
+
+ if (!IsNonStringArray(typeOfPathProperty))
+ {
+ LogError(new JsonPatchError(
+ objectToGetValueFrom,
+ operationToReport,
+ Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, location)));
+ return new GetValueResult(null, true);
+ }
+
+ // get the array
+ var array = (IList)treeAnalysisResult.Container.GetValueForCaseInsensitiveKey(
+ treeAnalysisResult.PropertyPathInParent);
+
+ if (positionAsInteger >= array.Count)
+ {
+ LogError(new JsonPatchError(
+ objectToGetValueFrom,
+ operationToReport,
+ Resources.FormatInvalidIndexForArrayProperty(
+ operationToReport.op,
+ location)));
+ return new GetValueResult(null, true);
+ }
+
+ if (getAtEndOfList)
+ {
+ return new GetValueResult(array[array.Count-1], false);
+ }
+ else
+ {
+ return new GetValueResult(array[positionAsInteger], false);
+ }
}
else
{
- LogError(new JsonPatchError(
- objectToApplyTo,
- operation,
- Resources.FormatInvalidPathForArrayProperty(operation.op, operation.from)));
+ // get the property
+ var propertyValueAtLocation = treeAnalysisResult.Container.GetValueForCaseInsensitiveKey(
+ treeAnalysisResult.PropertyPathInParent);
- return;
- }
+ return new GetValueResult(propertyValueAtLocation, false);
+ }
}
else
{
- // no list, just get the value
- // set the new value
- valueAtFromLocation = patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
+ // not dynamic
+ var patchProperty = treeAnalysisResult.JsonPatchProperty;
+
+ if (getAtEndOfList || positionAsInteger > -1)
+ {
+ if (!IsNonStringArray(patchProperty.Property.PropertyType))
+ {
+ LogError(new JsonPatchError(
+ objectToGetValueFrom,
+ operationToReport,
+ Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, location)));
+ return new GetValueResult(null, true);
+ }
+
+ if (!patchProperty.Property.Readable)
+ {
+ LogError(new JsonPatchError(
+ objectToGetValueFrom,
+ operationToReport,
+ Resources.FormatCannotReadProperty(location)));
+ return new GetValueResult(null, true);
+ }
+
+ var array = (IList)patchProperty.Property.ValueProvider
+ .GetValue(patchProperty.Parent);
+
+ if (positionAsInteger >= array.Count)
+ {
+ LogError(new JsonPatchError(
+ objectToGetValueFrom,
+ operationToReport,
+ Resources.FormatInvalidIndexForArrayProperty(
+ operationToReport.op,
+ location)));
+ return new GetValueResult(null, true);
+ }
+
+ if (getAtEndOfList)
+ {
+ return new GetValueResult(array[array.Count - 1], false);
+ }
+ else
+ {
+ return new GetValueResult(array[positionAsInteger], false);
+ }
+ }
+ else
+ {
+ if (!patchProperty.Property.Readable)
+ {
+ LogError(new JsonPatchError(
+ objectToGetValueFrom,
+ operationToReport,
+ Resources.FormatCannotReadProperty(
+ location)));
+ return new GetValueResult(null, true);
+ }
+
+ var propertyValueAtLocation = patchProperty.Property.ValueProvider
+ .GetValue(patchProperty.Parent);
+
+ return new GetValueResult(propertyValueAtLocation, false);
+ }
}
-
- // add operation to target location with that value.
- Add(operation.path, valueAtFromLocation, objectToApplyTo, operation);
- }
-
- private bool CheckIfPropertyExists(
- JsonPatchProperty patchProperty,
- object objectToApplyTo,
- Operation operation,
- string propertyPath)
- {
- if (patchProperty == null)
- {
- LogError(new JsonPatchError(
- objectToApplyTo,
- operation,
- Resources.FormatPropertyDoesNotExist(propertyPath)));
-
- return false;
- }
-
- if (patchProperty.Property.Ignored)
- {
- LogError(new JsonPatchError(
- objectToApplyTo,
- operation,
- Resources.FormatCannotUpdateProperty(propertyPath)));
-
- return false;
- }
-
- return true;
}
private bool IsNonStringArray(Type type)
@@ -840,25 +909,6 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
return (!(type == typeof(string)) && typeof(IList).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()));
}
- private bool CheckIfPropertyCanBeSet(
- ConversionResult result,
- object objectToApplyTo,
- Operation operation,
- string path)
- {
- if (!result.CanBeConverted)
- {
- LogError(new JsonPatchError(
- objectToApplyTo,
- operation,
- Resources.FormatInvalidValueForProperty(result.ConvertedInstance, path)));
-
- return false;
- }
-
- return true;
- }
-
private void LogError(JsonPatchError jsonPatchError)
{
if (LogErrorAction != null)
@@ -871,54 +921,6 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
}
}
- private JsonPatchProperty FindPropertyAndParent(object targetObject, string propertyPath)
- {
- try
- {
- var splitPath = propertyPath.Split('/');
-
- // skip the first one if it's empty
- var startIndex = (string.IsNullOrWhiteSpace(splitPath[0]) ? 1 : 0);
-
- for (int i = startIndex; i < splitPath.Length; i++)
- {
- var jsonContract = (JsonObjectContract)ContractResolver.ResolveContract(targetObject.GetType());
-
- foreach (var property in jsonContract.Properties)
- {
- if (string.Equals(property.PropertyName, splitPath[i], StringComparison.OrdinalIgnoreCase))
- {
- if (i == (splitPath.Length - 1))
- {
- return new JsonPatchProperty(property, targetObject);
- }
- else
- {
- targetObject = property.ValueProvider.GetValue(targetObject);
-
- // if property is of IList type then get the array index from splitPath and get the
- // object at the indexed position from the list.
- if (GetIListType(property.PropertyType) != null)
- {
- var index = int.Parse(splitPath[++i]);
- targetObject = ((IList)targetObject)[index];
- }
- }
-
- break;
- }
- }
- }
-
- return null;
- }
- catch (Exception)
- {
- // will result in JsonPatchException in calling class, as expected
- return null;
- }
- }
-
private ConversionResult ConvertToActualType(Type propertyType, object value)
{
try
@@ -962,20 +964,6 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
return false;
}
- private int GetNumericEnd(string path)
- {
- var possibleIndex = path.Substring(path.LastIndexOf("/") + 1);
- var castedIndex = -1;
-
- if (int.TryParse(possibleIndex, out castedIndex))
- {
- return castedIndex;
- }
-
- return -1;
- }
-
-
private ActualPropertyPathResult GetActualPropertyPath(
[NotNull] string propertyPath,
[NotNull] object objectToApplyTo,
diff --git a/src/Microsoft.AspNet.JsonPatch/Helpers/GetValueResult.cs b/src/Microsoft.AspNet.JsonPatch/Helpers/GetValueResult.cs
new file mode 100644
index 0000000000..89b7c93520
--- /dev/null
+++ b/src/Microsoft.AspNet.JsonPatch/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.AspNet.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/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/CopyOperationTests.cs b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/CopyOperationTests.cs
new file mode 100644
index 0000000000..3fa546faac
--- /dev/null
+++ b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/CopyOperationTests.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 System.Dynamic;
+using Newtonsoft.Json;
+using Xunit;
+
+namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
+{
+ public class CopyOperationTests
+ {
+ [Fact]
+ public void Copy()
+ {
+ dynamic doc = new ExpandoObject();
+
+ doc.StringProperty = "A";
+ doc.AnotherStringProperty = "B";
+
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("StringProperty", "AnotherStringProperty");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal("A", doc.AnotherStringProperty);
+ }
+
+ [Fact]
+ public void CopyInList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("IntegerList/0", "IntegerList/1");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 1, 1, 2, 3 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void CopyFromListToEndOfList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("IntegerList/0", "IntegerList/-");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 1, 2, 3, 1 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void CopyFromListToNonList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("IntegerList/0", "IntegerValue");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(1, doc.IntegerValue);
+ }
+
+ [Fact]
+ public void CopyFromNonListToList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerValue = 5;
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("IntegerValue", "IntegerList/0");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 5, 1, 2, 3 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void CopyToEndOfList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerValue = 5;
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("IntegerValue", "IntegerList/-");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 1, 2, 3, 5 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void NestedCopy()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.SimpleDTO = new SimpleDTO()
+ {
+ StringProperty = "A",
+ AnotherStringProperty = "B"
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("SimpleDTO/StringProperty", "SimpleDTO/AnotherStringProperty");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal("A", doc.SimpleDTO.AnotherStringProperty);
+ }
+
+ [Fact]
+ public void NestedCopyInList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.SimpleDTO = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/1");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 1, 1, 2, 3 }, doc.SimpleDTO.IntegerList);
+ }
+
+ [Fact]
+ public void NestedCopyFromListToEndOfList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.SimpleDTO = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/-");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 1, 2, 3, 1 }, doc.SimpleDTO.IntegerList);
+ }
+
+ [Fact]
+ public void NestedCopyFromListToNonList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.SimpleDTO = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerValue");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(1, doc.SimpleDTO.IntegerValue);
+ }
+
+ [Fact]
+ public void NestedCopyFromNonListToList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.SimpleDTO = new SimpleDTO()
+ {
+ IntegerValue = 5,
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/0");
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 5, 1, 2, 3 }, doc.SimpleDTO.IntegerList);
+ }
+
+ [Fact]
+ public void NestedCopyToEndOfList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.SimpleDTO = new SimpleDTO()
+ {
+ IntegerValue = 5,
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/-");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 1, 2, 3, 5 }, doc.SimpleDTO.IntegerList);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/CopyTypedOperationTests.cs b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/CopyTypedOperationTests.cs
new file mode 100644
index 0000000000..5b015416ac
--- /dev/null
+++ b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/CopyTypedOperationTests.cs
@@ -0,0 +1,268 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
+{
+ public class CopyTypedOperationTests
+ {
+ [Fact]
+ public void Copy()
+ {
+ var doc = new SimpleDTO()
+ {
+ StringProperty = "A",
+ AnotherStringProperty = "B"
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("StringProperty", "AnotherStringProperty");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal("A", doc.AnotherStringProperty);
+ }
+
+ [Fact]
+ public void CopyInList()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("IntegerList/0", "IntegerList/1");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 1, 1, 2, 3 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void CopyFromListToEndOfList()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("IntegerList/0", "IntegerList/-");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 1, 2, 3, 1 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void CopyFromListToNonList()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("IntegerList/0", "IntegerValue");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(1, doc.IntegerValue);
+ }
+
+ [Fact]
+ public void CopyFromNonListToList()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerValue = 5,
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("IntegerValue", "IntegerList/0");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 5, 1, 2, 3 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void CopyToEndOfList()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerValue = 5,
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("IntegerValue", "IntegerList/-");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 1, 2, 3, 5 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void NestedCopy()
+ {
+ var doc = new SimpleDTOWithNestedDTO()
+ {
+ SimpleDTO = new SimpleDTO()
+ {
+ StringProperty = "A",
+ AnotherStringProperty = "B"
+ }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("SimpleDTO/StringProperty", "SimpleDTO/AnotherStringProperty");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal("A", doc.SimpleDTO.AnotherStringProperty);
+ }
+
+ [Fact]
+ public void NestedCopyInList()
+ {
+ var doc = new SimpleDTOWithNestedDTO()
+ {
+ SimpleDTO = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/1");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 1, 1, 2, 3 }, doc.SimpleDTO.IntegerList);
+ }
+
+ [Fact]
+ public void NestedCopyFromListToEndOfList()
+ {
+ var doc = new SimpleDTOWithNestedDTO()
+ {
+ SimpleDTO = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/-");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 1, 2, 3, 1 }, doc.SimpleDTO.IntegerList);
+ }
+
+ [Fact]
+ public void NestedCopyFromListToNonList()
+ {
+ var doc = new SimpleDTOWithNestedDTO()
+ {
+ SimpleDTO = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerValue");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(1, doc.SimpleDTO.IntegerValue);
+ }
+
+ [Fact]
+ public void NestedCopyFromNonListToList()
+ {
+ var doc = new SimpleDTOWithNestedDTO()
+ {
+ SimpleDTO = new SimpleDTO()
+ {
+ IntegerValue = 5,
+ IntegerList = new List() { 1, 2, 3 }
+ }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/0");
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 5, 1, 2, 3 }, doc.SimpleDTO.IntegerList);
+ }
+
+ [Fact]
+ public void NestedCopyToEndOfList()
+ {
+ var doc = new SimpleDTOWithNestedDTO()
+ {
+ SimpleDTO = new SimpleDTO()
+ {
+ IntegerValue = 5,
+ IntegerList = new List() { 1, 2, 3 }
+ }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/-");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 1, 2, 3, 5 }, doc.SimpleDTO.IntegerList);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/MoveOperationTests.cs b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/MoveOperationTests.cs
new file mode 100644
index 0000000000..dea2b6ee32
--- /dev/null
+++ b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/MoveOperationTests.cs
@@ -0,0 +1,338 @@
+// 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;
+using Xunit;
+
+namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
+{
+ public class MoveOperationTests
+ {
+ [Fact]
+ public void Move()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.StringProperty = "A";
+ doc.AnotherStringProperty = "B";
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("StringProperty", "AnotherStringProperty");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal("A", doc.AnotherStringProperty);
+
+ var cont = doc as IDictionary;
+ object valueFromDictionary;
+ cont.TryGetValue("StringProperty", out valueFromDictionary);
+ Assert.Null(valueFromDictionary);
+ }
+
+ [Fact]
+ public void MoveToNonExisting()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.StringProperty = "A";
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("StringProperty", "AnotherStringProperty");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal("A", doc.AnotherStringProperty);
+
+ var cont = doc as IDictionary;
+ object valueFromDictionary;
+ cont.TryGetValue("StringProperty", out valueFromDictionary);
+ Assert.Null(valueFromDictionary);
+ }
+
+ [Fact]
+ public void MoveDynamicToTyped()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.StringProperty = "A";
+ doc.SimpleDTO = new SimpleDTO() { AnotherStringProperty = "B" };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("StringProperty", "SimpleDTO/AnotherStringProperty");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal("A", doc.SimpleDTO.AnotherStringProperty);
+
+ var cont = doc as IDictionary;
+ object valueFromDictionary;
+ cont.TryGetValue("StringProperty", out valueFromDictionary);
+ Assert.Null(valueFromDictionary);
+ }
+
+ [Fact]
+ public void MoveTypedToDynamic()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.StringProperty = "A";
+ doc.SimpleDTO = new SimpleDTO() { AnotherStringProperty = "B" };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("SimpleDTO/AnotherStringProperty", "StringProperty");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal("B", doc.StringProperty);
+ Assert.Equal(null, doc.SimpleDTO.AnotherStringProperty);
+ }
+
+ [Fact]
+ public void NestedMove()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.Nested = new SimpleDTO()
+ {
+ StringProperty = "A",
+ AnotherStringProperty = "B"
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("Nested/StringProperty", "Nested/AnotherStringProperty");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal("A", doc.Nested.AnotherStringProperty);
+ Assert.Equal(null, doc.Nested.StringProperty);
+ }
+
+ [Fact]
+ public void MoveInList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("IntegerList/0", "IntegerList/1");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 2, 1, 3 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void NestedMoveInList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.Nested = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("Nested/IntegerList/0", "Nested/IntegerList/1");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 2, 1, 3 }, doc.Nested.IntegerList);
+ }
+
+ [Fact]
+ public void MoveFromListToEndOfList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("IntegerList/0", "IntegerList/-");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 2, 3, 1 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void NestedMoveFromListToEndOfList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.Nested = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("Nested/IntegerList/0", "Nested/IntegerList/-");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 2, 3, 1 }, doc.Nested.IntegerList);
+ }
+
+ [Fact]
+ public void MoveFomListToNonList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("IntegerList/0", "IntegerValue");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 2, 3 }, doc.IntegerList);
+ Assert.Equal(1, doc.IntegerValue);
+ }
+
+ [Fact]
+ public void NestedMoveFomListToNonList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.Nested = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("Nested/IntegerList/0", "Nested/IntegerValue");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 2, 3 }, doc.Nested.IntegerList);
+ Assert.Equal(1, doc.Nested.IntegerValue);
+ }
+
+ [Fact]
+ public void MoveFromNonListToList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerValue = 5;
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("IntegerValue", "IntegerList/0");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ var cont = doc as IDictionary;
+ object valueFromDictionary;
+ cont.TryGetValue("IntegerValue", out valueFromDictionary);
+ Assert.Null(valueFromDictionary);
+
+ Assert.Equal(new List() { 5, 1, 2, 3 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void NestedMoveFromNonListToList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.Nested = new SimpleDTO()
+ {
+ IntegerValue = 5,
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("Nested/IntegerValue", "Nested/IntegerList/0");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(0, doc.Nested.IntegerValue);
+ Assert.Equal(new List() { 5, 1, 2, 3 }, doc.Nested.IntegerList);
+ }
+
+ [Fact]
+ public void MoveToEndOfList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerValue = 5;
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("IntegerValue", "IntegerList/-");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ var cont = doc as IDictionary;
+ object valueFromDictionary;
+ cont.TryGetValue("IntegerValue", out valueFromDictionary);
+ Assert.Null(valueFromDictionary);
+
+ Assert.Equal(new List() { 1, 2, 3, 5 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void NestedMoveToEndOfList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.Nested = new SimpleDTO()
+ {
+ IntegerValue = 5,
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("Nested/IntegerValue", "Nested/IntegerList/-");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(0, doc.Nested.IntegerValue);
+ Assert.Equal(new List() { 1, 2, 3, 5 }, doc.Nested.IntegerList);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/MoveTypedOperationTests.cs b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/MoveTypedOperationTests.cs
new file mode 100644
index 0000000000..3d356a3401
--- /dev/null
+++ b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/MoveTypedOperationTests.cs
@@ -0,0 +1,138 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
+{
+ public class MoveTypedOperationTests
+ {
+ [Fact]
+ public void Move()
+ {
+ var doc = new SimpleDTO()
+ {
+ StringProperty = "A",
+ AnotherStringProperty = "B"
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("StringProperty", "AnotherStringProperty");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal("A", doc.AnotherStringProperty);
+ Assert.Equal(null, doc.StringProperty);
+ }
+
+ [Fact]
+ public void MoveInList()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("IntegerList/0", "IntegerList/1");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 2, 1, 3 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void MoveFromListToEndOfList()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("IntegerList/0", "IntegerList/-");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 2, 3, 1 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void MoveFomListToNonList()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("IntegerList/0", "IntegerValue");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 2, 3 }, doc.IntegerList);
+ Assert.Equal(1, doc.IntegerValue);
+ }
+
+ [Fact]
+ public void MoveFromNonListToList()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerValue = 5,
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("IntegerValue", "IntegerList/0");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(0, doc.IntegerValue);
+ Assert.Equal(new List() { 5, 1, 2, 3 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void MoveToEndOfList()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerValue = 5,
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Move("IntegerValue", "IntegerList/-");
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(0, doc.IntegerValue);
+ Assert.Equal(new List() { 1, 2, 3, 5 }, doc.IntegerList);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/ReplaceOperationTests.cs b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/ReplaceOperationTests.cs
new file mode 100644
index 0000000000..c7573c0286
--- /dev/null
+++ b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/ReplaceOperationTests.cs
@@ -0,0 +1,242 @@
+// 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.Collections.ObjectModel;
+using System.Dynamic;
+using Newtonsoft.Json;
+using Xunit;
+
+namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
+{
+ public class ReplaceOperationTests
+ {
+ [Fact]
+ public void ReplaceGuidTest()
+ {
+ dynamic doc = new SimpleDTO()
+ {
+ GuidValue = Guid.NewGuid()
+ };
+
+ var newGuid = Guid.NewGuid();
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("GuidValue", newGuid);
+
+ // serialize & deserialize
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserizalized = JsonConvert.DeserializeObject(serialized);
+
+ deserizalized.ApplyTo(doc);
+
+ Assert.Equal(newGuid, doc.GuidValue);
+ }
+
+ [Fact]
+ public void ReplaceGuidTestExpandoObject()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.GuidValue = Guid.NewGuid();
+
+ var newGuid = Guid.NewGuid();
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("GuidValue", newGuid);
+
+ // serialize & deserialize
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserizalized = JsonConvert.DeserializeObject(serialized);
+
+ deserizalized.ApplyTo(doc);
+
+ Assert.Equal(newGuid, doc.GuidValue);
+ }
+
+ [Fact]
+ public void ReplaceGuidTestExpandoObjectInAnonymous()
+ {
+ dynamic nestedObject = new ExpandoObject();
+ nestedObject.GuidValue = Guid.NewGuid();
+
+ dynamic doc = new
+ {
+ NestedObject = nestedObject
+ };
+
+ var newGuid = Guid.NewGuid();
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("nestedobject/GuidValue", newGuid);
+
+ // serialize & deserialize
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserizalized = JsonConvert.DeserializeObject(serialized);
+
+ deserizalized.ApplyTo(doc);
+
+ Assert.Equal(newGuid, doc.NestedObject.GuidValue);
+ }
+
+ [Fact]
+ public void ReplaceNestedObjectTest()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.SimpleDTO = new SimpleDTO()
+ {
+ IntegerValue = 5,
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ var newDTO = new SimpleDTO()
+ {
+ DoubleValue = 1
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("SimpleDTO", newDTO);
+
+ // serialize & deserialize
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(1, doc.SimpleDTO.DoubleValue);
+ Assert.Equal(0, doc.SimpleDTO.IntegerValue);
+ Assert.Equal(null, doc.SimpleDTO.IntegerList);
+ }
+
+ [Fact]
+ public void ReplaceInList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("IntegerList/0", 5);
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 5, 2, 3 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void ReplaceFullList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("IntegerList", new List() { 4, 5, 6 });
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void ReplaceInListInList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.SimpleDTOList = new List() {
+ new SimpleDTO() {
+ IntegerList = new List(){1,2,3}
+ }};
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("SimpleDTOList/0/IntegerList/0", 4);
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(4, doc.SimpleDTOList[0].IntegerList[0]);
+ }
+
+ [Fact]
+ public void ReplaceInListInListAtEnd()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.SimpleDTOList = new List() {
+ new SimpleDTO() {
+ IntegerList = new List(){1,2,3}
+ }};
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("SimpleDTOList/0/IntegerList/-", 4);
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(4, doc.SimpleDTOList[0].IntegerList[2]);
+ }
+
+ [Fact]
+ public void ReplaceFullListFromEnumerable()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("IntegerList", new List() { 4, 5, 6 });
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+ Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void ReplaceFullListWithCollection()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("IntegerList", new Collection() { 4, 5, 6 });
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void ReplaceAtEndOfList()
+ {
+ dynamic doc = new ExpandoObject();
+ doc.IntegerList = new List() { 1, 2, 3 };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("IntegerList/-", 5);
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 1, 2, 5 }, doc.IntegerList);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/ReplaceTypedOperationTests.cs b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/ReplaceTypedOperationTests.cs
new file mode 100644
index 0000000000..ccdd86f1a7
--- /dev/null
+++ b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/ReplaceTypedOperationTests.cs
@@ -0,0 +1,191 @@
+// 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.Collections.ObjectModel;
+using System.Linq;
+using Newtonsoft.Json;
+using Xunit;
+
+namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
+{
+ public class ReplaceTypedOperationTests
+ {
+ [Fact]
+ public void ReplaceGuidTest()
+ {
+ var doc = new SimpleDTO()
+ {
+ GuidValue = Guid.NewGuid()
+ };
+
+ var newGuid = Guid.NewGuid();
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("GuidValue", newGuid);
+
+ // serialize & deserialize
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserizalized = JsonConvert.DeserializeObject(serialized);
+
+ deserizalized.ApplyTo(doc);
+
+ Assert.Equal(newGuid, doc.GuidValue);
+ }
+
+ [Fact]
+ public void SerializeAndReplaceNestedObjectTest()
+ {
+ var doc = new SimpleDTOWithNestedDTO()
+ {
+ SimpleDTO = new SimpleDTO()
+ {
+ IntegerValue = 5,
+ IntegerList = new List() { 1, 2, 3 }
+ }
+ };
+
+ var newDTO = new SimpleDTO()
+ {
+ DoubleValue = 1
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("SimpleDTO", newDTO);
+
+ // serialize & deserialize
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(1, doc.SimpleDTO.DoubleValue);
+ Assert.Equal(0, doc.SimpleDTO.IntegerValue);
+ Assert.Equal(null, doc.SimpleDTO.IntegerList);
+ }
+
+ [Fact]
+ public void ReplaceInList()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("IntegerList/0", 5);
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 5, 2, 3 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void ReplaceFullList()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("IntegerList", new List() { 4, 5, 6 });
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void ReplaceInListInList()
+ {
+ var doc = new SimpleDTO()
+ {
+ SimpleDTOList = new List() {
+ new SimpleDTO() {
+ IntegerList = new List(){1,2,3}
+ }}
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("SimpleDTOList/0/IntegerList/0", 4);
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(4, doc.SimpleDTOList.First().IntegerList.First());
+ }
+
+ [Fact]
+ public void ReplaceFullListFromEnumerable()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("IntegerList", new List() { 4, 5, 6 });
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void ReplaceFullListWithCollection()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("IntegerList", new Collection() { 4, 5, 6 });
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList);
+ }
+
+ [Fact]
+ public void ReplaceAtEndOfList()
+ {
+ var doc = new SimpleDTO()
+ {
+ IntegerList = new List() { 1, 2, 3 }
+ };
+
+ // create patch
+ JsonPatchDocument patchDoc = new JsonPatchDocument();
+ patchDoc.Replace("IntegerList/-", 5);
+
+ var serialized = JsonConvert.SerializeObject(patchDoc);
+ var deserialized = JsonConvert.DeserializeObject(serialized);
+ deserialized.ApplyTo(doc);
+
+ Assert.Equal(new List() { 1, 2, 5 }, doc.IntegerList);
+ }
+ }
+}