[Fixes #61] Move must keep object reference
This commit is contained in:
parent
dcfbbab005
commit
42d0d40b36
|
|
@ -362,10 +362,21 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
|||
// Get value at 'from' location and add that value to the 'path' location
|
||||
if (TryGetValue(operation.from, objectToApplyTo, operation, out propertyValue))
|
||||
{
|
||||
Add(operation.path,
|
||||
propertyValue,
|
||||
objectToApplyTo,
|
||||
operation);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// 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
|
||||
|
|
@ -10,9 +11,44 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
{
|
||||
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), typeToConvertTo);
|
||||
var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), targetType);
|
||||
return new ConversionResult(true, deserialized);
|
||||
}
|
||||
catch
|
||||
|
|
@ -20,5 +56,20 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
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<T>
|
||||
return typeInfo.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
|
||||
}
|
||||
else
|
||||
{
|
||||
// reference types are always nullable
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,22 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("CannotDeterminePropertyType"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property at '{0}' could not be copied.
|
||||
/// </summary>
|
||||
internal static string CannotCannotCopyProperty
|
||||
{
|
||||
get { return GetString("CannotCannotCopyProperty"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property at '{0}' could not be copied.
|
||||
/// </summary>
|
||||
internal static string FormatCannotCopyProperty(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("CannotCannotCopyProperty"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' operation at path '{1}' could not be performed.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -117,6 +117,9 @@
|
|||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="CannotCopyProperty" xml:space="preserve">
|
||||
<value>The property at '{0}' could not be copied.</value>
|
||||
</data>
|
||||
<data name="CannotDeterminePropertyType" xml:space="preserve">
|
||||
<value>The type of the property at path '{0}' could not be determined.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
// 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
|
||||
{
|
||||
public class InheritedDTO : SimpleDTO
|
||||
{
|
||||
public string AdditionalStringProperty { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -174,6 +174,27 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
Assert.Equal(new List<string>() { "James", "Mike", null }, targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_CompatibleTypeWorks()
|
||||
{
|
||||
// Arrange
|
||||
var sDto = new SimpleDTO();
|
||||
var iDto = new InheritedDTO();
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = (new List<SimpleDTO>() { sDto });
|
||||
var listAdapter = new ListAdapter();
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, "-", resolver.Object, iDto, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(2, targetObject.Count);
|
||||
Assert.Equal(new List<SimpleDTO>() { sDto, iDto }, targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_NonCompatibleType_Fails()
|
||||
{
|
||||
|
|
@ -244,6 +265,60 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
Assert.Equal(expected, targetObject);
|
||||
}
|
||||
|
||||
public static TheoryData<IList, object, string, IList> AddingKeepsObjectReferenceData {
|
||||
get {
|
||||
var sDto1 = new SimpleDTO();
|
||||
var sDto2 = new SimpleDTO();
|
||||
var sDto3 = new SimpleDTO();
|
||||
return new TheoryData<IList, object, string, IList>()
|
||||
{
|
||||
{
|
||||
new List<SimpleDTO>() { },
|
||||
sDto1,
|
||||
"-",
|
||||
new List<SimpleDTO>() { sDto1 }
|
||||
},
|
||||
{
|
||||
new List<SimpleDTO>() { sDto1, sDto2 },
|
||||
sDto3,
|
||||
"-",
|
||||
new List<SimpleDTO>() { sDto1, sDto2, sDto3 }
|
||||
},
|
||||
{
|
||||
new List<SimpleDTO>() { sDto1, sDto2 },
|
||||
sDto3,
|
||||
"0",
|
||||
new List<SimpleDTO>() { sDto3, sDto1, sDto2 }
|
||||
},
|
||||
{
|
||||
new List<SimpleDTO>() { sDto1, sDto2 },
|
||||
sDto3,
|
||||
"1",
|
||||
new List<SimpleDTO>() { sDto1, sDto3, sDto2 }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AddingKeepsObjectReferenceData))]
|
||||
public void Add_KeepsObjectReference(IList targetObject, object value, string position, IList expected)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var listAdapter = new ListAdapter();
|
||||
string message = null;
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, position, resolver.Object, value, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(expected.Count, targetObject.Count);
|
||||
Assert.Equal(expected, targetObject);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new int[] { }, "0")]
|
||||
[InlineData(new[] { 10, 20 }, "-1")]
|
||||
|
|
|
|||
|
|
@ -1699,6 +1699,81 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
Assert.Equal(new List<int>() { 1, 2, 3, 5 }, doc.SimpleDTO.IntegerList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Copy_DeepClonesObject()
|
||||
{
|
||||
// Arrange
|
||||
var doc = new SimpleDTOWithNestedDTO()
|
||||
{
|
||||
SimpleDTO = new SimpleDTO()
|
||||
{
|
||||
StringProperty = "A",
|
||||
AnotherStringProperty = "B"
|
||||
},
|
||||
InheritedDTO = new InheritedDTO()
|
||||
{
|
||||
StringProperty = "C",
|
||||
AnotherStringProperty = "D"
|
||||
}
|
||||
};
|
||||
|
||||
// create patch
|
||||
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
|
||||
patchDoc.Copy<SimpleDTO>(o => o.InheritedDTO, o => o.SimpleDTO);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(doc);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("C", doc.SimpleDTO.StringProperty);
|
||||
Assert.Equal("D", doc.SimpleDTO.AnotherStringProperty);
|
||||
Assert.Equal("C", doc.InheritedDTO.StringProperty);
|
||||
Assert.Equal("D", doc.InheritedDTO.AnotherStringProperty);
|
||||
Assert.NotSame(doc.SimpleDTO.StringProperty, doc.InheritedDTO.StringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Copy_KeepsObjectType()
|
||||
{
|
||||
// Arrange
|
||||
var doc = new SimpleDTOWithNestedDTO()
|
||||
{
|
||||
SimpleDTO = new SimpleDTO(),
|
||||
InheritedDTO = new InheritedDTO()
|
||||
};
|
||||
|
||||
// create patch
|
||||
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
|
||||
patchDoc.Copy<SimpleDTO>(o => o.InheritedDTO, o => o.SimpleDTO);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(doc);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(typeof(InheritedDTO), doc.SimpleDTO.GetType());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Copy_BreaksObjectReference()
|
||||
{
|
||||
// Arrange
|
||||
var doc = new SimpleDTOWithNestedDTO()
|
||||
{
|
||||
SimpleDTO = new SimpleDTO(),
|
||||
InheritedDTO = new InheritedDTO()
|
||||
};
|
||||
|
||||
// create patch
|
||||
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
|
||||
patchDoc.Copy<SimpleDTO>(o => o.InheritedDTO, o => o.SimpleDTO);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(doc);
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(doc.SimpleDTO, doc.InheritedDTO);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Move()
|
||||
{
|
||||
|
|
@ -1752,6 +1827,77 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
Assert.Equal(null, doc.SimpleDTO.StringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Move_KeepsObjectReference()
|
||||
{
|
||||
// Arrange
|
||||
var sDto = new SimpleDTO()
|
||||
{
|
||||
StringProperty = "A",
|
||||
AnotherStringProperty = "B"
|
||||
};
|
||||
var iDto = new InheritedDTO()
|
||||
{
|
||||
StringProperty = "C",
|
||||
AnotherStringProperty = "D"
|
||||
};
|
||||
var doc = new SimpleDTOWithNestedDTO()
|
||||
{
|
||||
SimpleDTO = sDto,
|
||||
InheritedDTO = iDto
|
||||
};
|
||||
|
||||
// create patch
|
||||
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
|
||||
patchDoc.Move<SimpleDTO>(o => o.InheritedDTO, o => o.SimpleDTO);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(doc);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("C", doc.SimpleDTO.StringProperty);
|
||||
Assert.Equal("D", doc.SimpleDTO.AnotherStringProperty);
|
||||
Assert.Same(iDto, doc.SimpleDTO);
|
||||
Assert.Equal(null, doc.InheritedDTO);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Move_KeepsObjectReferenceWithSerialization()
|
||||
{
|
||||
// Arrange
|
||||
var sDto = new SimpleDTO()
|
||||
{
|
||||
StringProperty = "A",
|
||||
AnotherStringProperty = "B"
|
||||
};
|
||||
var iDto = new InheritedDTO()
|
||||
{
|
||||
StringProperty = "C",
|
||||
AnotherStringProperty = "D"
|
||||
};
|
||||
var doc = new SimpleDTOWithNestedDTO()
|
||||
{
|
||||
SimpleDTO = sDto,
|
||||
InheritedDTO = iDto
|
||||
};
|
||||
|
||||
// create patch
|
||||
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
|
||||
patchDoc.Move<SimpleDTO>(o => o.InheritedDTO, o => o.SimpleDTO);
|
||||
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<SimpleDTOWithNestedDTO>>(serialized);
|
||||
|
||||
// Act
|
||||
deserialized.ApplyTo(doc);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("C", doc.SimpleDTO.StringProperty);
|
||||
Assert.Equal("D", doc.SimpleDTO.AnotherStringProperty);
|
||||
Assert.Same(iDto, doc.SimpleDTO);
|
||||
Assert.Equal(null, doc.InheritedDTO);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveInList()
|
||||
{
|
||||
|
|
@ -1801,6 +1947,71 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
Assert.Equal(new List<int>() { 2, 1, 3 }, doc.SimpleDTO.IntegerList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Move_KeepsObjectReferenceInList()
|
||||
{
|
||||
// Arrange
|
||||
var sDto1 = new SimpleDTO() { IntegerValue = 1 };
|
||||
var sDto2 = new SimpleDTO() { IntegerValue = 2 };
|
||||
var sDto3 = new SimpleDTO() { IntegerValue = 3 };
|
||||
var doc = new SimpleDTOWithNestedDTO()
|
||||
{
|
||||
SimpleDTOList = new List<SimpleDTO>() {
|
||||
sDto1,
|
||||
sDto2,
|
||||
sDto3
|
||||
}
|
||||
};
|
||||
|
||||
// create patch
|
||||
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
|
||||
patchDoc.Move<SimpleDTO>(o => o.SimpleDTOList, 0, o => o.SimpleDTOList, 1);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(doc);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<SimpleDTO>() { sDto2, sDto1, sDto3 }, doc.SimpleDTOList);
|
||||
Assert.Equal(2, doc.SimpleDTOList[0].IntegerValue);
|
||||
Assert.Equal(1, doc.SimpleDTOList[1].IntegerValue);
|
||||
Assert.Same(sDto2, doc.SimpleDTOList[0]);
|
||||
Assert.Same(sDto1, doc.SimpleDTOList[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Move_KeepsObjectReferenceInListWithSerialization()
|
||||
{
|
||||
// Arrange
|
||||
var sDto1 = new SimpleDTO() { IntegerValue = 1 };
|
||||
var sDto2 = new SimpleDTO() { IntegerValue = 2 };
|
||||
var sDto3 = new SimpleDTO() { IntegerValue = 3 };
|
||||
var doc = new SimpleDTOWithNestedDTO()
|
||||
{
|
||||
SimpleDTOList = new List<SimpleDTO>() {
|
||||
sDto1,
|
||||
sDto2,
|
||||
sDto3
|
||||
}
|
||||
};
|
||||
|
||||
// create patch
|
||||
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
|
||||
patchDoc.Move<SimpleDTO>(o => o.SimpleDTOList, 0, o => o.SimpleDTOList, 1);
|
||||
|
||||
var serialized = JsonConvert.SerializeObject(patchDoc);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<SimpleDTOWithNestedDTO>>(serialized);
|
||||
|
||||
// Act
|
||||
patchDoc.ApplyTo(doc);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<SimpleDTO>() { sDto2, sDto1, sDto3 }, doc.SimpleDTOList);
|
||||
Assert.Equal(2, doc.SimpleDTOList[0].IntegerValue);
|
||||
Assert.Equal(1, doc.SimpleDTOList[1].IntegerValue);
|
||||
Assert.Same(sDto2, doc.SimpleDTOList[0]);
|
||||
Assert.Same(sDto1, doc.SimpleDTOList[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveFromListToEndOfList()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
|
||||
public SimpleDTO SimpleDTO { get; set; }
|
||||
|
||||
public InheritedDTO InheritedDTO { get; set; }
|
||||
|
||||
public List<SimpleDTO> SimpleDTOList { get; set; }
|
||||
|
||||
public IList<SimpleDTO> SimpleDTOIList { get; set; }
|
||||
|
|
@ -21,6 +23,7 @@ namespace Microsoft.AspNetCore.JsonPatch
|
|||
{
|
||||
this.NestedDTO = new NestedDTO();
|
||||
this.SimpleDTO = new SimpleDTO();
|
||||
this.InheritedDTO = new InheritedDTO();
|
||||
this.SimpleDTOList = new List<SimpleDTO>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue