Example #1
0
        /// <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);
                }
            }
        }
Example #2
0
        /// <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));
            }
        }
Example #3
0
        /// <summary>
        ///  The "copy" operation copies the value at a specified location to the
        ///  target location.
        ///
        ///  The operation object MUST contain a "from" member, which is a string
        ///  containing a JSON Pointer value that references the location in the
        ///  target document to copy the value from.
        ///
        ///  The "from" location MUST exist for the operation to be successful.
        ///
        ///  For example:
        ///
        ///  { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
        ///
        ///  This operation is functionally identical to an "add" operation at the
        ///  target location using the value specified in the "from" member.
        ///
        ///  Note: even though it's the same functionally, we do not call add with
        ///  the value specified in from for performance reasons (multiple checks of same requirements).
        /// </summary>
        /// <param name="operation">The copy operation</param>
        /// <param name="objectApplyTo">Object to apply the operation to</param>
        public void Copy(Operation <T> operation, T objectToApplyTo)
        {
            // get value at from location
            object valueAtFromLocation = null;
            var    positionAsInteger   = -1;
            var    actualFromProperty  = operation.from;

            positionAsInteger = PropertyHelpers.GetNumericEnd(operation.from);

            if (positionAsInteger > -1)
            {
                actualFromProperty = operation.from.Substring(0,
                                                              operation.from.IndexOf('/' + positionAsInteger.ToString()));
            }

            var patchProperty = PropertyHelpers
                                .FindPropertyAndParent(objectToApplyTo, actualFromProperty, ContractResolver);

            // does property at from exist?
            CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.from);

            // get the property path
            // is the path an array (but not a string (= char[]))?  In this case,
            // the path must end with "/position" or "/-", which we already determined before.
            if (positionAsInteger > -1)
            {
                if (IsNonStringArray(patchProperty))
                {
                    // now, get the generic type of the enumerable
                    var genericTypeOfArray = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType);

                    // get value (it can be cast, we just checked that)
                    var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);

                    if (array.Count <= positionAsInteger)
                    {
                        throw new JsonPatchException <T>(operation,
                                                         string.Format("Patch failed: provided from path is invalid for array property type at " +
                                                                       "location from: {0}: invalid position",
                                                                       operation.from),
                                                         objectToApplyTo, 422);
                    }

                    valueAtFromLocation = array[positionAsInteger];
                }
                else
                {
                    throw new JsonPatchException <T>(operation,
                                                     string.Format("Patch failed: provided from path is invalid for array property type at " +
                                                                   "location from: {0}: expected array",
                                                                   operation.from),
                                                     objectToApplyTo, 422);
                }
            }
            else
            {
                // no list, just get the value
                // set the new value
                valueAtFromLocation = patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
            }

            // add operation to target location with that value.
            Add(operation.path, valueAtFromLocation, objectToApplyTo, operation);
        }
