/// <summary> /// Method is used by Copy and Move to avoid duplicate code /// </summary> /// <param name="location">Location where value should be</param> /// <param name="objectToGetValueFrom">Object to inspect for the desired value</param> /// <param name="operationToReport">Operation to report in case of an error</param> /// <returns>GetValueResult containing value and a bool signifying a possible error</returns> private GetValueResult GetValueAtLocation( string location, object objectToGetValueFrom, Operation operationToReport) { if (location == null) { throw new ArgumentNullException(nameof(location)); } if (objectToGetValueFrom == null) { throw new ArgumentNullException(nameof(objectToGetValueFrom)); } if (operationToReport == null) { throw new ArgumentNullException(nameof(operationToReport)); } // get path result var pathResult = GetActualPropertyPath( location, objectToGetValueFrom, operationToReport); if (pathResult == null) { return(new GetValueResult(null, true)); } var getAtEndOfList = pathResult.ExecuteAtEnd; var positionAsInteger = pathResult.NumericEnd; var actualPathToProperty = pathResult.PathToProperty; var treeAnalysisResult = new ObjectTreeAnalysisResult( objectToGetValueFrom, actualPathToProperty, ContractResolver); if (treeAnalysisResult.UseDynamicLogic) { // if it's not an array, we can remove the property from // the dictionary. If it's an array, we need to check the position first. if (getAtEndOfList || positionAsInteger > -1) { var propertyValue = treeAnalysisResult.Container .GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent); // we cannot continue when the value is null, because to be able to // continue we need to be able to check if the array is a non-string array if (propertyValue == null) { LogError(new JsonPatchError( objectToGetValueFrom, operationToReport, Resources.FormatCannotDeterminePropertyType(location))); return(new GetValueResult(null, true)); } var typeOfPathProperty = propertyValue.GetType(); if (!IsNonStringArray(typeOfPathProperty)) { LogError(new JsonPatchError( objectToGetValueFrom, operationToReport, Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, location))); return(new GetValueResult(null, true)); } // get the array var array = (IList)treeAnalysisResult.Container.GetValueForCaseInsensitiveKey( treeAnalysisResult.PropertyPathInParent); if (positionAsInteger >= array.Count) { LogError(new JsonPatchError( objectToGetValueFrom, operationToReport, Resources.FormatInvalidIndexForArrayProperty( operationToReport.op, location))); return(new GetValueResult(null, true)); } if (getAtEndOfList) { return(new GetValueResult(array[array.Count - 1], false)); } else { return(new GetValueResult(array[positionAsInteger], false)); } } else { // get the property var propertyValueAtLocation = treeAnalysisResult.Container.GetValueForCaseInsensitiveKey( treeAnalysisResult.PropertyPathInParent); return(new GetValueResult(propertyValueAtLocation, false)); } } else { // not dynamic var patchProperty = treeAnalysisResult.JsonPatchProperty; if (getAtEndOfList || positionAsInteger > -1) { if (!IsNonStringArray(patchProperty.Property.PropertyType)) { LogError(new JsonPatchError( objectToGetValueFrom, operationToReport, Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, location))); return(new GetValueResult(null, true)); } if (!patchProperty.Property.Readable) { LogError(new JsonPatchError( objectToGetValueFrom, operationToReport, Resources.FormatCannotReadProperty(location))); return(new GetValueResult(null, true)); } var array = (IList)patchProperty.Property.ValueProvider .GetValue(patchProperty.Parent); if (positionAsInteger >= array.Count) { LogError(new JsonPatchError( objectToGetValueFrom, operationToReport, Resources.FormatInvalidIndexForArrayProperty( operationToReport.op, location))); return(new GetValueResult(null, true)); } if (getAtEndOfList) { return(new GetValueResult(array[array.Count - 1], false)); } else { return(new GetValueResult(array[positionAsInteger], false)); } } else { if (!patchProperty.Property.Readable) { LogError(new JsonPatchError( objectToGetValueFrom, operationToReport, Resources.FormatCannotReadProperty( location))); return(new GetValueResult(null, true)); } var propertyValueAtLocation = patchProperty.Property.ValueProvider .GetValue(patchProperty.Parent); return(new GetValueResult(propertyValueAtLocation, false)); } } }
/// <summary> /// Add is used by various operations (eg: add, copy, ...), yet through different operations; /// This method allows code reuse yet reporting the correct operation on error /// </summary> private void Add( string path, object value, object objectToApplyTo, Operation operationToReport) { if (path == null) { throw new ArgumentNullException(nameof(path)); } if (objectToApplyTo == null) { throw new ArgumentNullException(nameof(objectToApplyTo)); } if (operationToReport == null) { throw new ArgumentNullException(nameof(operationToReport)); } // 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. // get path result var pathResult = GetActualPropertyPath( path, objectToApplyTo, operationToReport); if (pathResult == null) { return; } var appendList = pathResult.ExecuteAtEnd; var positionAsInteger = pathResult.NumericEnd; var actualPathToProperty = pathResult.PathToProperty; var treeAnalysisResult = new ObjectTreeAnalysisResult( objectToApplyTo, actualPathToProperty, ContractResolver); if (!treeAnalysisResult.IsValidPathForAdd) { LogError(new JsonPatchError( objectToApplyTo, operationToReport, Resources.FormatPropertyCannotBeAdded(path))); return; } if (treeAnalysisResult.UseDynamicLogic) { var container = treeAnalysisResult.Container; if (container.ContainsCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent)) { // 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) { // get the actual type var propertyValue = container.GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent); var typeOfPathProperty = propertyValue.GetType(); 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 { // New property - add it. treeAnalysisResult.Container.Add(treeAnalysisResult.PropertyPathInParent, value); } } else { // If it's 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. var patchProperty = treeAnalysisResult.JsonPatchProperty; if (appendList || positionAsInteger > -1) { if (!IsNonStringArray(patchProperty.Property.PropertyType)) { LogError(new JsonPatchError( objectToApplyTo, operationToReport, Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path))); return; } // 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); } } }
/// <summary> /// Remove is used by various operations (eg: remove, move, ...), yet through different operations; /// This method allows code reuse yet reporting the correct operation on error. The return value /// contains the type of the item that has been removed (and a bool possibly signifying an error) /// This can be used by other methods, like replace, to ensure that we can pass in the correctly /// typed value to whatever method follows. /// </summary> private RemovedPropertyTypeResult Remove(string path, object objectToApplyTo, Operation operationToReport) { // get path result var pathResult = GetActualPropertyPath( path, objectToApplyTo, operationToReport); if (pathResult == null) { return(new RemovedPropertyTypeResult(null, true)); } var removeFromList = pathResult.ExecuteAtEnd; var positionAsInteger = pathResult.NumericEnd; var actualPathToProperty = pathResult.PathToProperty; var treeAnalysisResult = new ObjectTreeAnalysisResult( objectToApplyTo, actualPathToProperty, ContractResolver); if (!treeAnalysisResult.IsValidPathForRemove) { LogError(new JsonPatchError( objectToApplyTo, operationToReport, Resources.FormatPropertyCannotBeRemoved(path))); return(new RemovedPropertyTypeResult(null, true)); } if (treeAnalysisResult.UseDynamicLogic) { // if it's not an array, we can remove the property from // the dictionary. If it's an array, we need to check the position first. if (removeFromList || positionAsInteger > -1) { var propertyValue = treeAnalysisResult.Container .GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent); // we cannot continue when the value is null, because to be able to // continue we need to be able to check if the array is a non-string array if (propertyValue == null) { LogError(new JsonPatchError( objectToApplyTo, operationToReport, Resources.FormatCannotDeterminePropertyType(path))); return(new RemovedPropertyTypeResult(null, true)); } var typeOfPathProperty = propertyValue.GetType(); if (!IsNonStringArray(typeOfPathProperty)) { LogError(new JsonPatchError( objectToApplyTo, operationToReport, Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path))); return(new RemovedPropertyTypeResult(null, true)); } // now, get the generic type of the enumerable (we'll return this type) var genericTypeOfArray = GetIListType(typeOfPathProperty); // get the array var array = (IList)treeAnalysisResult.Container.GetValueForCaseInsensitiveKey( treeAnalysisResult.PropertyPathInParent); if (array.Count == 0) { // if the array is empty, we should throw an error LogError(new JsonPatchError( objectToApplyTo, operationToReport, Resources.FormatInvalidIndexForArrayProperty( operationToReport.op, path))); return(new RemovedPropertyTypeResult(null, true)); } if (removeFromList) { array.RemoveAt(array.Count - 1); treeAnalysisResult.Container.SetValueForCaseInsensitiveKey( treeAnalysisResult.PropertyPathInParent, array); // return the type of the value that has been removed. return(new RemovedPropertyTypeResult(genericTypeOfArray, false)); } else { if (positionAsInteger >= array.Count) { LogError(new JsonPatchError( objectToApplyTo, operationToReport, Resources.FormatInvalidIndexForArrayProperty( operationToReport.op, path))); return(new RemovedPropertyTypeResult(null, true)); } array.RemoveAt(positionAsInteger); treeAnalysisResult.Container.SetValueForCaseInsensitiveKey( treeAnalysisResult.PropertyPathInParent, array); // return the type of the value that has been removed. return(new RemovedPropertyTypeResult(genericTypeOfArray, false)); } } else { // get the property var getResult = treeAnalysisResult.Container.GetValueForCaseInsensitiveKey( treeAnalysisResult.PropertyPathInParent); // remove the property treeAnalysisResult.Container.RemoveValueForCaseInsensitiveKey( treeAnalysisResult.PropertyPathInParent); // value is not null, we can determine the type if (getResult != null) { var actualType = getResult.GetType(); return(new RemovedPropertyTypeResult(actualType, false)); } else { return(new RemovedPropertyTypeResult(null, false)); } } } else { // not dynamic var patchProperty = treeAnalysisResult.JsonPatchProperty; if (removeFromList || positionAsInteger > -1) { if (!IsNonStringArray(patchProperty.Property.PropertyType)) { LogError(new JsonPatchError( objectToApplyTo, operationToReport, Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path))); return(new RemovedPropertyTypeResult(null, true)); } // now, get the generic type of the IList<> from Property type. var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType); if (!patchProperty.Property.Readable) { LogError(new JsonPatchError( objectToApplyTo, operationToReport, Resources.FormatCannotReadProperty(path))); return(new RemovedPropertyTypeResult(null, true)); } var array = (IList)patchProperty.Property.ValueProvider .GetValue(patchProperty.Parent); if (array.Count == 0) { // if the array is empty, we should throw an error LogError(new JsonPatchError( objectToApplyTo, operationToReport, Resources.FormatInvalidIndexForArrayProperty( operationToReport.op, path))); return(new RemovedPropertyTypeResult(null, true)); } if (removeFromList) { array.RemoveAt(array.Count - 1); // return the type of the value that has been removed return(new RemovedPropertyTypeResult(genericTypeOfArray, false)); } else { if (positionAsInteger >= array.Count) { LogError(new JsonPatchError( objectToApplyTo, operationToReport, Resources.FormatInvalidIndexForArrayProperty( operationToReport.op, path))); return(new RemovedPropertyTypeResult(null, true)); } array.RemoveAt(positionAsInteger); // return the type of the value that has been removed return(new RemovedPropertyTypeResult(genericTypeOfArray, false)); } } else { if (!patchProperty.Property.Writable) { LogError(new JsonPatchError( objectToApplyTo, operationToReport, Resources.FormatCannotUpdateProperty(path))); return(new RemovedPropertyTypeResult(null, true)); } // setting the value to "null" will use the default value in case of value types, and // null in case of reference types object value = null; if (patchProperty.Property.PropertyType.GetTypeInfo().IsValueType && Nullable.GetUnderlyingType(patchProperty.Property.PropertyType) == null) { value = Activator.CreateInstance(patchProperty.Property.PropertyType); } patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent, value); return(new RemovedPropertyTypeResult(patchProperty.Property.PropertyType, false)); } } }
/// <summary> /// Add is used by various operations (eg: add, copy, ...), yet through different operations; /// This method allows code reuse yet reporting the correct operation on error /// </summary> private void Add(string path, object value, object objectToApplyTo, Operation operationToReport) { // add, in this implementation, CAN add properties if the container is an // ExpandoObject. // 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. // get path result var pathResult = PropertyHelpers.GetActualPropertyPath( path, objectToApplyTo, operationToReport, true); var appendList = pathResult.ExecuteAtEnd; var positionAsInteger = pathResult.NumericEnd; var actualPathToProperty = pathResult.PathToProperty; var result = new ObjectTreeAnalysisResult(objectToApplyTo, actualPathToProperty, ContractResolver); if (result.UseDynamicLogic) { if (result.IsValidPathForAdd) { if (result.Container.ContainsCaseInsensitiveKey(result.PropertyPathInParent)) { // 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) { // get the actual type var typeOfPathProperty = result.Container .GetValueForCaseInsensitiveKey(result.PropertyPathInParent).GetType(); if (PropertyHelpers.IsNonStringArray(typeOfPathProperty)) { // now, get the generic type of the enumerable var genericTypeOfArray = PropertyHelpers.GetIListType(typeOfPathProperty); var conversionResult = PropertyHelpers.ConvertToActualType(genericTypeOfArray, value); if (!conversionResult.CanBeConverted) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided value is invalid for array property type at location path: {0}", path)), 422); } // get value (it can be cast, we just checked that) // var array = containerDictionary[finalPath] as IList; var array = result.Container.GetValueForCaseInsensitiveKey(result.PropertyPathInParent) as IList; if (appendList) { array.Add(conversionResult.ConvertedInstance); result.Container.SetValueForCaseInsensitiveKey(result.PropertyPathInParent, array); } else { // specified index must not be greater than // the amount of items in the array if (positionAsInteger > array.Count) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)), 422); } array.Insert(positionAsInteger, conversionResult.ConvertedInstance); result.Container.SetValueForCaseInsensitiveKey( result.PropertyPathInParent, array); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array", path)), 422); } } else { // get the actual type var typeOfPathProperty = result.Container.GetValueForCaseInsensitiveKey(result.PropertyPathInParent).GetType(); // can the value be converted to the actual type? var conversionResultTuple = PropertyHelpers.ConvertToActualType(typeOfPathProperty, value); // conversion successful if (conversionResultTuple.CanBeConverted) { result.Container.SetValueForCaseInsensitiveKey(result.PropertyPathInParent, conversionResultTuple.ConvertedInstance); } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided value is invalid for property type at location path: {0}", path)), 422); } } } else { // New property - add it. result.Container.Add(result.PropertyPathInParent, value); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: cannot add to the parent of the property at location path: {0}. To be able to dynamically add properties, the parent must be an ExpandoObject.", path)), 422); } } else { if (!result.IsValidPathForAdd) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: the provided path is invalid: {0}.", path)), 422); } // 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. var patchProperty = result.JsonPatchProperty; if (appendList || positionAsInteger > -1) { if (PropertyHelpers.IsNonStringArray(patchProperty.Property.PropertyType)) { // now, get the generic type of the IList<> from Property type. var genericTypeOfArray = PropertyHelpers.GetIListType(patchProperty.Property.PropertyType); var conversionResult = PropertyHelpers.ConvertToActualType(genericTypeOfArray, value); if (!conversionResult.CanBeConverted) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided value is invalid for array property type at location path: {0}", path)), 422); } if (patchProperty.Property.Readable) { 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 { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)), 422); } } } else { // cannot read the property throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: cannot get property value at path {0}. Possible cause: the property doesn't have an accessible getter.", path)), 422); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array", path)), 422); } } else { var conversionResultTuple = PropertyHelpers.ConvertToActualType( patchProperty.Property.PropertyType, value); if (conversionResultTuple.CanBeConverted) { if (patchProperty.Property.Writable) { patchProperty.Property.ValueProvider.SetValue( patchProperty.Parent, conversionResultTuple.ConvertedInstance); } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: property at path location cannot be set: {0}. Possible causes: the property may not have an accessible setter, or the property may be part of an anonymous object (and thus cannot be changed after initialization).", path)), 422); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: property value cannot be converted to type of path location {0}.", path)), 422); } } } }
private GetValueResult GetValueAtLocation(string location, object objectToGetValueFrom, Operation operationToReport) { // get value from "objectToGetValueFrom" at location "location" object valueAtLocation = null; var pathResult = PropertyHelpers.GetActualPropertyPath( location, objectToGetValueFrom, operationToReport, false); var positionAsInteger = pathResult.NumericEnd; var actualFromProperty = pathResult.PathToProperty; // first, analyze the tree. var result = new ObjectTreeAnalysisResult(objectToGetValueFrom, actualFromProperty, ContractResolver); if (result.UseDynamicLogic) { // find the property if (result.Container.ContainsCaseInsensitiveKey(result.PropertyPathInParent)) { if (positionAsInteger > -1) { // get the actual type var typeOfPathProperty = result.Container .GetValueForCaseInsensitiveKey(result.PropertyPathInParent).GetType(); if (PropertyHelpers.IsNonStringArray(typeOfPathProperty)) { // now, get the generic type of the enumerable var genericTypeOfArray = PropertyHelpers.GetIListType(typeOfPathProperty); // get value var array = result.Container.GetValueForCaseInsensitiveKey(result.PropertyPathInParent) as IList; if (positionAsInteger >= array.Count) { throw new JsonPatchException( new JsonPatchError( objectToGetValueFrom, operationToReport, string.Format("Patch failed: property at location from: {0} does not exist", location)), 422); } valueAtLocation = array[positionAsInteger]; } else { throw new JsonPatchException( new JsonPatchError( objectToGetValueFrom, operationToReport, string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: expected array", location)), 422); } } else { // get the value valueAtLocation = result.Container.GetValueForCaseInsensitiveKey(result.PropertyPathInParent); } } else { throw new JsonPatchException( new JsonPatchError( objectToGetValueFrom, operationToReport, string.Format("Patch failed: property at location from: {0} does not exist.", location)), 422); } } else { // not dynamic. var patchProperty = result.JsonPatchProperty; // is the path an array (but not a string (= char[]))? In this case, // the path must end with "/position" or "/-", which we already determined before. if (positionAsInteger > -1) { if (PropertyHelpers.IsNonStringArray(patchProperty.Property.PropertyType)) { // now, get the generic type of the enumerable if (patchProperty.Property.Readable) { var array = (IList)patchProperty.Property.ValueProvider .GetValue(patchProperty.Parent); if (positionAsInteger >= array.Count) { throw new JsonPatchException( new JsonPatchError( objectToGetValueFrom, operationToReport, string.Format("Patch failed: property at location from: {0} does not exist", location)), 422); } valueAtLocation = array[positionAsInteger]; } else { throw new JsonPatchException( new JsonPatchError( objectToGetValueFrom, operationToReport, string.Format("Patch failed: cannot get property at location from from: {0}. Possible cause: the property doesn't have an accessible getter.", location)), 422); } } else { throw new JsonPatchException( new JsonPatchError( objectToGetValueFrom, operationToReport, string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: expected array", location)), 422); } } else { if (!patchProperty.Property.Readable) { throw new JsonPatchException( new JsonPatchError( objectToGetValueFrom, operationToReport, string.Format("Patch failed: cannot get property at location from from: {0}. Possible cause: the property doesn't have an accessible getter.", location)), 422); } valueAtLocation = patchProperty.Property.ValueProvider .GetValue(patchProperty.Parent); } } return(new GetValueResult(valueAtLocation, false)); }
/// <summary> /// Remove is used by various operations (eg: remove, move, ...), yet through different operations; /// This method allows code reuse yet reporting the correct operation on error. The return value /// contains the type of the item that has been removed (and a bool possibly signifying an error) /// This can be used by other methods, like replace, to ensure that we can pass in the correctly /// typed value to whatever method follows. /// </summary> private RemovedPropertyTypeResult Remove(string path, object objectToApplyTo, Operation operationToReport) { // remove, in this implementation, CAN remove properties if the container is an // ExpandoObject. var pathResult = PropertyHelpers.GetActualPropertyPath( path, objectToApplyTo, operationToReport, true); var removeFromList = pathResult.ExecuteAtEnd; var positionAsInteger = pathResult.NumericEnd; var actualPathToProperty = pathResult.PathToProperty; var result = new ObjectTreeAnalysisResult(objectToApplyTo, actualPathToProperty, ContractResolver); if (result.UseDynamicLogic) { if (result.IsValidPathForRemove) { // if it's not an array, we can remove the property from // the dictionary. If it's an array, we need to check the position first. if (removeFromList || positionAsInteger > -1) { var valueOfPathProperty = result.Container .GetValueForCaseInsensitiveKey(result.PropertyPathInParent); // we cannot continue when the value is null, because to be able to // continue we need to be able to check if the array is a non-string array if (valueOfPathProperty == null) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: cannot determine array property type at location path: {0}.", path)), 422); } var typeOfPathProperty = valueOfPathProperty.GetType(); if (PropertyHelpers.IsNonStringArray(typeOfPathProperty)) { // now, get the generic type of the enumerable var genericTypeOfArray = PropertyHelpers.GetIListType(typeOfPathProperty); var array = (IList)result.Container.GetValueForCaseInsensitiveKey(result.PropertyPathInParent); if (removeFromList) { if (array.Count == 0) { // if the array is empty, we should throw an error throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)), 422); } array.RemoveAt(array.Count - 1); result.Container.SetValueForCaseInsensitiveKey(result.PropertyPathInParent, array); // return the type of the value that has been removed. return(new RemovedPropertyTypeResult(genericTypeOfArray, false)); } else { if (positionAsInteger >= array.Count) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)), 422); } array.RemoveAt(positionAsInteger); result.Container.SetValueForCaseInsensitiveKey(result.PropertyPathInParent, array); // return the type of the value that has been removed. return(new RemovedPropertyTypeResult(genericTypeOfArray, false)); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array", path)), 422); } } else { // get the property var getResult = result.Container.GetValueForCaseInsensitiveKey(result.PropertyPathInParent); // remove the property result.Container.RemoveValueForCaseInsensitiveKey(result.PropertyPathInParent); // value is not null, we can determine the type if (getResult != null) { var actualType = getResult.GetType(); return(new RemovedPropertyTypeResult(actualType, false)); } else { return(new RemovedPropertyTypeResult(null, false)); } } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: cannot remove property at location path: {0}. To be able to dynamically remove properties, the parent must be an ExpandoObject.", path)), 422); } } else { // not dynamic if (!result.IsValidPathForRemove) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: the provided path is invalid: {0}.", path)), 422); } var patchProperty = result.JsonPatchProperty; if (removeFromList || positionAsInteger > -1) { if (PropertyHelpers.IsNonStringArray(patchProperty.Property.PropertyType)) { // now, get the generic type of the IList<> from Property type. var genericTypeOfArray = PropertyHelpers.GetIListType(patchProperty.Property.PropertyType); if (patchProperty.Property.Readable) { var array = (IList)patchProperty.Property.ValueProvider .GetValue(patchProperty.Parent); if (removeFromList) { if (array.Count == 0) { // if the array is empty, we should throw an error throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)), 422); } array.RemoveAt(array.Count - 1); // return the type of the value that has been removed return(new RemovedPropertyTypeResult(genericTypeOfArray, false)); } else { if (positionAsInteger >= array.Count) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)), 422); } array.RemoveAt(positionAsInteger); // return the type of the value that has been removed return(new RemovedPropertyTypeResult(genericTypeOfArray, false)); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: cannot get property value at path {0}. Possible cause: the property doesn't have an accessible getter.", path)), 422); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array.", path)), 422); } } else { if (!patchProperty.Property.Writable) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: property at path location cannot be set: {0}. Possible causes: the property may not have an accessible setter, or the property may be part of an anonymous object (and thus cannot be changed after initialization).", path)), 422); } // setting the value to "null" will use the default value in case of value types, and // null in case of reference types object value = null; if (patchProperty.Property.PropertyType.GetTypeInfo().IsValueType && Nullable.GetUnderlyingType(patchProperty.Property.PropertyType) == null) { value = Activator.CreateInstance(patchProperty.Property.PropertyType); } patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent, value); return(new RemovedPropertyTypeResult(patchProperty.Property.PropertyType, false)); } } }
/// <summary> /// Remove is used by various operations (eg: remove, move, ...), yet through different operations; /// This method allows code reuse yet reporting the correct operation on error /// </summary> private RemovedPropertyTypeResult Remove(string path, T objectToApplyTo, Operation <T> operationToReport) { // get path result var pathResult = PropertyHelpers.GetActualPropertyPath( path, objectToApplyTo, operationToReport, false); var removeFromList = pathResult.ExecuteAtEnd; var positionAsInteger = pathResult.NumericEnd; var actualPathToProperty = pathResult.PathToProperty; var expressionToUse = pathResult.ExpressionEnd; var result = new ObjectTreeAnalysisResult(objectToApplyTo, actualPathToProperty, ContractResolver); if (!result.IsValidPathForRemove) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: the provided path is invalid: {0}.", path)), 422); } var patchProperty = result.JsonPatchProperty; if (removeFromList || positionAsInteger > -1) { if (expressionToUse != null) { // we have an expression. var elementType = PropertyHelpers.GetCollectionType(patchProperty.Property.PropertyType); object coll = patchProperty.Property.ValueProvider .GetValue(patchProperty.Parent); var remMethod = typeof(ICollection <>).MakeGenericType(elementType).GetMethod("Remove"); var objToRemove = ExpressionHelpers.GetElementAtFromObjectExpression(coll, expressionToUse); var remResult = remMethod.Invoke(coll, new object[] { objToRemove }); if ((bool)remResult != true) { throw new Exception("D'oh"); } return(new RemovedPropertyTypeResult(elementType, false)); } else if (PropertyHelpers.IsNonStringList(patchProperty.Property.PropertyType)) { // now, get the generic type of the enumerable var genericTypeOfArray = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType); if (patchProperty.Property.Readable) { var array = (IList)patchProperty.Property.ValueProvider .GetValue(patchProperty.Parent); if (removeFromList) { if (array.Count == 0) { // if the array is empty, we should throw an error throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)), 422); } SetValueEventArgs eArg = new SetValueEventArgs(operationToReport) { Cancel = false, //NewValue = conversionResultTuple.ConvertedInstance, ParentObject = patchProperty.Parent, Property = patchProperty.Property, Index = array.Count - 1, }; InvokeWithBeforeSetValue(eArg, () => array.RemoveAt(eArg.Index.Value)); // return the type of the value that has been removed return(new RemovedPropertyTypeResult(genericTypeOfArray, false)); } else { if (positionAsInteger >= array.Count) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)), 422); } SetValueEventArgs eArg = new SetValueEventArgs(operationToReport) { Cancel = false, //NewValue = conversionResultTuple.ConvertedInstance, ParentObject = patchProperty.Parent, Property = patchProperty.Property, Index = positionAsInteger }; InvokeWithBeforeSetValue(eArg, () => array.RemoveAt(eArg.Index.Value)); // return the type of the value that has been removed return(new RemovedPropertyTypeResult(genericTypeOfArray, false)); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: cannot get property value at path {0}. Possible cause: the property doesn't have an accessible getter.", path)), 422); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array.", path)), 422); } } else { if (!patchProperty.Property.Writable) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: property at path location cannot be set: {0}. Possible causes: the property may not have an accessible setter, or the property may be part of an anonymous object (and thus cannot be changed after initialization).", path)), 422); } // set value to null, or for non-nullable value types, to its default value. object value = null; if (patchProperty.Property.PropertyType.GetType().IsValueType && Nullable.GetUnderlyingType(patchProperty.Property.PropertyType) == null) { value = Activator.CreateInstance(patchProperty.Property.PropertyType); } // check if it can be converted. var conversionResultTuple = PropertyHelpers.ConvertToActualType( patchProperty.Property.PropertyType, value); SetValueEventArgs eArg = new SetValueEventArgs(operationToReport) { Cancel = false, //NewValue = conversionResultTuple.ConvertedInstance, ParentObject = patchProperty.Parent, Property = patchProperty.Property, IsRemoveStepOfReplace = operationToReport.OperationType == OperationType.Replace }; if (!conversionResultTuple.CanBeConverted) { // conversion failed, so use reflection (somewhat slower) to // create a new default instance of the property type to set as value eArg.NewValue = Activator.CreateInstance(patchProperty.Property.PropertyType); //patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent, // Activator.CreateInstance(patchProperty.Property.PropertyType)); //return new RemovedPropertyTypeResult(patchProperty.Property.PropertyType, false); } else { eArg.NewValue = conversionResultTuple.ConvertedInstance; } InvokeWithBeforeSetValue(eArg, () => patchProperty.Property.ValueProvider.SetValue(eArg.ParentObject, eArg.NewValue)); return(new RemovedPropertyTypeResult(patchProperty.Property.PropertyType, false)); } }
/// <summary> /// Add is used by various operations (eg: add, copy, ...), yet through different operations; /// This method allows code reuse yet reporting the correct operation on error /// </summary> private void Add(string path, object value, T objectToApplyTo, Operation <T> 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. // get path result var pathResult = PropertyHelpers.GetActualPropertyPath( path, objectToApplyTo, operationToReport, true); var appendList = pathResult.ExecuteAtEnd; var positionAsInteger = pathResult.NumericEnd; var actualPathToProperty = pathResult.PathToProperty; var result = new ObjectTreeAnalysisResult(objectToApplyTo, actualPathToProperty, ContractResolver); if (!result.IsValidPathForAdd) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: the provided path is invalid: {0}.", path)), 422); } // 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. var patchProperty = result.JsonPatchProperty; if (appendList || positionAsInteger > -1) { if (PropertyHelpers.IsNonStringList(patchProperty.Property.PropertyType)) { // now, get the generic type of the enumerable var genericTypeOfArray = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType); var conversionResult = PropertyHelpers.ConvertToActualType(genericTypeOfArray, value); if (!conversionResult.CanBeConverted) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided value is invalid for array property type at location path: {0}", path)), 422); } if (patchProperty.Property.Readable) { var array = (IList)patchProperty.Property.ValueProvider .GetValue(patchProperty.Parent); if (array == null) { array = (IList)Activator.CreateInstance(typeof(List <>).MakeGenericType(genericTypeOfArray)); patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent, array); } if (appendList) { SetValueEventArgs eArg = new SetValueEventArgs(operationToReport) { Cancel = false, Index = array.Count + 1, NewValue = conversionResult.ConvertedInstance, ParentObject = patchProperty.Parent, Property = patchProperty.Property }; InvokeWithBeforeSetValue(eArg, () => array.Add(eArg.NewValue)); } else { // specified index must not be greater than the amount of items in the // array if (positionAsInteger <= array.Count) { SetValueEventArgs eArg = new SetValueEventArgs(operationToReport) { Cancel = false, Index = positionAsInteger, NewValue = conversionResult.ConvertedInstance, ParentObject = patchProperty.Parent, Property = patchProperty.Property }; InvokeWithBeforeSetValue(eArg, () => array.Insert(positionAsInteger, eArg.NewValue)); } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)), 422); } } } else { // cannot read the property throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: cannot get property value at path {0}. Possible cause: the property doesn't have an accessible getter.", path)), 422); } } else if (PropertyHelpers.IsNonStringCollection(patchProperty.Property.PropertyType)) { // we need to do a little work with collections here. All generic collections have an "Add" operation. In this case, we should ONLY // support adding to the end. if (!(positionAsInteger == -1 || appendList)) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: can only insert at end of array when target type is ICollection", path)), 422); } var genericTypeOfCollection = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType); ConversionResult conversionResult;// = PropertyHelpers.ConvertToActualType(genericTypeOfCollection, value); if (!genericTypeOfCollection.IsValueType && genericTypeOfCollection != typeof(string) && value != null && value.GetType() == typeof(string)) { object newObj; if (CustomDeserializationHandler != null) { newObj = CustomDeserializationHandler(value.ToString(), genericTypeOfCollection); } else { newObj = JsonConvert.DeserializeObject(value.ToString(), genericTypeOfCollection); } conversionResult = new ConversionResult(true, newObj); } else if (value != null && genericTypeOfCollection.IsAssignableFrom(value.GetType())) { conversionResult = new ConversionResult(true, value); } else { conversionResult = PropertyHelpers.ConvertToActualType( genericTypeOfCollection, value); } if (!conversionResult.CanBeConverted) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided value is invalid for array property type at location path: {0}", path)), 422); } if (patchProperty.Property.Readable) { var array = patchProperty.Property.ValueProvider .GetValue(patchProperty.Parent); if (array == null) { // the array was null. This might mean we are dealing with a fresh object. This is okay. Lets initialize it with a basic collection. array = Activator.CreateInstance(typeof(Collection <>).MakeGenericType(genericTypeOfCollection)); patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent, array); } var addMethod = patchProperty.Property.PropertyType.GetMethod("Add"); SetValueEventArgs eArg = new SetValueEventArgs(operationToReport) { Cancel = false, Index = -1, NewValue = conversionResult.ConvertedInstance, ParentObject = patchProperty.Parent, Property = patchProperty.Property }; InvokeWithBeforeSetValue(eArg, () => addMethod.Invoke(array, new object[] { eArg.NewValue })); } else { // cannot read the property throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: cannot get property value at path {0}. Possible cause: the property doesn't have an accessible getter.", path)), 422); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array", path)), 422); } } else { Type propType = patchProperty.Property.PropertyType; ConversionResult conversionResultTuple; if (!propType.IsValueType && propType != typeof(string) && value != null && value.GetType() == typeof(string)) { object newObj; if (CustomDeserializationHandler != null) { newObj = CustomDeserializationHandler(value.ToString(), propType); } else { newObj = JsonConvert.DeserializeObject(value.ToString(), propType); } conversionResultTuple = new ConversionResult(true, newObj); } else if (value != null && patchProperty.Property.PropertyType.IsAssignableFrom(value.GetType())) { conversionResultTuple = new ConversionResult(true, value); } else { conversionResultTuple = PropertyHelpers.ConvertToActualType( patchProperty.Property.PropertyType, value); } if (conversionResultTuple.CanBeConverted) { if (patchProperty.Property.Writable) { SetValueEventArgs eArg = new SetValueEventArgs(operationToReport) { Cancel = false, NewValue = conversionResultTuple.ConvertedInstance, ParentObject = patchProperty.Parent, Property = patchProperty.Property }; InvokeWithBeforeSetValue(eArg, () => patchProperty.Property.ValueProvider.SetValue( eArg.ParentObject, eArg.NewValue)); } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: property at path location cannot be set: {0}. Possible causes: the property may not have an accessible setter, or the property may be part of an anonymous object (and thus cannot be changed after initialization).", path)), 422); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: property value cannot be converted to type of path location {0}.", path)), 422); } } }
/// <summary> /// Remove is used by various operations (eg: remove, move, ...), yet through different operations; /// This method allows code reuse yet reporting the correct operation on error /// </summary> private RemovedPropertyTypeResult Remove(string path, T objectToApplyTo, Operation <T> operationToReport) { // get path result var pathResult = PropertyHelpers.GetActualPropertyPath( path, objectToApplyTo, operationToReport, false); var removeFromList = pathResult.ExecuteAtEnd; var positionAsInteger = pathResult.NumericEnd; var actualPathToProperty = pathResult.PathToProperty; var result = new ObjectTreeAnalysisResult(objectToApplyTo, actualPathToProperty, ContractResolver); if (!result.IsValidPathForRemove) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: the provided path is invalid: {0}.", path)), 422); } var patchProperty = result.JsonPatchProperty; if (removeFromList || positionAsInteger > -1) { if (PropertyHelpers.IsNonStringList(patchProperty.Property.PropertyType)) { // now, get the generic type of the enumerable var genericTypeOfArray = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType); if (patchProperty.Property.Readable) { var array = (IList)patchProperty.Property.ValueProvider .GetValue(patchProperty.Parent); if (removeFromList) { if (array.Count == 0) { // if the array is empty, we should throw an error throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)), 422); } array.RemoveAt(array.Count - 1); // return the type of the value that has been removed return(new RemovedPropertyTypeResult(genericTypeOfArray, false)); } else { if (positionAsInteger >= array.Count) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)), 422); } array.RemoveAt(positionAsInteger); // return the type of the value that has been removed return(new RemovedPropertyTypeResult(genericTypeOfArray, false)); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: cannot get property value at path {0}. Possible cause: the property doesn't have an accessible getter.", path)), 422); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array.", path)), 422); } } else { if (!patchProperty.Property.Writable) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: property at path location cannot be set: {0}. Possible causes: the property may not have an accessible setter, or the property may be part of an anonymous object (and thus cannot be changed after initialization).", path)), 422); } // set value to null, or for non-nullable value types, to its default value. object value = null; if (patchProperty.Property.PropertyType.GetType().IsValueType && Nullable.GetUnderlyingType(patchProperty.Property.PropertyType) == null) { value = Activator.CreateInstance(patchProperty.Property.PropertyType); } // check if it can be converted. var conversionResultTuple = PropertyHelpers.ConvertToActualType( patchProperty.Property.PropertyType, value); if (!conversionResultTuple.CanBeConverted) { // conversion failed, so use reflection (somewhat slower) to // create a new default instance of the property type to set as value patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent, Activator.CreateInstance(patchProperty.Property.PropertyType)); return(new RemovedPropertyTypeResult(patchProperty.Property.PropertyType, false)); } patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent, conversionResultTuple.ConvertedInstance); return(new RemovedPropertyTypeResult(patchProperty.Property.PropertyType, false)); } }
/// <summary> /// Add is used by various operations (eg: add, copy, ...), yet through different operations; /// This method allows code reuse yet reporting the correct operation on error /// </summary> private void Add(string path, object value, T objectToApplyTo, Operation <T> 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. // get path result var pathResult = PropertyHelpers.GetActualPropertyPath( path, objectToApplyTo, operationToReport, true); var appendList = pathResult.ExecuteAtEnd; var positionAsInteger = pathResult.NumericEnd; var actualPathToProperty = pathResult.PathToProperty; var result = new ObjectTreeAnalysisResult(objectToApplyTo, actualPathToProperty, ContractResolver); if (!result.IsValidPathForAdd) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: the provided path is invalid: {0}.", path)), 422); } // 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. var patchProperty = result.JsonPatchProperty; if (appendList || positionAsInteger > -1) { if (PropertyHelpers.IsNonStringList(patchProperty.Property.PropertyType)) { // now, get the generic type of the enumerable var genericTypeOfArray = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType); var conversionResult = PropertyHelpers.ConvertToActualType(genericTypeOfArray, value); if (!conversionResult.CanBeConverted) { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided value is invalid for array property type at location path: {0}", path)), 422); } if (patchProperty.Property.Readable) { 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 { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: position larger than array size", path)), 422); } } } else { // cannot read the property throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: cannot get property value at path {0}. Possible cause: the property doesn't have an accessible getter.", path)), 422); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: provided path is invalid for array property type at location path: {0}: expected array", path)), 422); } } else { var conversionResultTuple = PropertyHelpers.ConvertToActualType( patchProperty.Property.PropertyType, value); if (conversionResultTuple.CanBeConverted) { if (patchProperty.Property.Writable) { patchProperty.Property.ValueProvider.SetValue( patchProperty.Parent, conversionResultTuple.ConvertedInstance); } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: property at path location cannot be set: {0}. Possible causes: the property may not have an accessible setter, or the property may be part of an anonymous object (and thus cannot be changed after initialization).", path)), 422); } } else { throw new JsonPatchException( new JsonPatchError( objectToApplyTo, operationToReport, string.Format("Patch failed: property value cannot be converted to type of path location {0}.", path)), 422); } } }