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] object objectToApplyTo,
|
||||||
[NotNull] Operation operationToReport)
|
[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
|
// 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
|
// that value represents the position; if the path ends in "-", we're appending
|
||||||
// to the list.
|
// to the list.
|
||||||
var appendList = false;
|
|
||||||
var positionAsInteger = -1;
|
|
||||||
var actualPathToProperty = path;
|
|
||||||
|
|
||||||
if (path.EndsWith("/-"))
|
// get path result
|
||||||
{
|
var pathResult = GetActualPropertyPath(
|
||||||
appendList = true;
|
path,
|
||||||
actualPathToProperty = path.Substring(0, path.Length - 2);
|
objectToApplyTo,
|
||||||
}
|
operationToReport);
|
||||||
else
|
if (pathResult == null)
|
||||||
{
|
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// it exists. If it' an array, add to that array. If it's not, we replace.
|
var appendList = pathResult.ExecuteAtEnd;
|
||||||
// is the path an array (but not a string (= char[]))? In this case,
|
var positionAsInteger = pathResult.NumericEnd;
|
||||||
// the path must end with "/position" or "/-", which we already determined before.
|
var actualPathToProperty = pathResult.PathToProperty;
|
||||||
if (appendList || positionAsInteger > -1)
|
|
||||||
|
var treeAnalysisResult = new ObjectTreeAnalysisResult(
|
||||||
|
objectToApplyTo,
|
||||||
|
actualPathToProperty,
|
||||||
|
ContractResolver);
|
||||||
|
|
||||||
|
if (!treeAnalysisResult.IsValidPathForAdd)
|
||||||
{
|
{
|
||||||
// what if it's an array but there's no position??
|
LogError(new JsonPatchError(
|
||||||
if (IsNonStringArray(patchProperty.Property.PropertyType))
|
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.
|
// Existing property.
|
||||||
var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType);
|
// If it's not an array, we need to check if the value fits the property type
|
||||||
|
//
|
||||||
var conversionResult = ConvertToActualType(genericTypeOfArray, value);
|
// 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 (!CheckIfPropertyCanBeSet(conversionResult, objectToApplyTo, operationToReport, path))
|
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)
|
if (!IsNonStringArray(typeOfPathProperty))
|
||||||
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
|
|
||||||
{
|
{
|
||||||
LogError(new JsonPatchError(
|
LogError(new JsonPatchError(
|
||||||
objectToApplyTo,
|
objectToApplyTo,
|
||||||
operationToReport,
|
operationToReport,
|
||||||
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LogError(new JsonPatchError(
|
// New property - add it.
|
||||||
objectToApplyTo,
|
treeAnalysisResult.Container.Add(treeAnalysisResult.PropertyPathInParent, value);
|
||||||
operationToReport,
|
|
||||||
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var conversionResultTuple = ConvertToActualType(
|
// If it's an array, add to that array. If it's not, we replace.
|
||||||
patchProperty.Property.PropertyType,
|
|
||||||
value);
|
|
||||||
|
|
||||||
// Is conversion successful
|
// is the path an array (but not a string (= char[]))? In this case,
|
||||||
if (!CheckIfPropertyCanBeSet(conversionResultTuple, objectToApplyTo, operationToReport, path))
|
// 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,
|
patchProperty.Parent,
|
||||||
conversionResultTuple.ConvertedInstance);
|
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
|
// if property is of IList type then get the array index from splitPath and get the
|
||||||
// object at the indexed position from the list.
|
// object at the indexed position from the list.
|
||||||
if (GetIListType(property.PropertyType) != null)
|
if (GetIListType(property.PropertyType) != null)
|
||||||
{
|
{
|
||||||
var index = int.Parse(splitPath[++i]);
|
var index = int.Parse(splitPath[++i]);
|
||||||
targetObject = ((IList)targetObject)[index];
|
targetObject = ((IList)targetObject)[index];
|
||||||
|
|
@ -722,5 +835,45 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
|
||||||
|
|
||||||
return -1;
|
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);
|
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>
|
/// <summary>
|
||||||
/// The type '{0}' was malformed and could not be parsed.
|
/// The type '{0}' was malformed and could not be parsed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -186,6 +203,23 @@ namespace Microsoft.AspNet.JsonPatch
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyDoesNotExist"), p0);
|
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>
|
/// <summary>
|
||||||
/// The test operation is not supported.
|
/// The test operation is not supported.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,9 @@
|
||||||
<data name="CannotUpdateProperty" xml:space="preserve">
|
<data name="CannotUpdateProperty" xml:space="preserve">
|
||||||
<value>The property at path '{0}' could not be updated.</value>
|
<value>The property at path '{0}' could not be updated.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DictionaryKeyNotFound" xml:space="preserve">
|
||||||
|
<value>The key '{0}' was not found.</value>
|
||||||
|
</data>
|
||||||
<data name="InvalidIndexForArrayProperty" xml:space="preserve">
|
<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>
|
<value>For operation '{0}' on array property at path '{1}', the index is larger than the array size.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
@ -138,6 +141,9 @@
|
||||||
<data name="InvalidValueForProperty" xml:space="preserve">
|
<data name="InvalidValueForProperty" xml:space="preserve">
|
||||||
<value>The value '{0}' is invalid for property at path '{1}'.</value>
|
<value>The value '{0}' is invalid for property at path '{1}'.</value>
|
||||||
</data>
|
</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">
|
<data name="ParameterMustMatchType" xml:space="preserve">
|
||||||
<value>'{0}' must be of type '{1}'.</value>
|
<value>'{0}' must be of type '{1}'.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -470,7 +470,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
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]
|
[Fact]
|
||||||
|
|
@ -497,7 +499,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
||||||
{
|
{
|
||||||
deserialized.ApplyTo(doc);
|
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]
|
[Fact]
|
||||||
|
|
@ -523,7 +527,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
||||||
|
|
||||||
|
|
||||||
//Assert
|
//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]
|
[Fact]
|
||||||
|
|
@ -1316,7 +1322,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
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]
|
[Fact]
|
||||||
|
|
@ -1340,7 +1348,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var exception = Assert.Throws<JsonPatchException>(() => { deserialized.ApplyTo(doc); });
|
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]
|
[Fact]
|
||||||
|
|
@ -1366,7 +1376,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
||||||
|
|
||||||
|
|
||||||
// Assert
|
// 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]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -312,7 +312,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
|
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]
|
[Fact]
|
||||||
|
|
@ -333,7 +335,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var exception = Assert.Throws<JsonPatchException>(() => { deserialized.ApplyTo(doc); });
|
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]
|
[Fact]
|
||||||
|
|
@ -354,7 +358,9 @@ namespace Microsoft.AspNet.JsonPatch.Test
|
||||||
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
|
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
|
||||||
|
|
||||||
// Assert
|
// 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]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
var input = "[{ \"op\": \"add\", " +
|
var input = "[{ \"op\": \"add\", " +
|
||||||
"\"path\": \"Customer/Orders/2\", " +
|
"\"path\": \"Orders/2\", " +
|
||||||
"\"value\": { \"OrderName\": \"Name2\" }}]";
|
"\"value\": { \"OrderName\": \"Name2\" }}]";
|
||||||
var request = new HttpRequestMessage
|
var request = new HttpRequestMessage
|
||||||
{
|
{
|
||||||
|
|
@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
var input = "[{ \"op\": \"replace\", " +
|
var input = "[{ \"op\": \"replace\", " +
|
||||||
"\"path\": \"Customer/Orders/0/OrderName\", " +
|
"\"path\": \"Orders/0/OrderName\", " +
|
||||||
"\"value\": \"ReplacedOrder\" }]";
|
"\"value\": \"ReplacedOrder\" }]";
|
||||||
var request = new HttpRequestMessage
|
var request = new HttpRequestMessage
|
||||||
{
|
{
|
||||||
|
|
@ -91,8 +91,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
var input = "[{ \"op\": \"copy\", " +
|
var input = "[{ \"op\": \"copy\", " +
|
||||||
"\"path\": \"Customer/Orders/1/OrderName\", " +
|
"\"path\": \"Orders/1/OrderName\", " +
|
||||||
"\"from\": \"Customer/Orders/0/OrderName\"}]";
|
"\"from\": \"Orders/0/OrderName\"}]";
|
||||||
var request = new HttpRequestMessage
|
var request = new HttpRequestMessage
|
||||||
{
|
{
|
||||||
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
|
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
|
||||||
|
|
@ -120,8 +120,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
var input = "[{ \"op\": \"move\", " +
|
var input = "[{ \"op\": \"move\", " +
|
||||||
"\"path\": \"Customer/Orders/1/OrderName\", " +
|
"\"path\": \"Orders/1/OrderName\", " +
|
||||||
"\"from\": \"Customer/Orders/0/OrderName\"}]";
|
"\"from\": \"Orders/0/OrderName\"}]";
|
||||||
var request = new HttpRequestMessage
|
var request = new HttpRequestMessage
|
||||||
{
|
{
|
||||||
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
|
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
|
||||||
|
|
@ -151,7 +151,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
var input = "[{ \"op\": \"remove\", " +
|
var input = "[{ \"op\": \"remove\", " +
|
||||||
"\"path\": \"Customer/Orders/1/OrderName\"}]";
|
"\"path\": \"Orders/1/OrderName\"}]";
|
||||||
var request = new HttpRequestMessage
|
var request = new HttpRequestMessage
|
||||||
{
|
{
|
||||||
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
|
Content = new StringContent(input, Encoding.UTF8, "application/json-patch+json"),
|
||||||
|
|
@ -179,13 +179,13 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
var input = "[{ \"op\": \"add\", "+
|
var input = "[{ \"op\": \"add\", "+
|
||||||
"\"path\": \"Customer/Orders/2\", " +
|
"\"path\": \"Orders/2\", " +
|
||||||
"\"value\": { \"OrderName\": \"Name2\" }}, " +
|
"\"value\": { \"OrderName\": \"Name2\" }}, " +
|
||||||
"{\"op\": \"copy\", " +
|
"{\"op\": \"copy\", " +
|
||||||
"\"from\": \"Customer/Orders/2\", " +
|
"\"from\": \"Orders/2\", " +
|
||||||
"\"path\": \"Customer/Orders/3\" }, " +
|
"\"path\": \"Orders/3\" }, " +
|
||||||
"{\"op\": \"replace\", " +
|
"{\"op\": \"replace\", " +
|
||||||
"\"path\": \"Customer/Orders/2/OrderName\", " +
|
"\"path\": \"Orders/2/OrderName\", " +
|
||||||
"\"value\": \"ReplacedName\" }]";
|
"\"value\": \"ReplacedName\" }]";
|
||||||
var request = new HttpRequestMessage
|
var request = new HttpRequestMessage
|
||||||
{
|
{
|
||||||
|
|
@ -213,46 +213,46 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
new object[] {
|
new object[] {
|
||||||
"http://localhost/jsonpatch/JsonPatchWithModelStateAndPrefix?prefix=Patch",
|
"http://localhost/jsonpatch/JsonPatchWithModelStateAndPrefix?prefix=Patch",
|
||||||
"[{ \"op\": \"add\", " +
|
"[{ \"op\": \"add\", " +
|
||||||
"\"path\": \"Customer/Orders/5\", " +
|
"\"path\": \"Orders/5\", " +
|
||||||
"\"value\": { \"OrderName\": \"Name5\" }}]",
|
"\"value\": { \"OrderName\": \"Name5\" }}]",
|
||||||
"{\"Patch.Customer\":[\"For operation 'add' on array property at path " +
|
"{\"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[] {
|
new object[] {
|
||||||
"http://localhost/jsonpatch/JsonPatchWithModelState",
|
"http://localhost/jsonpatch/JsonPatchWithModelState",
|
||||||
"[{ \"op\": \"add\", " +
|
"[{ \"op\": \"add\", " +
|
||||||
"\"path\": \"Customer/Orders/5\", " +
|
"\"path\": \"Orders/5\", " +
|
||||||
"\"value\": { \"OrderName\": \"Name5\" }}]",
|
"\"value\": { \"OrderName\": \"Name5\" }}]",
|
||||||
"{\"Customer\":[\"For operation 'add' on array property at path " +
|
"{\"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[] {
|
new object[] {
|
||||||
"http://localhost/jsonpatch/JsonPatchWithModelStateAndPrefix?prefix=Patch",
|
"http://localhost/jsonpatch/JsonPatchWithModelStateAndPrefix?prefix=Patch",
|
||||||
"[{ \"op\": \"add\", " +
|
"[{ \"op\": \"add\", " +
|
||||||
"\"path\": \"Customer/Orders/2\", " +
|
"\"path\": \"Orders/2\", " +
|
||||||
"\"value\": { \"OrderName\": \"Name2\" }}, " +
|
"\"value\": { \"OrderName\": \"Name2\" }}, " +
|
||||||
"{\"op\": \"copy\", " +
|
"{\"op\": \"copy\", " +
|
||||||
"\"from\": \"Customer/Orders/4\", " +
|
"\"from\": \"Orders/4\", " +
|
||||||
"\"path\": \"Customer/Orders/3\" }, " +
|
"\"path\": \"Orders/3\" }, " +
|
||||||
"{\"op\": \"replace\", " +
|
"{\"op\": \"replace\", " +
|
||||||
"\"path\": \"Customer/Orders/2/OrderName\", " +
|
"\"path\": \"Orders/2/OrderName\", " +
|
||||||
"\"value\": \"ReplacedName\" }]",
|
"\"value\": \"ReplacedName\" }]",
|
||||||
"{\"Patch.Customer\":[\"For operation 'copy' on array property at path " +
|
"{\"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[] {
|
new object[] {
|
||||||
"http://localhost/jsonpatch/JsonPatchWithModelState",
|
"http://localhost/jsonpatch/JsonPatchWithModelState",
|
||||||
"[{ \"op\": \"add\", " +
|
"[{ \"op\": \"add\", " +
|
||||||
"\"path\": \"Customer/Orders/2\", " +
|
"\"path\": \"Orders/2\", " +
|
||||||
"\"value\": { \"OrderName\": \"Name2\" }}, " +
|
"\"value\": { \"OrderName\": \"Name2\" }}, " +
|
||||||
"{\"op\": \"copy\", " +
|
"{\"op\": \"copy\", " +
|
||||||
"\"from\": \"Customer/Orders/4\", " +
|
"\"from\": \"Orders/4\", " +
|
||||||
"\"path\": \"Customer/Orders/3\" }, " +
|
"\"path\": \"Orders/3\" }, " +
|
||||||
"{\"op\": \"replace\", " +
|
"{\"op\": \"replace\", " +
|
||||||
"\"path\": \"Customer/Orders/2/OrderName\", " +
|
"\"path\": \"Orders/2/OrderName\", " +
|
||||||
"\"value\": \"ReplacedName\" }]",
|
"\"value\": \"ReplacedName\" }]",
|
||||||
"{\"Customer\":[\"For operation 'copy' on array property at path " +
|
"{\"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 client = server.CreateClient();
|
||||||
|
|
||||||
var input = "{ \"op\": \"add\", " +
|
var input = "{ \"op\": \"add\", " +
|
||||||
"\"path\": \"Customer/Orders/2\", " +
|
"\"path\": \"Orders/2\", " +
|
||||||
"\"value\": { \"OrderName\": \"Name2\" }}";
|
"\"value\": { \"OrderName\": \"Name2\" }}";
|
||||||
var request = new HttpRequestMessage
|
var request = new HttpRequestMessage
|
||||||
{
|
{
|
||||||
|
|
@ -313,7 +313,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
var input = "[{ \"op\": \"add\", " +
|
var input = "[{ \"op\": \"add\", " +
|
||||||
"\"path\": \"Customer/Orders/2\", " +
|
"\"path\": \"Orders/2\", " +
|
||||||
"\"value\": { \"OrderType\": \"Type2\" }}]";
|
"\"value\": { \"OrderType\": \"Type2\" }}]";
|
||||||
var request = new HttpRequestMessage
|
var request = new HttpRequestMessage
|
||||||
{
|
{
|
||||||
|
|
@ -339,7 +339,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
var input = "[{ \"op\": \"add\", " +
|
var input = "[{ \"op\": \"add\", " +
|
||||||
"\"path\": \"Product/ProductCategory\", " +
|
"\"path\": \"ProductCategory\", " +
|
||||||
"\"value\": { \"CategoryName\": \"Name2\" }}]";
|
"\"value\": { \"CategoryName\": \"Name2\" }}]";
|
||||||
var request = new HttpRequestMessage
|
var request = new HttpRequestMessage
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var error = Assert.Single(modelState["Customer"].Errors);
|
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]
|
[Fact]
|
||||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var error = Assert.Single(modelState["jsonpatch.Customer"].Errors);
|
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
|
public class Customer
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue