Replace of Add operation (jsonpatch dynamic support)
This commit is contained in:
parent
9d467810b5
commit
94fad918a3
|
|
@ -115,109 +115,222 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
|||
[NotNull] object objectToApplyTo,
|
||||
[NotNull] Operation operationToReport)
|
||||
{
|
||||
// add, in this implementation, does not just "add" properties - that's
|
||||
// technically impossible; It can however be used to add items to arrays,
|
||||
// or to replace values.
|
||||
|
||||
// first up: if the path ends in a numeric value, we're inserting in a list and
|
||||
// that value represents the position; if the path ends in "-", we're appending
|
||||
// to the list.
|
||||
var appendList = false;
|
||||
var positionAsInteger = -1;
|
||||
var actualPathToProperty = path;
|
||||
|
||||
if (path.EndsWith("/-"))
|
||||
{
|
||||
appendList = true;
|
||||
actualPathToProperty = path.Substring(0, path.Length - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
positionAsInteger = GetNumericEnd(path);
|
||||
|
||||
if (positionAsInteger > -1)
|
||||
{
|
||||
actualPathToProperty = path.Substring(0,
|
||||
path.LastIndexOf('/' + positionAsInteger.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
var patchProperty = FindPropertyAndParent(objectToApplyTo, actualPathToProperty);
|
||||
|
||||
// does property at path exist?
|
||||
if (!CheckIfPropertyExists(patchProperty, objectToApplyTo, operationToReport, path))
|
||||
// get path result
|
||||
var pathResult = GetActualPropertyPath(
|
||||
path,
|
||||
objectToApplyTo,
|
||||
operationToReport);
|
||||
if (pathResult == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// it exists. If it' an array, add to that array. If it's not, we replace.
|
||||
// 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 (appendList || positionAsInteger > -1)
|
||||
var appendList = pathResult.ExecuteAtEnd;
|
||||
var positionAsInteger = pathResult.NumericEnd;
|
||||
var actualPathToProperty = pathResult.PathToProperty;
|
||||
|
||||
var treeAnalysisResult = new ObjectTreeAnalysisResult(
|
||||
objectToApplyTo,
|
||||
actualPathToProperty,
|
||||
ContractResolver);
|
||||
|
||||
if (!treeAnalysisResult.IsValidPathForAdd)
|
||||
{
|
||||
// what if it's an array but there's no position??
|
||||
if (IsNonStringArray(patchProperty.Property.PropertyType))
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatPropertyCannotBeAdded(path)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (treeAnalysisResult.UseDynamicLogic)
|
||||
{
|
||||
var container = treeAnalysisResult.Container;
|
||||
if (container.ContainsCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent))
|
||||
{
|
||||
// now, get the generic type of the IList<> from Property type.
|
||||
var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType);
|
||||
|
||||
var conversionResult = ConvertToActualType(genericTypeOfArray, value);
|
||||
|
||||
if (!CheckIfPropertyCanBeSet(conversionResult, objectToApplyTo, operationToReport, path))
|
||||
// Existing property.
|
||||
// If it's not an array, we need to check if the value fits the property type
|
||||
//
|
||||
// If it's an array, we need to check if the value fits in that array type,
|
||||
// and add it at the correct position (if allowed).
|
||||
if (appendList || positionAsInteger > -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// get the actual type
|
||||
var propertyValue = container.GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent);
|
||||
var typeOfPathProperty = value.GetType();
|
||||
|
||||
// get value (it can be cast, we just checked that)
|
||||
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||
|
||||
if (appendList)
|
||||
{
|
||||
array.Add(conversionResult.ConvertedInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
// specified index must not be greater than the amount of items in the array
|
||||
if (positionAsInteger <= array.Count)
|
||||
{
|
||||
array.Insert(positionAsInteger, conversionResult.ConvertedInstance);
|
||||
}
|
||||
else
|
||||
if (!IsNonStringArray(typeOfPathProperty))
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
|
||||
return;
|
||||
}
|
||||
|
||||
// now, get the generic type of the enumerable
|
||||
var genericTypeOfArray = GetIListType(typeOfPathProperty);
|
||||
var conversionResult = ConvertToActualType(genericTypeOfArray, value);
|
||||
if (!conversionResult.CanBeConverted)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidValueForProperty(value, path)));
|
||||
return;
|
||||
}
|
||||
|
||||
// get value (it can be cast, we just checked that)
|
||||
var array = treeAnalysisResult.Container.GetValueForCaseInsensitiveKey(
|
||||
treeAnalysisResult.PropertyPathInParent) as IList;
|
||||
|
||||
if (appendList)
|
||||
{
|
||||
array.Add(conversionResult.ConvertedInstance);
|
||||
treeAnalysisResult.Container.SetValueForCaseInsensitiveKey(
|
||||
treeAnalysisResult.PropertyPathInParent, array);
|
||||
}
|
||||
else
|
||||
{
|
||||
// specified index must not be greater than
|
||||
// the amount of items in the array
|
||||
if (positionAsInteger > array.Count)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(
|
||||
operationToReport.op,
|
||||
path)));
|
||||
return;
|
||||
}
|
||||
|
||||
array.Insert(positionAsInteger, conversionResult.ConvertedInstance);
|
||||
treeAnalysisResult.Container.SetValueForCaseInsensitiveKey(
|
||||
treeAnalysisResult.PropertyPathInParent, array);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// get the actual type
|
||||
var typeOfPathProperty = treeAnalysisResult.Container
|
||||
.GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent).GetType();
|
||||
|
||||
// can the value be converted to the actual type?
|
||||
var conversionResult = ConvertToActualType(typeOfPathProperty, value);
|
||||
if (conversionResult.CanBeConverted)
|
||||
{
|
||||
treeAnalysisResult.Container.SetValueForCaseInsensitiveKey(
|
||||
treeAnalysisResult.PropertyPathInParent,
|
||||
conversionResult.ConvertedInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidValueForProperty(conversionResult.ConvertedInstance, path)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
|
||||
|
||||
return;
|
||||
// New property - add it.
|
||||
treeAnalysisResult.Container.Add(treeAnalysisResult.PropertyPathInParent, value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var conversionResultTuple = ConvertToActualType(
|
||||
patchProperty.Property.PropertyType,
|
||||
value);
|
||||
// If it's an array, add to that array. If it's not, we replace.
|
||||
|
||||
// Is conversion successful
|
||||
if (!CheckIfPropertyCanBeSet(conversionResultTuple, objectToApplyTo, operationToReport, 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.
|
||||
|
||||
var patchProperty = treeAnalysisResult.JsonPatchProperty;
|
||||
|
||||
if (appendList || positionAsInteger > -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!IsNonStringArray(patchProperty.Property.PropertyType))
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
|
||||
return;
|
||||
}
|
||||
|
||||
patchProperty.Property.ValueProvider.SetValue(
|
||||
// now, get the generic type of the IList<> from Property type.
|
||||
var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType);
|
||||
var conversionResult = ConvertToActualType(genericTypeOfArray, value);
|
||||
if (!conversionResult.CanBeConverted)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidValueForProperty(conversionResult.ConvertedInstance, path)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!patchProperty.Property.Readable)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatCannotReadProperty(path)));
|
||||
return;
|
||||
}
|
||||
|
||||
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
|
||||
if (appendList)
|
||||
{
|
||||
array.Add(conversionResult.ConvertedInstance);
|
||||
}
|
||||
else if (positionAsInteger <= array.Count)
|
||||
{
|
||||
array.Insert(positionAsInteger, conversionResult.ConvertedInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var conversionResultTuple = ConvertToActualType(
|
||||
patchProperty.Property.PropertyType,
|
||||
value);
|
||||
|
||||
if (!conversionResultTuple.CanBeConverted)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatInvalidValueForProperty(value, path)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!patchProperty.Property.Writable)
|
||||
{
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatCannotUpdateProperty(path)));
|
||||
return;
|
||||
}
|
||||
|
||||
patchProperty.Property.ValueProvider.SetValue(
|
||||
patchProperty.Parent,
|
||||
conversionResultTuple.ConvertedInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -646,7 +759,7 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
|||
|
||||
// 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)
|
||||
if (GetIListType(property.PropertyType) != null)
|
||||
{
|
||||
var index = int.Parse(splitPath[++i]);
|
||||
targetObject = ((IList)targetObject)[index];
|
||||
|
|
@ -722,5 +835,45 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
|||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
private ActualPropertyPathResult GetActualPropertyPath(
|
||||
[NotNull] string propertyPath,
|
||||
[NotNull] object objectToApplyTo,
|
||||
[NotNull] Operation operationToReport)
|
||||
{
|
||||
if (propertyPath.EndsWith("/-"))
|
||||
{
|
||||
return new ActualPropertyPathResult(-1, propertyPath.Substring(0, propertyPath.Length - 2), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var possibleIndex = propertyPath.Substring(propertyPath.LastIndexOf("/") + 1);
|
||||
int castedIndex = -1;
|
||||
if (int.TryParse(possibleIndex, out castedIndex))
|
||||
{
|
||||
// has numeric end.
|
||||
if (castedIndex > -1)
|
||||
{
|
||||
var pathToProperty = propertyPath.Substring(
|
||||
0,
|
||||
propertyPath.LastIndexOf('/' + castedIndex.ToString()));
|
||||
|
||||
return new ActualPropertyPathResult(castedIndex, pathToProperty, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// negative position - invalid path
|
||||
LogError(new JsonPatchError(
|
||||
objectToApplyTo,
|
||||
operationToReport,
|
||||
Resources.FormatNegativeIndexForArrayProperty(operationToReport.op, propertyPath)));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new ActualPropertyPathResult(-1, propertyPath, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// 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
|
||||
{
|
||||
internal class ActualPropertyPathResult
|
||||
{
|
||||
public int NumericEnd { get; private set; }
|
||||
public string PathToProperty { get; set; }
|
||||
public bool ExecuteAtEnd { get; set; }
|
||||
|
||||
public ActualPropertyPathResult(
|
||||
int numericEnd,
|
||||
string pathToProperty,
|
||||
bool executeAtEnd)
|
||||
{
|
||||
NumericEnd = numericEnd;
|
||||
PathToProperty = pathToProperty;
|
||||
ExecuteAtEnd = executeAtEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Helpers
|
||||
{
|
||||
// Helper methods to allow case-insensitive key search
|
||||
internal static class ExpandoObjectDictionaryExtensions
|
||||
{
|
||||
internal static void SetValueForCaseInsensitiveKey(this IDictionary<string, object> propertyDictionary,
|
||||
string key, object value)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> kvp in propertyDictionary)
|
||||
{
|
||||
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
propertyDictionary[kvp.Key] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RemoveValueForCaseInsensitiveKey(this IDictionary<string, object> propertyDictionary,
|
||||
string key)
|
||||
{
|
||||
string realKey = null;
|
||||
foreach (KeyValuePair<string, object> kvp in propertyDictionary)
|
||||
{
|
||||
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
realKey = kvp.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (realKey != null)
|
||||
{
|
||||
propertyDictionary.Remove(realKey);
|
||||
}
|
||||
}
|
||||
|
||||
internal static object GetValueForCaseInsensitiveKey(this IDictionary<string, object> propertyDictionary,
|
||||
string key)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> kvp in propertyDictionary)
|
||||
{
|
||||
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentException(Resources.FormatDictionaryKeyNotFound(key));
|
||||
}
|
||||
|
||||
internal static bool ContainsCaseInsensitiveKey(this IDictionary<string, object> propertyDictionary,
|
||||
string key)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> kvp in propertyDictionary)
|
||||
{
|
||||
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
// 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 System.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNet.JsonPatch.Helpers
|
||||
{
|
||||
internal class ObjectTreeAnalysisResult
|
||||
{
|
||||
// either the property is part of the container dictionary,
|
||||
// or we have a direct reference to a JsonPatchProperty instance
|
||||
|
||||
public bool UseDynamicLogic { get; private set; }
|
||||
|
||||
public bool IsValidPathForAdd { get; private set; }
|
||||
|
||||
public bool IsValidPathForRemove { get; private set; }
|
||||
|
||||
public IDictionary<string, object> Container { get; private set; }
|
||||
|
||||
public string PropertyPathInParent { get; private set; }
|
||||
|
||||
public JsonPatchProperty JsonPatchProperty { get; private set; }
|
||||
|
||||
public ObjectTreeAnalysisResult(
|
||||
object objectToSearch,
|
||||
string propertyPath,
|
||||
IContractResolver contractResolver)
|
||||
{
|
||||
// construct the analysis result.
|
||||
|
||||
// split the propertypath, and if necessary, remove the first
|
||||
// empty item (that's the case when it starts with a "/")
|
||||
var propertyPathTree = propertyPath.Split(
|
||||
new char[] { '/' },
|
||||
StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// we've now got a split up property tree "base/property/otherproperty/..."
|
||||
int lastPosition = 0;
|
||||
object targetObject = objectToSearch;
|
||||
for (int i = 0; i < propertyPathTree.Length; i++)
|
||||
{
|
||||
lastPosition = i;
|
||||
|
||||
// if the current target object is an ExpandoObject (IDictionary<string, object>),
|
||||
// we cannot use the ContractResolver.
|
||||
var dictionary = targetObject as IDictionary<string, object>;
|
||||
if (dictionary != null)
|
||||
{
|
||||
// find the value in the dictionary
|
||||
if (dictionary.ContainsCaseInsensitiveKey(propertyPathTree[i]))
|
||||
{
|
||||
var possibleNewTargetObject = dictionary.GetValueForCaseInsensitiveKey(propertyPathTree[i]);
|
||||
|
||||
// unless we're at the last item, we should set the targetobject
|
||||
// to the new object. If we're at the last item, we need to stop
|
||||
if (i != propertyPathTree.Length - 1)
|
||||
{
|
||||
targetObject = possibleNewTargetObject;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if the current part of the path is numeric, this means we're trying
|
||||
// to get the propertyInfo of a specific object in an array. To allow
|
||||
// for this, the previous value (targetObject) must be an IEnumerable, and
|
||||
// the position must exist.
|
||||
|
||||
int numericValue = -1;
|
||||
if (int.TryParse(propertyPathTree[i], out numericValue))
|
||||
{
|
||||
var element = GetElementAtFromObject(targetObject, numericValue);
|
||||
if (element != null)
|
||||
{
|
||||
targetObject = element;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(targetObject.GetType());
|
||||
|
||||
// does the property exist?
|
||||
var attemptedProperty = jsonContract
|
||||
.Properties
|
||||
.FirstOrDefault(p => string.Equals(p.PropertyName, propertyPathTree[i], StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (attemptedProperty != null)
|
||||
{
|
||||
// unless we're at the last item, we should continue searching.
|
||||
// If we're at the last item, we need to stop
|
||||
if ((i != propertyPathTree.Length - 1))
|
||||
{
|
||||
targetObject = attemptedProperty.ValueProvider.GetValue(targetObject);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// property cannot be found, and we're not working with dynamics.
|
||||
// Stop, and return invalid path.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (propertyPathTree.Length - lastPosition != 1)
|
||||
{
|
||||
IsValidPathForAdd = false;
|
||||
IsValidPathForRemove = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// two things can happen now. The targetproperty can be an IDictionary - in that
|
||||
// case, it's valid for add if there's 1 item left in the propertyPathTree.
|
||||
//
|
||||
// it can also be a property info. In that case, if there's nothing left in the path
|
||||
// tree we're at the end, if there's one left we can try and set that.
|
||||
if (targetObject is IDictionary<string, object>)
|
||||
{
|
||||
UseDynamicLogic = true;
|
||||
|
||||
Container = (IDictionary<string, object>)targetObject;
|
||||
IsValidPathForAdd = true;
|
||||
PropertyPathInParent = propertyPathTree[propertyPathTree.Length - 1];
|
||||
|
||||
// to be able to remove this property, it must exist
|
||||
IsValidPathForRemove = Container.ContainsCaseInsensitiveKey(PropertyPathInParent);
|
||||
}
|
||||
else if (targetObject is IList)
|
||||
{
|
||||
System.Diagnostics.Debugger.Launch();
|
||||
UseDynamicLogic = false;
|
||||
|
||||
int index;
|
||||
if (!Int32.TryParse(propertyPathTree[propertyPathTree.Length - 1], out index))
|
||||
{
|
||||
// We only support indexing into a list
|
||||
IsValidPathForAdd = false;
|
||||
IsValidPathForRemove = false;
|
||||
return;
|
||||
}
|
||||
|
||||
IsValidPathForAdd = true;
|
||||
IsValidPathForRemove = ((IList)targetObject).Count > index;
|
||||
PropertyPathInParent = propertyPathTree[propertyPathTree.Length - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
UseDynamicLogic = false;
|
||||
|
||||
var property = propertyPathTree[propertyPathTree.Length - 1];
|
||||
var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(targetObject.GetType());
|
||||
var attemptedProperty = jsonContract
|
||||
.Properties
|
||||
.FirstOrDefault(p => string.Equals(p.PropertyName, property, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (attemptedProperty == null)
|
||||
{
|
||||
IsValidPathForAdd = false;
|
||||
IsValidPathForRemove = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsValidPathForAdd = true;
|
||||
IsValidPathForRemove = true;
|
||||
JsonPatchProperty = new JsonPatchProperty(attemptedProperty, targetObject);
|
||||
PropertyPathInParent = property;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object GetElementAtFromObject(object targetObject, int numericValue)
|
||||
{
|
||||
if (numericValue > -1)
|
||||
{
|
||||
// Check if the targetobject is an IEnumerable,
|
||||
// and if the position is valid.
|
||||
if (targetObject is IEnumerable)
|
||||
{
|
||||
var indexable = ((IEnumerable)targetObject).Cast<object>();
|
||||
|
||||
if (indexable.Count() >= numericValue)
|
||||
{
|
||||
return indexable.ElementAt(numericValue);
|
||||
}
|
||||
else { return null; }
|
||||
}
|
||||
else { return null; }
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,23 @@ namespace Microsoft.AspNet.JsonPatch
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidIndexForArrayProperty"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For operation '{0}' on array property at path '{1}', the index is negative.
|
||||
/// </summary>
|
||||
internal static string NegativeIndexForArrayProperty
|
||||
{
|
||||
get { return GetString("NegativeIndexForArrayProperty"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For operation '{0}' on array property at path '{1}', the index is negative.
|
||||
/// </summary>
|
||||
internal static string FormatNegativeIndexForArrayProperty(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("NegativeIndexForArrayProperty"), p0, p1);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The type '{0}' was malformed and could not be parsed.
|
||||
/// </summary>
|
||||
|
|
@ -186,6 +203,23 @@ namespace Microsoft.AspNet.JsonPatch
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyDoesNotExist"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The key '{0}' was not found.
|
||||
/// </summary>
|
||||
internal static string DictionaryKeyNotFound
|
||||
{
|
||||
get { return GetString("DictionaryKeyNotFound"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The key '{0}' was not found.
|
||||
/// </summary>
|
||||
internal static string FormatDictionaryKeyNotFound(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("DictionaryKeyNotFound"), p0);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The test operation is not supported.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -123,6 +123,9 @@
|
|||
<data name="CannotUpdateProperty" xml:space="preserve">
|
||||
<value>The property at path '{0}' could not be updated.</value>
|
||||
</data>
|
||||
<data name="DictionaryKeyNotFound" xml:space="preserve">
|
||||
<value>The key '{0}' was not found.</value>
|
||||
</data>
|
||||
<data name="InvalidIndexForArrayProperty" xml:space="preserve">
|
||||
<value>For operation '{0}' on array property at path '{1}', the index is larger than the array size.</value>
|
||||
</data>
|
||||
|
|
@ -138,6 +141,9 @@
|
|||
<data name="InvalidValueForProperty" xml:space="preserve">
|
||||
<value>The value '{0}' is invalid for property at path '{1}'.</value>
|
||||
</data>
|
||||
<data name="NegativeIndexForArrayProperty" xml:space="preserve">
|
||||
<value>For operation '{0}' on array property at path '{1}', the index is negative.</value>
|
||||
</data>
|
||||
<data name="ParameterMustMatchType" xml:space="preserve">
|
||||
<value>'{0}' must be of type '{1}'.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -470,7 +470,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
|||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
||||
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message);
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/simpledto/integerlist/-1', the index is negative.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -497,7 +499,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
|||
{
|
||||
deserialized.ApplyTo(doc);
|
||||
});
|
||||
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message);
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/simpledto/integerlist/-1', the index is negative.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -523,7 +527,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
|||
|
||||
|
||||
//Assert
|
||||
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", logger.ErrorMessage);
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/simpledto/integerlist/-1', the index is negative.",
|
||||
logger.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1316,7 +1322,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
|||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
||||
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message);
|
||||
Assert.Equal(
|
||||
"Property does not exist at path '/simpledto/integerlist/-1'.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1340,7 +1348,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
|||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { deserialized.ApplyTo(doc); });
|
||||
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message);
|
||||
Assert.Equal(
|
||||
"Property does not exist at path '/simpledto/integerlist/-1'.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1366,7 +1376,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
|||
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", logger.ErrorMessage);
|
||||
Assert.Equal(
|
||||
"For operation 'replace' on array property at path '/simpledto/integerlist/-1', the index is negative.",
|
||||
logger.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -312,7 +312,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
|||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
||||
Assert.Equal("Property does not exist at path '/integerlist/-1'.", exception.Message);
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/integerlist/-1', the index is negative.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -333,7 +335,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
|||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { deserialized.ApplyTo(doc); });
|
||||
Assert.Equal("Property does not exist at path '/integerlist/-1'.", exception.Message);
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/integerlist/-1', the index is negative.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -354,7 +358,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
|||
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Property does not exist at path '/integerlist/-1'.", logger.ErrorMessage);
|
||||
Assert.Equal(
|
||||
"For operation 'add' on array property at path '/integerlist/-1', the index is negative.",
|
||||
logger.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var client = server.CreateClient();
|
||||
|
||||
var input = "[{ \"op\": \"add\", " +
|
||||
"\"path\": \"Customer/Orders/2\", " +
|
||||
"\"path\": \"Orders/2\", " +
|
||||
"\"value\": { \"OrderName\": \"Name2\" }}]";
|
||||
var request = new HttpRequestMessage
|
||||
{
|
||||
|
|
@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var client = server.CreateClient();
|
||||
|
||||
var input = "[{ \"op\": \"replace\", " +
|
||||
"\"path\": \"Customer/Orders/0/OrderName\", " +
|
||||
"\"path\": \"Orders/0/OrderName\", " +
|
||||
"\"value\": \"ReplacedOrder\" }]";
|
||||
var request = new HttpRequestMessage
|
||||
{
|
||||
|
|
@ -91,8 +91,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var client = server.CreateClient();
|
||||
|
||||
var input = "[{ \"op\": \"copy\", " +
|
||||
"\"path\": \"Customer/Orders/1/OrderName\", " +
|
||||
"\"from\": \"Customer/Orders/0/OrderName\"}]";
|
||||
"\"path\": \"Orders/1/OrderName\", " +
|
||||
"\"from\": \"Orders/0/OrderName\"}]";
|
||||
var request = new HttpRequestMessage
|
||||
{
|
||||
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
|
||||
|
|
@ -120,8 +120,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var client = server.CreateClient();
|
||||
|
||||
var input = "[{ \"op\": \"move\", " +
|
||||
"\"path\": \"Customer/Orders/1/OrderName\", " +
|
||||
"\"from\": \"Customer/Orders/0/OrderName\"}]";
|
||||
"\"path\": \"Orders/1/OrderName\", " +
|
||||
"\"from\": \"Orders/0/OrderName\"}]";
|
||||
var request = new HttpRequestMessage
|
||||
{
|
||||
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
|
||||
|
|
@ -151,7 +151,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var client = server.CreateClient();
|
||||
|
||||
var input = "[{ \"op\": \"remove\", " +
|
||||
"\"path\": \"Customer/Orders/1/OrderName\"}]";
|
||||
"\"path\": \"Orders/1/OrderName\"}]";
|
||||
var request = new HttpRequestMessage
|
||||
{
|
||||
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
|
||||
|
|
@ -179,13 +179,13 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var client = server.CreateClient();
|
||||
|
||||
var input = "[{ \"op\": \"add\", "+
|
||||
"\"path\": \"Customer/Orders/2\", " +
|
||||
"\"path\": \"Orders/2\", " +
|
||||
"\"value\": { \"OrderName\": \"Name2\" }}, " +
|
||||
"{\"op\": \"copy\", " +
|
||||
"\"from\": \"Customer/Orders/2\", " +
|
||||
"\"path\": \"Customer/Orders/3\" }, " +
|
||||
"\"from\": \"Orders/2\", " +
|
||||
"\"path\": \"Orders/3\" }, " +
|
||||
"{\"op\": \"replace\", " +
|
||||
"\"path\": \"Customer/Orders/2/OrderName\", " +
|
||||
"\"path\": \"Orders/2/OrderName\", " +
|
||||
"\"value\": \"ReplacedName\" }]";
|
||||
var request = new HttpRequestMessage
|
||||
{
|
||||
|
|
@ -213,46 +213,46 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
new object[] {
|
||||
"http://localhost/jsonpatch/JsonPatchWithModelStateAndPrefix?prefix=Patch",
|
||||
"[{ \"op\": \"add\", " +
|
||||
"\"path\": \"Customer/Orders/5\", " +
|
||||
"\"path\": \"Orders/5\", " +
|
||||
"\"value\": { \"OrderName\": \"Name5\" }}]",
|
||||
"{\"Patch.Customer\":[\"For operation 'add' on array property at path " +
|
||||
"'Customer/Orders/5', the index is larger than the array size.\"]}"
|
||||
"'Orders/5', the index is larger than the array size.\"]}"
|
||||
},
|
||||
new object[] {
|
||||
"http://localhost/jsonpatch/JsonPatchWithModelState",
|
||||
"[{ \"op\": \"add\", " +
|
||||
"\"path\": \"Customer/Orders/5\", " +
|
||||
"\"path\": \"Orders/5\", " +
|
||||
"\"value\": { \"OrderName\": \"Name5\" }}]",
|
||||
"{\"Customer\":[\"For operation 'add' on array property at path " +
|
||||
"'Customer/Orders/5', the index is larger than the array size.\"]}"
|
||||
"'Orders/5', the index is larger than the array size.\"]}"
|
||||
},
|
||||
new object[] {
|
||||
"http://localhost/jsonpatch/JsonPatchWithModelStateAndPrefix?prefix=Patch",
|
||||
"[{ \"op\": \"add\", " +
|
||||
"\"path\": \"Customer/Orders/2\", " +
|
||||
"\"path\": \"Orders/2\", " +
|
||||
"\"value\": { \"OrderName\": \"Name2\" }}, " +
|
||||
"{\"op\": \"copy\", " +
|
||||
"\"from\": \"Customer/Orders/4\", " +
|
||||
"\"path\": \"Customer/Orders/3\" }, " +
|
||||
"\"from\": \"Orders/4\", " +
|
||||
"\"path\": \"Orders/3\" }, " +
|
||||
"{\"op\": \"replace\", " +
|
||||
"\"path\": \"Customer/Orders/2/OrderName\", " +
|
||||
"\"path\": \"Orders/2/OrderName\", " +
|
||||
"\"value\": \"ReplacedName\" }]",
|
||||
"{\"Patch.Customer\":[\"For operation 'copy' on array property at path " +
|
||||
"'Customer/Orders/4', the index is larger than the array size.\"]}"
|
||||
"'Orders/4', the index is larger than the array size.\"]}"
|
||||
},
|
||||
new object[] {
|
||||
"http://localhost/jsonpatch/JsonPatchWithModelState",
|
||||
"[{ \"op\": \"add\", " +
|
||||
"\"path\": \"Customer/Orders/2\", " +
|
||||
"\"path\": \"Orders/2\", " +
|
||||
"\"value\": { \"OrderName\": \"Name2\" }}, " +
|
||||
"{\"op\": \"copy\", " +
|
||||
"\"from\": \"Customer/Orders/4\", " +
|
||||
"\"path\": \"Customer/Orders/3\" }, " +
|
||||
"\"from\": \"Orders/4\", " +
|
||||
"\"path\": \"Orders/3\" }, " +
|
||||
"{\"op\": \"replace\", " +
|
||||
"\"path\": \"Customer/Orders/2/OrderName\", " +
|
||||
"\"path\": \"Orders/2/OrderName\", " +
|
||||
"\"value\": \"ReplacedName\" }]",
|
||||
"{\"Customer\":[\"For operation 'copy' on array property at path " +
|
||||
"'Customer/Orders/4', the index is larger than the array size.\"]}"
|
||||
"'Orders/4', the index is larger than the array size.\"]}"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -288,7 +288,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var client = server.CreateClient();
|
||||
|
||||
var input = "{ \"op\": \"add\", " +
|
||||
"\"path\": \"Customer/Orders/2\", " +
|
||||
"\"path\": \"Orders/2\", " +
|
||||
"\"value\": { \"OrderName\": \"Name2\" }}";
|
||||
var request = new HttpRequestMessage
|
||||
{
|
||||
|
|
@ -313,7 +313,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var client = server.CreateClient();
|
||||
|
||||
var input = "[{ \"op\": \"add\", " +
|
||||
"\"path\": \"Customer/Orders/2\", " +
|
||||
"\"path\": \"Orders/2\", " +
|
||||
"\"value\": { \"OrderType\": \"Type2\" }}]";
|
||||
var request = new HttpRequestMessage
|
||||
{
|
||||
|
|
@ -339,7 +339,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var client = server.CreateClient();
|
||||
|
||||
var input = "[{ \"op\": \"add\", " +
|
||||
"\"path\": \"Product/ProductCategory\", " +
|
||||
"\"path\": \"ProductCategory\", " +
|
||||
"\"value\": { \"CategoryName\": \"Name2\" }}]";
|
||||
var request = new HttpRequestMessage
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
// Assert
|
||||
var error = Assert.Single(modelState["Customer"].Errors);
|
||||
Assert.Equal("Property does not exist at path 'Customer/CustomerId'.", error.ErrorMessage);
|
||||
Assert.Equal("The property at path 'Customer/CustomerId' could not be added.", error.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
// Assert
|
||||
var error = Assert.Single(modelState["jsonpatch.Customer"].Errors);
|
||||
Assert.Equal("Property does not exist at path 'Customer/CustomerId'.", error.ErrorMessage);
|
||||
Assert.Equal("The property at path 'Customer/CustomerId' could not be added.", error.ErrorMessage);
|
||||
}
|
||||
|
||||
public class Customer
|
||||
|
|
|
|||
Loading…
Reference in New Issue