Example #4
0
        /// <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.
            var appendList           = false;
            var positionAsInteger    = -1;
            var actualPathToProperty = path;

            if (path.EndsWith("/-"))
            {
                appendList           = true;
                actualPathToProperty = path.Substring(0, path.Length - 2);
            }
            else
            {
                positionAsInteger = PropertyHelpers.GetNumericEnd(path);

                if (positionAsInteger > -1)
                {
                    actualPathToProperty = path.Substring(0,
                                                          path.IndexOf('/' + positionAsInteger.ToString()));
                }
            }

            var patchProperty = PropertyHelpers
                                .FindPropertyAndParent(objectToApplyTo, actualPathToProperty, ContractResolver);

            // does property at path exist?
            CheckIfPropertyExists(patchProperty, objectToApplyTo, operationToReport, path);

            // it exists.  If it' an array, add to that array.  If it's not, we replace.
            // is the path an array (but not a string (= char[]))?  In this case,
            // the path must end with "/position" or "/-", which we already determined before.
            if (appendList || positionAsInteger > -1)
            {
                // what if it's an array but there's no position??
                if (IsNonStringArray(patchProperty))
                {
                    // now, get the generic type of the enumerable
                    var genericTypeOfArray = PropertyHelpers.GetEnumerableType(
                        patchProperty.Property.PropertyType);

                    var conversionResult = PropertyHelpers.ConvertToActualType(genericTypeOfArray, value);

                    CheckIfPropertyCanBeSet(conversionResult, objectToApplyTo, operationToReport, path);

                    // get value (it can be cast, we just checked that)
                    var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);

                    if (appendList)
                    {
                        array.Add(conversionResult.ConvertedInstance);
                    }
                    else
                    {
                        // specified index must not be greater than the amount of items in the array
                        if (positionAsInteger <= array.Count)
                        {
                            array.Insert(positionAsInteger, conversionResult.ConvertedInstance);
                        }
                        else
                        {
                            throw new JsonPatchException <T>(operationToReport,
                                                             string.Format("Patch failed: provided path is invalid for array property type at " +
                                                                           "location path: {0}: position larger than array size",
                                                                           path, 422),
                                                             objectToApplyTo);
                        }
                    }
                }
                else
                {
                    throw new JsonPatchException <T>(operationToReport,
                                                     string.Format("Patch failed: provided path is invalid for array property type at location " +
                                                                   "path: {0}: expected array",
                                                                   path),
                                                     objectToApplyTo, 422);
                }
            }
            else
            {
                var conversionResultTuple = PropertyHelpers.ConvertToActualType(
                    patchProperty.Property.PropertyType,
                    value);

                // Is conversion successful
                CheckIfPropertyCanBeSet(conversionResultTuple, objectToApplyTo, operationToReport, path);

                patchProperty.Property.ValueProvider.SetValue(
                    patchProperty.Parent,
                    conversionResultTuple.ConvertedInstance);
            }
        }
Example #5
0
        /// <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 void Remove(string path, T objectToApplyTo, Operation <T> operationToReport)
        {
            var removeFromList       = false;
            var positionAsInteger    = -1;
            var actualPathToProperty = path;

            if (path.EndsWith("/-"))
            {
                removeFromList       = true;
                actualPathToProperty = path.Substring(0, path.Length - 2);
            }
            else
            {
                positionAsInteger = PropertyHelpers.GetNumericEnd(path);

                if (positionAsInteger > -1)
                {
                    actualPathToProperty = path.Substring(0,
                                                          path.IndexOf('/' + positionAsInteger.ToString()));
                }
            }

            var patchProperty = PropertyHelpers
                                .FindPropertyAndParent(objectToApplyTo, actualPathToProperty, ContractResolver);

            // does the target location exist?
            CheckIfPropertyExists(patchProperty, objectToApplyTo, operationToReport, path);

            // get the property, and remove it - in this case, for DTO's, that means setting
            // it to null or its default value; in case of an array, remove at provided index
            // or at the end.
            if (removeFromList || positionAsInteger > -1)
            {
                // what if it's an array but there's no position??
                if (IsNonStringArray(patchProperty))
                {
                    // now, get the generic type of the enumerable
                    var genericTypeOfArray = PropertyHelpers.GetEnumerableType(patchProperty.Property.PropertyType);

                    // get value (it can be cast, we just checked that)
                    var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);

                    if (removeFromList)
                    {
                        array.RemoveAt(array.Count - 1);
                    }
                    else
                    {
                        if (positionAsInteger < array.Count)
                        {
                            array.RemoveAt(positionAsInteger);
                        }
                        else
                        {
                            throw new JsonPatchException <T>(operationToReport,
                                                             string.Format("Patch failed: provided path is invalid for array property type at " +
                                                                           "location path: {0}: position larger than array size",
                                                                           path),
                                                             objectToApplyTo, 422);
                        }
                    }
                }
                else
                {
                    throw new JsonPatchException <T>(operationToReport,
                                                     string.Format("Patch failed: provided path is invalid for array property type at " +
                                                                   "location path: {0}: expected array",
                                                                   path),
                                                     objectToApplyTo, 422);
                }
            }
            else
            {
                // 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);
            }
        }
