Improve conformance of replace operations to spec
This ensures that JSON patch "replace" operations are functionally equivalent to "remove" operations followed by "add" operations at the same path, as RFC 6902 specifies. Addresses #110
This commit is contained in:
parent
e842973c71
commit
0b76599c31
|
|
@ -96,6 +96,11 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!TryRemove(target, segment, contractResolver, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TrySetDynamicObjectProperty(target, contractResolver, segment, convertedValue, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -130,11 +130,11 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void TryReplace_ReplacesPropertyValue()
|
||||
public void TryReplace_RemovesExistingValue_BeforeAddingNewValue()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
dynamic target = new WriteOnceDynamicTestObject();
|
||||
target.NewProperty = new object();
|
||||
var segment = "NewProperty";
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
// 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.Dynamic;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This class is used specifically to test that JSON patch "replace" operations are functionally equivalent to
|
||||
/// "add" and "remove" operations applied sequentially using the same path.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This is done by asserting that no value exists for a particular key before setting its value. To replace the
|
||||
/// value for a key, the key must first be removed, and then re-added with the new value.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// See JsonPatch#110 for further details.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class WriteOnceDynamicTestObject : DynamicObject
|
||||
{
|
||||
private Dictionary<string, object> _dictionary = new Dictionary<string, object>();
|
||||
|
||||
public object this[string key] { get => ((IDictionary<string, object>)_dictionary)[key]; set => SetValueForKey(key, value); }
|
||||
|
||||
public ICollection<string> Keys => ((IDictionary<string, object>)_dictionary).Keys;
|
||||
|
||||
public ICollection<object> Values => ((IDictionary<string, object>)_dictionary).Values;
|
||||
|
||||
public int Count => ((IDictionary<string, object>)_dictionary).Count;
|
||||
|
||||
public bool IsReadOnly => ((IDictionary<string, object>)_dictionary).IsReadOnly;
|
||||
|
||||
public void Add(string key, object value)
|
||||
{
|
||||
SetValueForKey(key, value);
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, object> item)
|
||||
{
|
||||
SetValueForKey(item.Key, item.Value);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
((IDictionary<string, object>)_dictionary).Clear();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<string, object> item)
|
||||
{
|
||||
return ((IDictionary<string, object>)_dictionary).Contains(item);
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return ((IDictionary<string, object>)_dictionary).ContainsKey(key);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
|
||||
{
|
||||
((IDictionary<string, object>)_dictionary).CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
||||
{
|
||||
return ((IDictionary<string, object>)_dictionary).GetEnumerator();
|
||||
}
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
return ((IDictionary<string, object>)_dictionary).Remove(key);
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<string, object> item)
|
||||
{
|
||||
return ((IDictionary<string, object>)_dictionary).Remove(item);
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out object value)
|
||||
{
|
||||
return ((IDictionary<string, object>)_dictionary).TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public override bool TryGetMember(GetMemberBinder binder, out object result)
|
||||
{
|
||||
var name = binder.Name;
|
||||
|
||||
return TryGetValue(name, out result);
|
||||
}
|
||||
|
||||
public override bool TrySetMember(SetMemberBinder binder, object value)
|
||||
{
|
||||
SetValueForKey(binder.Name, value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetValueForKey(string key, object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
_dictionary.Remove(key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_dictionary.ContainsKey(key))
|
||||
{
|
||||
throw new ArgumentException($"Value for {key} already exists");
|
||||
}
|
||||
|
||||
_dictionary[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue