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