Example #6
0
        /// <summary>
        /// The "test" operation tests that a value at the target location is
        /// equal to a specified value.
        ///
        /// The operation object MUST contain a "value" member that conveys the
        /// value to be compared to the target location's value.
        ///
        /// The target location MUST be equal to the "value" value for the
        /// operation to be considered successful.
        ///
        /// Here, "equal" means that the value at the target location and the
        /// value conveyed by "value" are of the same JSON type, and that they
        /// are considered equal by the following rules for that type:
        ///
        /// o  strings: are considered equal if they contain the same number of
        ///    Unicode characters and their code points are byte-by-byte equal.
        ///
        /// o  numbers: are considered equal if their values are numerically
        ///    equal.
        ///
        /// o  arrays: are considered equal if they contain the same number of
        ///    values, and if each value can be considered equal to the value at
        ///    the corresponding position in the other array, using this list of
        ///    type-specific rules.
        ///
        /// o  objects: are considered equal if they contain the same number of
        ///    members, and if each member can be considered equal to a member in
        ///    the other object, by comparing their keys (as strings) and their
        ///    values (using this list of type-specific rules).
        ///
        /// o  literals (false, true, and null): are considered equal if they are
        ///    the same.
        ///
        /// Note that the comparison that is done is a logical comparison; e.g.,
        /// whitespace between the member values of an array is not significant.
        ///
        /// Also, note that ordering of the serialization of object members is
        /// not significant.
        ///
        /// Note that we divert from the rules here - we use .NET's comparison,
        /// not the one above.  In a future version, a "strict" setting might
        /// be added (configurable), that takes into account above rules.
        ///
        /// For example:
        ///
        /// { "op": "test", "path": "/a/b/c", "value": "foo" }
        /// </summary>
        /// <param name="operation">The test operation</param>
        /// <param name="objectApplyTo">Object to apply the operation to</param>
        public void Test(Operation <T> operation, T objectToApplyTo)
        {
            // get value at path location
            object valueAtPathLocation     = null;
            var    positionInPathAsInteger = -1;
            var    actualPathProperty      = operation.path;

            positionInPathAsInteger = PropertyHelpers.GetNumericEnd(operation.path);

            if (positionInPathAsInteger > -1)
            {
                actualPathProperty = operation.path.Substring(0,
                                                              operation.path.IndexOf('/' + positionInPathAsInteger.ToString()));
            }

            var patchProperty = PropertyHelpers
                                .FindPropertyAndParent(objectToApplyTo, actualPathProperty, ContractResolver);

            // does property at path exist?
            CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.path);

            // get the property path
            Type typeOfFinalPropertyAtPathLocation;

            // 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 (positionInPathAsInteger > -1)
            {
                if (IsNonStringArray(patchProperty))
                {
                    // now, get the generic type of the enumerable
                    typeOfFinalPropertyAtPathLocation = PropertyHelpers
                                                        .GetEnumerableType(patchProperty.Property.PropertyType);

                    // get value (it can be cast, we just checked that)
                    var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);

                    if (array.Count <= positionInPathAsInteger)
                    {
                        throw new JsonPatchException <T>(operation,
                                                         string.Format("Patch failed: provided from path is invalid for array property type at " +
                                                                       "location path: {0}: invalid position",
                                                                       operation.path),
                                                         objectToApplyTo, 422);
                    }

                    valueAtPathLocation = array[positionInPathAsInteger];
                }
                else
                {
                    throw new JsonPatchException <T>(operation,
                                                     string.Format("Patch failed: provided from path is invalid for array property type at " +
                                                                   "location path: {0}: expected array",
                                                                   operation.path),
                                                     objectToApplyTo, 422);
                }
            }
            else
            {
                // no list, just get the value
                valueAtPathLocation = patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
                typeOfFinalPropertyAtPathLocation = patchProperty.Property.PropertyType;
            }

            var conversionResultTuple = PropertyHelpers.ConvertToActualType(
                typeOfFinalPropertyAtPathLocation,
                operation.value);

            // Is conversion successful
            CheckIfPropertyCanBeSet(conversionResultTuple, objectToApplyTo, operation, operation.path);

            //Compare
        }
