// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Linq.Expressions; using Microsoft.AspNet.JsonPatch.Adapters; using Microsoft.AspNet.JsonPatch.Converters; using Microsoft.AspNet.JsonPatch.Helpers; using Microsoft.AspNet.JsonPatch.Operations; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; namespace Microsoft.AspNet.JsonPatch { // Implementation details: the purpose of this type of patch document is to ensure we can do type-checking // when producing a JsonPatchDocument. However, we cannot send this "typed" over the wire, as that would require // including type data in the JsonPatchDocument serialized as JSON (to allow for correct deserialization) - that's // not according to RFC 6902, and would thus break cross-platform compatibility. [JsonConverter(typeof(TypedJsonPatchDocumentConverter))] public class JsonPatchDocument : IJsonPatchDocument where TModel : class { public List> Operations { get; private set; } [JsonIgnore] public IContractResolver ContractResolver { get; set; } public JsonPatchDocument() { Operations = new List>(); ContractResolver = new DefaultContractResolver(); } // Create from list of operations public JsonPatchDocument(List> operations, IContractResolver contractResolver) { Operations = operations; ContractResolver = contractResolver; } /// /// Add operation. Will result in, for example, /// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] } /// /// value type /// path /// value /// public JsonPatchDocument Add(Expression> path, TProp value) { Operations.Add(new Operation( "add", ExpressionHelpers.GetPath(path).ToLower(), from: null, value: value)); return this; } /// /// Add value to list at given position /// /// value type /// path /// value /// position /// public JsonPatchDocument Add( Expression>> path, TProp value, int position) { Operations.Add(new Operation( "add", ExpressionHelpers.GetPath(path).ToLower() + "/" + position, from: null, value: value)); return this; } /// /// At value at end of list /// /// value type /// path /// value /// public JsonPatchDocument Add(Expression>> path, TProp value) { Operations.Add(new Operation( "add", ExpressionHelpers.GetPath(path).ToLower() + "/-", from: null, value: value)); return this; } /// /// Remove value at target location. Will result in, for example, /// { "op": "remove", "path": "/a/b/c" } /// /// /// /// public JsonPatchDocument Remove(Expression> path) { Operations.Add(new Operation("remove", ExpressionHelpers.GetPath(path).ToLower(), from: null)); return this; } /// /// Remove value from list at given position /// /// value type /// target location /// position /// public JsonPatchDocument Remove(Expression>> path, int position) { Operations.Add(new Operation( "remove", ExpressionHelpers.GetPath(path).ToLower() + "/" + position, from: null)); return this; } /// /// Remove value from end of list /// /// value type /// target location /// public JsonPatchDocument Remove(Expression>> path) { Operations.Add(new Operation( "remove", ExpressionHelpers.GetPath(path).ToLower() + "/-", from: null)); return this; } /// /// Replace value. Will result in, for example, /// { "op": "replace", "path": "/a/b/c", "value": 42 } /// /// /// /// public JsonPatchDocument Replace(Expression> path, TProp value) { Operations.Add(new Operation( "replace", ExpressionHelpers.GetPath(path).ToLower(), from: null, value: value)); return this; } /// /// Replace value in a list at given position /// /// value type /// target location /// position /// public JsonPatchDocument Replace( Expression>> path, TProp value, int position) { Operations.Add(new Operation( "replace", ExpressionHelpers.GetPath(path).ToLower() + "/" + position, from: null, value: value)); return this; } /// /// Replace value at end of a list /// /// value type /// target location /// public JsonPatchDocument Replace(Expression>> path, TProp value) { Operations.Add(new Operation( "replace", ExpressionHelpers.GetPath(path).ToLower() + "/-", from: null, value: value)); return this; } /// /// Removes value at specified location and add it to the target location. Will result in, for example: /// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" } /// /// /// /// public JsonPatchDocument Move( Expression> from, Expression> path) { Operations.Add(new Operation( "move", ExpressionHelpers.GetPath(path).ToLower(), ExpressionHelpers.GetPath(from).ToLower())); return this; } /// /// Move from a position in a list to a new location /// /// /// /// /// /// public JsonPatchDocument Move( Expression>> from, int positionFrom, Expression> path) { Operations.Add(new Operation( "move", ExpressionHelpers.GetPath(path).ToLower(), ExpressionHelpers.GetPath(from).ToLower() + "/" + positionFrom)); return this; } /// /// Move from a property to a location in a list /// /// /// /// /// /// public JsonPatchDocument Move( Expression> from, Expression>> path, int positionTo) { Operations.Add(new Operation( "move", ExpressionHelpers.GetPath(path).ToLower() + "/" + positionTo, ExpressionHelpers.GetPath(from).ToLower())); return this; } /// /// Move from a position in a list to another location in a list /// /// /// /// /// /// public JsonPatchDocument Move( Expression>> from, int positionFrom, Expression>> path, int positionTo) { Operations.Add(new Operation( "move", ExpressionHelpers.GetPath(path).ToLower() + "/" + positionTo, ExpressionHelpers.GetPath(from).ToLower() + "/" + positionFrom)); return this; } /// /// Move from a position in a list to the end of another list /// /// /// /// /// /// public JsonPatchDocument Move( Expression>> from, int positionFrom, Expression>> path) { Operations.Add(new Operation( "move", ExpressionHelpers.GetPath(path).ToLower() + "/-", ExpressionHelpers.GetPath(from).ToLower() + "/" + positionFrom)); return this; } /// /// Move to the end of a list /// /// /// /// /// /// public JsonPatchDocument Move( Expression> from, Expression>> path) { Operations.Add(new Operation( "move", ExpressionHelpers.GetPath(path).ToLower() + "/-", ExpressionHelpers.GetPath(from).ToLower())); return this; } /// /// Copy the value at specified location to the target location. Willr esult in, for example: /// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" } /// /// /// /// public JsonPatchDocument Copy( Expression> from, Expression> path) { Operations.Add(new Operation( "copy", ExpressionHelpers.GetPath(path).ToLower() , ExpressionHelpers.GetPath(from).ToLower())); return this; } /// /// Copy from a position in a list to a new location /// /// /// /// /// /// public JsonPatchDocument Copy( Expression>> from, int positionFrom, Expression> path) { Operations.Add(new Operation( "copy", ExpressionHelpers.GetPath(path).ToLower(), ExpressionHelpers.GetPath(from).ToLower() + "/" + positionFrom)); return this; } /// /// Copy from a property to a location in a list /// /// /// /// /// /// public JsonPatchDocument Copy( Expression> from, Expression>> path, int positionTo) { Operations.Add(new Operation( "copy", ExpressionHelpers.GetPath(path).ToLower() + "/" + positionTo, ExpressionHelpers.GetPath(from).ToLower())); return this; } /// /// Copy from a position in a list to a new location in a list /// /// /// /// /// /// public JsonPatchDocument Copy( Expression>> from, int positionFrom, Expression>> path, int positionTo) { Operations.Add(new Operation( "copy", ExpressionHelpers.GetPath(path).ToLower() + "/" + positionTo, ExpressionHelpers.GetPath(from).ToLower() + "/" + positionFrom)); return this; } /// /// Copy from a position in a list to the end of another list /// /// /// /// /// /// public JsonPatchDocument Copy( Expression>> from, int positionFrom, Expression>> path) { Operations.Add(new Operation( "copy", ExpressionHelpers.GetPath(path).ToLower() + "/-", ExpressionHelpers.GetPath(from).ToLower() + "/" + positionFrom)); return this; } /// /// Copy to the end of a list /// /// /// /// /// /// public JsonPatchDocument Copy( Expression> from, Expression>> path) { Operations.Add(new Operation( "copy", ExpressionHelpers.GetPath(path).ToLower() + "/-", ExpressionHelpers.GetPath(from).ToLower())); return this; } public void ApplyTo(TModel objectToApplyTo) { ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction: null)); } public void ApplyTo(TModel objectToApplyTo, Action> logErrorAction) { ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction)); } public void ApplyTo(TModel objectToApplyTo, IObjectAdapter adapter) { // apply each operation in order foreach (var op in Operations) { op.Apply(objectToApplyTo, adapter); } } public List GetOperations() { var allOps = new List(); if (Operations != null) { foreach (var op in Operations) { var untypedOp = new Operation(); untypedOp.op = op.op; untypedOp.value = op.value; untypedOp.path = op.path; untypedOp.from = op.from; allOps.Add(untypedOp); } } return allOps; } } }