/// <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)); } }