Example #7
0
        /// <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 pathProperty = PropertyHelpers
                               .FindProperty(objectToApplyTo, actualPathToProperty);

            // does property at path exist?
            if (pathProperty == null)
            {
                throw new JsonPatchException(
                          new JsonPatchError(
                              objectToApplyTo,
                              operationToReport,
                              string.Format("Patch failed: property at location path: {0} does not exist", path))
                          , 422);
            }

            // it exists.  If it' an array, add to that array.  If it's not, we replace.

            // is the path an array (but not a string (= char[]))?  In this case,
            // the path must end with "/position" or "/-", which we already determined before.

            if (appendList || positionAsInteger > -1)
            {
                // what if it's an array but there's no position??
                if (pathProperty.PropertyType.IsNonStringList())
                {
                    // now, get the generic type of the enumerable
                    var genericTypeOfArray = PropertyHelpers.GetEnumerableType(pathProperty.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 (!pathProperty.CanRead)
                    {
                        // cannot get the property
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: cannot get property value at location from: {0}.  Possible cause: the property doesn't have an accessible getter.", path)),
                                  422);
                    }

                    // get value (it can be cast, we just checked that)
                    var array = PropertyHelpers.GetValue(pathProperty, objectToApplyTo, actualPathToProperty) as IList;

                    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
                {
                    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(pathProperty.PropertyType, value);

                // conversion successful
                if (conversionResultTuple.CanBeConverted)
                {
                    if (!pathProperty.CanWrite)
                    {
                        // cannot set the property
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operationToReport,
                                      string.Format("Patch failed: cannot set property value at path {0}.  Possible cause: the property doesn't have an accessible setter.", path)),
                                  422);
                    }

                    PropertyHelpers.SetValue(pathProperty, objectToApplyTo, actualPathToProperty,
                                             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);
                }
            }
        }
Example #8
0
        /// <summary>
        ///  The "copy" operation copies the value at a specified location to the
        ///  target location.
        ///
        ///  The operation object MUST contain a "from" member, which is a string
        ///  containing a JSON Pointer value that references the location in the
        ///  target document to copy the value from.
        ///
        ///  The "from" location MUST exist for the operation to be successful.
        ///
        ///  For example:
        ///
        ///  { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
        ///
        ///  This operation is functionally identical to an "add" operation at the
        ///  target location using the value specified in the "from" member.
        ///
        ///  Note: even though it's the same functionally, we do not call add with
        ///  the value specified in from for performance reasons (multiple checks of same requirements).
        /// </summary>
        /// <param name="operation">The copy operation</param>
        /// <param name="objectApplyTo">Object to apply the operation to</param>
        public void Copy(Operation <T> operation, T objectToApplyTo)
        {
            object valueAtFromLocation = null;

            // get path result
            var pathResult = PropertyHelpers.GetActualPropertyPath(
                operation.from,
                objectToApplyTo,
                operation,
                true);

            var positionAsInteger        = pathResult.NumericEnd;
            var actualPathToFromProperty = pathResult.PathToProperty;

            PropertyInfo fromProperty = PropertyHelpers
                                        .FindProperty(objectToApplyTo, actualPathToFromProperty);

            // does property at from exist?
            if (fromProperty == null)
            {
                throw new JsonPatchException(
                          new JsonPatchError(
                              objectToApplyTo,
                              operation,
                              string.Format("Patch failed: property at location from: {0} does not exist", operation.from))
                          , 422);
            }

            // get the property path

            // is the path an array (but not a string (= char[]))?  In this case,
            // the path must end with "/position" or "/-", which we already determined before.

            if (positionAsInteger > -1)
            {
                if (fromProperty.PropertyType.IsNonStringList())
                {
                    // now, get the generic type of the enumerable
                    var genericTypeOfArray = PropertyHelpers.GetEnumerableType(fromProperty.PropertyType);

                    if (!fromProperty.CanRead)
                    {
                        // cannot get the property
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operation,
                                      string.Format("Patch failed: cannot get property value at location from: {0}.  Possible cause: the property doesn't have an accessible getter.", operation.from)),
                                  422);
                    }

                    // get value (it can be cast, we just checked that)
                    var array = PropertyHelpers.GetValue(fromProperty, objectToApplyTo, actualPathToFromProperty) as IList;

                    if (array.Count <= positionAsInteger)
                    {
                        throw new JsonPatchException(
                                  new JsonPatchError(
                                      objectToApplyTo,
                                      operation,
                                      string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: invalid position",
                                                    operation.from))
                                  , 422);
                    }

                    valueAtFromLocation = array[positionAsInteger];
                }
                else
                {
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operation,
                                  string.Format("Patch failed: provided from path is invalid for array property type at location from: {0}: expected array",
                                                operation.from))
                              , 422);
                }
            }
            else
            {
                if (!fromProperty.CanRead)
                {
                    // cannot get the property
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operation,
                                  string.Format("Patch failed: cannot get property value at location from: {0}.  Possible cause: the property doesn't have an accessible getter.", operation.from)),
                              422);
                }

                // no list, just get the value
                valueAtFromLocation = PropertyHelpers.GetValue(fromProperty, objectToApplyTo, actualPathToFromProperty);
            }

            // add operation to target location with that value.
            Add(operation.path, valueAtFromLocation, objectToApplyTo, operation);
        }
Example #9
0
        /// <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 void 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 pathProperty = PropertyHelpers
                               .FindProperty(objectToApplyTo, actualPathToProperty);

            // does the target location exist?
            if (pathProperty == null)
            {
                throw new JsonPatchException(
                          new JsonPatchError(
                              objectToApplyTo,
                              operationToReport,
                              string.Format("Patch failed: property at location path: {0} does not exist", path))
                          , 422);
            }

            // get the property, and remove it - in this case, for DTO's, that means setting
            // it to null or its default value; in case of an array, remove at provided index
            // or at the end.
            if (removeFromList || positionAsInteger > -1)
            {
                // what if it's an array but there's no position??
                if (pathProperty.PropertyType.IsNonStringList())
                {
                    // now, get the generic type of the enumerable
                    var genericTypeOfArray = PropertyHelpers.GetEnumerableType(pathProperty.PropertyType);

                    if (!pathProperty.CanRead)
                    {
                        // cannot get 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);
                    }

                    // get value (it can be cast, we just checked that)
                    var array = PropertyHelpers.GetValue(pathProperty, objectToApplyTo, actualPathToProperty) as IList;

                    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);
                    }
                    else
                    {
                        if (positionAsInteger < array.Count)
                        {
                            array.RemoveAt(positionAsInteger);
                        }
                        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
                {
                    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 (!pathProperty.CanWrite)
                {
                    // cannot set the property
                    throw new JsonPatchException(
                              new JsonPatchError(
                                  objectToApplyTo,
                                  operationToReport,
                                  string.Format("Patch failed: cannot set property value at path {0}.  Possible cause: the property doesn't have an accessible setter.", path)),
                              422);
                }

                // setting the value to "null" will use the default value in case of value types, and
                // null in case of reference types
                PropertyHelpers.SetValue(pathProperty, objectToApplyTo, actualPathToProperty, null);
            }
        }
Example #10
0
        /// <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));
            }
        }
Example #11
0
        /// <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);
                }
            }
        }