Add JsonPatch

Moved from https://github.com/aspnet/Mvc
This commit is contained in:
Ryan Nowak 2015-09-21 11:15:18 -07:00
parent d3f2b240d7
commit e0ac69c9f7
51 changed files with 10306 additions and 1 deletions

36
JsonPatch.sln Normal file
View File

@ -0,0 +1,36 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{430B59ED-F960-4D3A-8FFE-3370008E168D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{36CD6341-AB44-44EB-B3AA-BF98C89FECDD}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.JsonPatch", "src\Microsoft.AspNet.JsonPatch\Microsoft.AspNet.JsonPatch.xproj", "{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.JsonPatch.Test", "test\Microsoft.AspNet.JsonPatch.Test\Microsoft.AspNet.JsonPatch.Test.xproj", "{81C20848-E063-4E12-AC40-0B55A532C16C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534}.Release|Any CPU.Build.0 = Release|Any CPU
{81C20848-E063-4E12-AC40-0B55A532C16C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81C20848-E063-4E12-AC40-0B55A532C16C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81C20848-E063-4E12-AC40-0B55A532C16C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81C20848-E063-4E12-AC40-0B55A532C16C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{4D55F4D8-633B-462F-A5B1-FEB84BD2D534} = {430B59ED-F960-4D3A-8FFE-3370008E168D}
{81C20848-E063-4E12-AC40-0B55A532C16C} = {36CD6341-AB44-44EB-B3AA-BF98C89FECDD}
EndGlobalSection
EndGlobal

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="AspNetVNext" value="https://www.myget.org/F/aspnetcidev/api/v3/index.json" />

View File

@ -0,0 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.JsonPatch.Operations;
namespace Microsoft.AspNet.JsonPatch.Adapters
{
/// <summary>
/// Defines the operations that can be performed on a JSON patch document.
/// </summary>
public interface IObjectAdapter
{
void Add(Operation operation, object objectToApplyTo);
void Copy(Operation operation, object objectToApplyTo);
void Move(Operation operation, object objectToApplyTo);
void Remove(Operation operation, object objectToApplyTo);
void Replace(Operation operation, object objectToApplyTo);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,76 @@
// Copyright (c) .NET Foundation. 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 Microsoft.AspNet.JsonPatch.Exceptions;
using Microsoft.AspNet.JsonPatch.Operations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
namespace Microsoft.AspNet.JsonPatch.Converters
{
public class JsonPatchDocumentConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
if (objectType != typeof(JsonPatchDocument))
{
throw new ArgumentException(Resources.FormatParameterMustMatchType("objectType", "JsonPatchDocument"), "objectType");
}
try
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
// load jObject
var jObject = JArray.Load(reader);
// Create target object for Json => list of operations
var targetOperations = new List<Operation>();
// Create a new reader for this jObject, and set all properties
// to match the original reader.
var jObjectReader = jObject.CreateReader();
jObjectReader.Culture = reader.Culture;
jObjectReader.DateParseHandling = reader.DateParseHandling;
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
// Populate the object properties
serializer.Populate(jObjectReader, targetOperations);
// container target: the JsonPatchDocument.
var container = new JsonPatchDocument(targetOperations, new DefaultContractResolver());
return container;
}
catch (Exception ex)
{
throw new JsonPatchException(Resources.FormatInvalidJsonPatchDocument(objectType.Name), ex);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is IJsonPatchDocument)
{
var jsonPatchDoc = (IJsonPatchDocument)value;
var lst = jsonPatchDoc.GetOperations();
// write out the operations, no envelope
serializer.Serialize(writer, lst);
}
}
}
}

View File

@ -0,0 +1,65 @@
// Copyright (c) .NET Foundation. 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.Reflection;
using Microsoft.AspNet.JsonPatch.Exceptions;
using Microsoft.AspNet.JsonPatch.Operations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
namespace Microsoft.AspNet.JsonPatch.Converters
{
public class TypedJsonPatchDocumentConverter : JsonPatchDocumentConverter
{
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
try
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
var genericType = objectType.GetGenericArguments()[0];
// load jObject
var jObject = JArray.Load(reader);
// Create target object for Json => list of operations, typed to genericType
var genericOperation = typeof(Operation<>);
var concreteOperationType = genericOperation.MakeGenericType(genericType);
var genericList = typeof(List<>);
var concreteList = genericList.MakeGenericType(concreteOperationType);
var targetOperations = Activator.CreateInstance(concreteList);
//Create a new reader for this jObject, and set all properties to match the original reader.
var jObjectReader = jObject.CreateReader();
jObjectReader.Culture = reader.Culture;
jObjectReader.DateParseHandling = reader.DateParseHandling;
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
// Populate the object properties
serializer.Populate(jObjectReader, targetOperations);
// container target: the typed JsonPatchDocument.
var container = Activator.CreateInstance(objectType, targetOperations, new DefaultContractResolver());
return container;
}
catch (Exception ex)
{
throw new JsonPatchException(Resources.FormatInvalidJsonPatchDocument(objectType.Name), ex);
}
}
}
}

View File

@ -0,0 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.JsonPatch.Operations;
namespace Microsoft.AspNet.JsonPatch.Exceptions
{
public class JsonPatchException : Exception
{
public Operation FailedOperation { get; private set; }
public object AffectedObject { get; private set; }
public JsonPatchException()
{
}
public JsonPatchException(JsonPatchError jsonPatchError, Exception innerException)
: base(jsonPatchError.ErrorMessage, innerException)
{
FailedOperation = jsonPatchError.Operation;
AffectedObject = jsonPatchError.AffectedObject;
}
public JsonPatchException(JsonPatchError jsonPatchError)
: this(jsonPatchError, null)
{
}
public JsonPatchException(string message, Exception innerException)
: base (message, innerException)
{
}
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.JsonPatch.Helpers
{
internal class ActualPropertyPathResult
{
public int NumericEnd { get; private set; }
public string PathToProperty { get; set; }
public bool ExecuteAtEnd { get; set; }
public ActualPropertyPathResult(
int numericEnd,
string pathToProperty,
bool executeAtEnd)
{
NumericEnd = numericEnd;
PathToProperty = pathToProperty;
ExecuteAtEnd = executeAtEnd;
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.JsonPatch.Helpers
{
internal class ConversionResult
{
public bool CanBeConverted { get; private set; }
public object ConvertedInstance { get; private set; }
public ConversionResult(bool canBeConverted, object convertedInstance)
{
CanBeConverted = canBeConverted;
ConvertedInstance = convertedInstance;
}
}
}

View File

@ -0,0 +1,77 @@
// Copyright (c) .NET Foundation. 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;
namespace Microsoft.AspNet.JsonPatch.Helpers
{
// Helper methods to allow case-insensitive key search
internal static class ExpandoObjectDictionaryExtensions
{
internal static void SetValueForCaseInsensitiveKey(
this IDictionary<string, object> propertyDictionary,
string key,
object value)
{
foreach (KeyValuePair<string, object> kvp in propertyDictionary)
{
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
{
propertyDictionary[kvp.Key] = value;
break;
}
}
}
internal static void RemoveValueForCaseInsensitiveKey(
this IDictionary<string, object> propertyDictionary,
string key)
{
string realKey = null;
foreach (KeyValuePair<string, object> kvp in propertyDictionary)
{
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
{
realKey = kvp.Key;
break;
}
}
if (realKey != null)
{
propertyDictionary.Remove(realKey);
}
}
internal static object GetValueForCaseInsensitiveKey(
this IDictionary<string, object> propertyDictionary,
string key)
{
foreach (KeyValuePair<string, object> kvp in propertyDictionary)
{
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
{
return kvp.Value;
}
}
throw new ArgumentException(Resources.FormatDictionaryKeyNotFound(key));
}
internal static bool ContainsCaseInsensitiveKey(
this IDictionary<string, object> propertyDictionary,
string key)
{
foreach (KeyValuePair<string, object> kvp in propertyDictionary)
{
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,99 @@
// Copyright (c) .NET Foundation. 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.Globalization;
using System.Linq.Expressions;
namespace Microsoft.AspNet.JsonPatch.Helpers
{
internal static class ExpressionHelpers
{
public static string GetPath<TModel, TProp>(Expression<Func<TModel, TProp>> expr) where TModel : class
{
return "/" + GetPath(expr.Body, true);
}
private static string GetPath(Expression expr, bool firstTime)
{
switch (expr.NodeType)
{
case ExpressionType.ArrayIndex:
var binaryExpression = (BinaryExpression)expr;
if (ContinueWithSubPath(binaryExpression.Left.NodeType, false))
{
var leftFromBinaryExpression = GetPath(binaryExpression.Left, false);
return leftFromBinaryExpression + "/" + binaryExpression.Right.ToString();
}
else
{
return binaryExpression.Right.ToString();
}
case ExpressionType.Call:
var methodCallExpression = (MethodCallExpression)expr;
if (ContinueWithSubPath(methodCallExpression.Object.NodeType, false))
{
var leftFromMemberCallExpression = GetPath(methodCallExpression.Object, false);
return leftFromMemberCallExpression + "/" +
GetIndexerInvocation(methodCallExpression.Arguments[0]);
}
else
{
return GetIndexerInvocation(methodCallExpression.Arguments[0]);
}
case ExpressionType.Convert:
return GetPath(((UnaryExpression)expr).Operand, false);
case ExpressionType.MemberAccess:
var memberExpression = expr as MemberExpression;
if (ContinueWithSubPath(memberExpression.Expression.NodeType, false))
{
var left = GetPath(memberExpression.Expression, false);
return left + "/" + memberExpression.Member.Name;
}
else
{
return memberExpression.Member.Name;
}
case ExpressionType.Parameter:
// Fits "x => x" (the whole document which is "" as JSON pointer)
return firstTime ? string.Empty : null;
default:
return string.Empty;
}
}
private static bool ContinueWithSubPath(ExpressionType expressionType, bool firstTime)
{
if (firstTime)
{
return (expressionType == ExpressionType.ArrayIndex
|| expressionType == ExpressionType.Call
|| expressionType == ExpressionType.Convert
|| expressionType == ExpressionType.MemberAccess
|| expressionType == ExpressionType.Parameter);
}
else
{
return (expressionType == ExpressionType.ArrayIndex
|| expressionType == ExpressionType.Call
|| expressionType == ExpressionType.Convert
|| expressionType == ExpressionType.MemberAccess);
}
}
private static string GetIndexerInvocation(Expression expression)
{
var converted = Expression.Convert(expression, typeof(object));
var fakeParameter = Expression.Parameter(typeof(object), null);
var lambda = Expression.Lambda<Func<object, object>>(converted, fakeParameter);
Func<object, object> func;
func = lambda.Compile();
return Convert.ToString(func(null), CultureInfo.InvariantCulture);
}
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.JsonPatch.Helpers
{
/// <summary>
/// Return value for the helper method used by Copy/Move. Needed to ensure we can make a different
/// decision in the calling method when the value is null because it cannot be fetched (HasError = true)
/// versus when it actually is null (much like why RemovedPropertyTypeResult is used for returning
/// type in the Remove operation).
/// </summary>
public class GetValueResult
{
public GetValueResult(object propertyValue, bool hasError)
{
PropertyValue = propertyValue;
HasError = hasError;
}
/// <summary>
/// The value of the property we're trying to get
/// </summary>
public object PropertyValue { get; private set; }
/// <summary>
/// HasError: true when an error occurred, the operation didn't complete succesfully
/// </summary>
public bool HasError { get; private set; }
}
}

View File

@ -0,0 +1,43 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Newtonsoft.Json.Serialization;
namespace Microsoft.AspNet.JsonPatch
{
/// <summary>
/// Metadata for JsonProperty.
/// </summary>
public class JsonPatchProperty
{
/// <summary>
/// Initializes a new instance.
/// </summary>
public JsonPatchProperty(JsonProperty property, object parent)
{
if (property == null)
{
throw new ArgumentNullException(nameof(property));
}
if (parent == null)
{
throw new ArgumentNullException(nameof(parent));
}
Property = property;
Parent = parent;
}
/// <summary>
/// Gets or sets JsonProperty.
/// </summary>
public JsonProperty Property { get; set; }
/// <summary>
/// Gets or sets Parent.
/// </summary>
public object Parent { get; set; }
}
}

View File

@ -0,0 +1,210 @@
// Copyright (c) .NET Foundation. 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;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Serialization;
namespace Microsoft.AspNet.JsonPatch.Helpers
{
internal class ObjectTreeAnalysisResult
{
// either the property is part of the container dictionary,
// or we have a direct reference to a JsonPatchProperty instance
public bool UseDynamicLogic { get; private set; }
public bool IsValidPathForAdd { get; private set; }
public bool IsValidPathForRemove { get; private set; }
public IDictionary<string, object> Container { get; private set; }
public string PropertyPathInParent { get; private set; }
public JsonPatchProperty JsonPatchProperty { get; private set; }
public ObjectTreeAnalysisResult(
object objectToSearch,
string propertyPath,
IContractResolver contractResolver)
{
// construct the analysis result.
// split the propertypath, and if necessary, remove the first
// empty item (that's the case when it starts with a "/")
var propertyPathTree = propertyPath.Split(
new char[] { '/' },
StringSplitOptions.RemoveEmptyEntries);
// we've now got a split up property tree "base/property/otherproperty/..."
int lastPosition = 0;
object targetObject = objectToSearch;
for (int i = 0; i < propertyPathTree.Length; i++)
{
lastPosition = i;
// if the current target object is an ExpandoObject (IDictionary<string, object>),
// we cannot use the ContractResolver.
var dictionary = targetObject as IDictionary<string, object>;
if (dictionary != null)
{
// find the value in the dictionary
if (dictionary.ContainsCaseInsensitiveKey(propertyPathTree[i]))
{
var possibleNewTargetObject = dictionary.GetValueForCaseInsensitiveKey(propertyPathTree[i]);
// unless we're at the last item, we should set the targetobject
// to the new object. If we're at the last item, we need to stop
if (i != propertyPathTree.Length - 1)
{
targetObject = possibleNewTargetObject;
}
}
else
{
break;
}
}
else
{
// if the current part of the path is numeric, this means we're trying
// to get the propertyInfo of a specific object in an array. To allow
// for this, the previous value (targetObject) must be an IEnumerable, and
// the position must exist.
int numericValue = -1;
if (int.TryParse(propertyPathTree[i], out numericValue))
{
var element = GetElementAtFromObject(targetObject, numericValue);
if (element != null)
{
targetObject = element;
}
else
{
break;
}
}
else
{
var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(targetObject.GetType());
// does the property exist?
var attemptedProperty = jsonContract
.Properties
.FirstOrDefault(p => string.Equals(p.PropertyName, propertyPathTree[i], StringComparison.OrdinalIgnoreCase));
if (attemptedProperty != null)
{
// unless we're at the last item, we should continue searching.
// If we're at the last item, we need to stop
if ((i != propertyPathTree.Length - 1))
{
targetObject = attemptedProperty.ValueProvider.GetValue(targetObject);
}
}
else
{
// property cannot be found, and we're not working with dynamics.
// Stop, and return invalid path.
break;
}
}
}
}
if (propertyPathTree.Length - lastPosition != 1)
{
IsValidPathForAdd = false;
IsValidPathForRemove = false;
return;
}
// two things can happen now. The targetproperty can be an IDictionary - in that
// case, it's valid for add if there's 1 item left in the propertyPathTree.
//
// it can also be a property info. In that case, if there's nothing left in the path
// tree we're at the end, if there's one left we can try and set that.
if (targetObject is IDictionary<string, object>)
{
UseDynamicLogic = true;
Container = (IDictionary<string, object>)targetObject;
IsValidPathForAdd = true;
PropertyPathInParent = propertyPathTree[propertyPathTree.Length - 1];
// to be able to remove this property, it must exist
IsValidPathForRemove = Container.ContainsCaseInsensitiveKey(PropertyPathInParent);
}
else if (targetObject is IList)
{
System.Diagnostics.Debugger.Launch();
UseDynamicLogic = false;
int index;
if (!Int32.TryParse(propertyPathTree[propertyPathTree.Length - 1], out index))
{
// We only support indexing into a list
IsValidPathForAdd = false;
IsValidPathForRemove = false;
return;
}
IsValidPathForAdd = true;
IsValidPathForRemove = ((IList)targetObject).Count > index;
PropertyPathInParent = propertyPathTree[propertyPathTree.Length - 1];
}
else
{
UseDynamicLogic = false;
var property = propertyPathTree[propertyPathTree.Length - 1];
var jsonContract = (JsonObjectContract)contractResolver.ResolveContract(targetObject.GetType());
var attemptedProperty = jsonContract
.Properties
.FirstOrDefault(p => string.Equals(p.PropertyName, property, StringComparison.OrdinalIgnoreCase));
if (attemptedProperty == null)
{
IsValidPathForAdd = false;
IsValidPathForRemove = false;
}
else
{
IsValidPathForAdd = true;
IsValidPathForRemove = true;
JsonPatchProperty = new JsonPatchProperty(attemptedProperty, targetObject);
PropertyPathInParent = property;
}
}
}
private object GetElementAtFromObject(object targetObject, int numericValue)
{
if (numericValue > -1)
{
// Check if the targetobject is an IEnumerable,
// and if the position is valid.
if (targetObject is IEnumerable)
{
var indexable = ((IEnumerable)targetObject).Cast<object>();
if (indexable.Count() >= numericValue)
{
return indexable.ElementAt(numericValue);
}
else { return null; }
}
else { return null; }
}
else
{
return null;
}
}
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.JsonPatch.Exceptions;
namespace Microsoft.AspNet.JsonPatch.Helpers
{
internal static class PathHelpers
{
internal static string NormalizePath(string path)
{
// check for most common path errors on create. This is not
// absolutely necessary, but it allows us to already catch mistakes
// on creation of the patch document rather than on execute.
if (path.Contains(".") || path.Contains("//") || path.Contains(" ") || path.Contains("\\"))
{
throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
}
if (!(path.StartsWith("/")))
{
return "/" + path;
}
else
{
return path;
}
}
}
}

View File

@ -0,0 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.JsonPatch.Helpers
{
/// <summary>
/// Return value for Remove operation. The combination tells us what to do next (if this operation
/// is called from inside another operation, eg: Replace, Copy.
///
/// Possible combo:
/// - ActualType contains type: operation succesfully completed, can continue when called from inside
/// another operation
/// - ActualType null and HasError true: operation not completed succesfully, should not be allowed to continue
/// - ActualType null and HasError false: operation completed succesfully, but we should not be allowed to
/// continue when called from inside another method as we could not verify the type of the removed property.
/// This happens when the value of an item in an ExpandoObject dictionary is null.
/// </summary>
internal class RemovedPropertyTypeResult
{
/// <summary>
/// The type of the removed property (value)
/// </summary>
public Type ActualType { get; private set; }
/// <summary>
/// HasError: true when an error occurred, the operation didn't complete succesfully
/// </summary>
public bool HasError { get; set; }
public RemovedPropertyTypeResult(Type actualType, bool hasError)
{
ActualType = actualType;
HasError = hasError;
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.JsonPatch.Operations;
using System.Collections.Generic;
using Newtonsoft.Json.Serialization;
namespace Microsoft.AspNet.JsonPatch
{
public interface IJsonPatchDocument
{
IContractResolver ContractResolver { get; set; }
IList<Operation> GetOperations();
}
}

View File

@ -0,0 +1,222 @@
// Copyright (c) .NET Foundation. 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 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 allow creation of such
// documents for cases where there's no class/DTO to work on. Typical use case: backend not built in
// .NET or architecture doesn't contain a shared DTO layer.
[JsonConverter(typeof(JsonPatchDocumentConverter))]
public class JsonPatchDocument : IJsonPatchDocument
{
public List<Operation> Operations { get; private set; }
[JsonIgnore]
public IContractResolver ContractResolver { get; set; }
public JsonPatchDocument()
{
Operations = new List<Operation>();
ContractResolver = new DefaultContractResolver();
}
public JsonPatchDocument(List<Operation> operations, IContractResolver contractResolver)
{
if (operations == null)
{
throw new ArgumentNullException(nameof(operations));
}
if (contractResolver == null)
{
throw new ArgumentNullException(nameof(contractResolver));
}
Operations = operations;
ContractResolver = contractResolver;
}
/// <summary>
/// Add operation. Will result in, for example,
/// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
/// </summary>
/// <param name="path">target location</param>
/// <param name="value">value</param>
/// <returns></returns>
public JsonPatchDocument Add(string path, object value)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation("add", PathHelpers.NormalizePath(path), null, value));
return this;
}
/// <summary>
/// Remove value at target location. Will result in, for example,
/// { "op": "remove", "path": "/a/b/c" }
/// </summary>
/// <param name="path">target location</param>
/// <returns></returns>
public JsonPatchDocument Remove(string path)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation("remove", PathHelpers.NormalizePath(path), null, null));
return this;
}
/// <summary>
/// Replace value. Will result in, for example,
/// { "op": "replace", "path": "/a/b/c", "value": 42 }
/// </summary>
/// <param name="path">target location</param>
/// <param name="value">value</param>
/// <returns></returns>
public JsonPatchDocument Replace(string path, object value)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation("replace", PathHelpers.NormalizePath(path), null, value));
return this;
}
/// <summary>
/// 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" }
/// </summary>
/// <param name="from">source location</param>
/// <param name="path">target location</param>
/// <returns></returns>
public JsonPatchDocument Move(string from, string path)
{
if (from == null)
{
throw new ArgumentNullException(nameof(from));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation("move", PathHelpers.NormalizePath(path), PathHelpers.NormalizePath(from)));
return this;
}
/// <summary>
/// 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" }
/// </summary>
/// <param name="from">source location</param>
/// <param name="path">target location</param>
/// <returns></returns>
public JsonPatchDocument Copy(string from, string path)
{
if (from == null)
{
throw new ArgumentNullException(nameof(from));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation("copy", PathHelpers.NormalizePath(path), PathHelpers.NormalizePath(from)));
return this;
}
/// <summary>
/// Apply this JsonPatchDocument
/// </summary>
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
public void ApplyTo(object objectToApplyTo)
{
if (objectToApplyTo == null)
{
throw new ArgumentNullException(nameof(objectToApplyTo));
}
ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction: null));
}
/// <summary>
/// Apply this JsonPatchDocument
/// </summary>
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
/// <param name="logErrorAction">Action to log errors</param>
public void ApplyTo(object objectToApplyTo, Action<JsonPatchError> logErrorAction)
{
if (objectToApplyTo == null)
{
throw new ArgumentNullException(nameof(objectToApplyTo));
}
ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction));
}
/// <summary>
/// Apply this JsonPatchDocument
/// </summary>
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
/// <param name="adapter">IObjectAdapter instance to use when applying</param>
public void ApplyTo(object objectToApplyTo, IObjectAdapter adapter)
{
if (objectToApplyTo == null)
{
throw new ArgumentNullException(nameof(objectToApplyTo));
}
if (adapter == null)
{
throw new ArgumentNullException(nameof(adapter));
}
// apply each operation in order
foreach (var op in Operations)
{
op.Apply(objectToApplyTo, adapter);
}
}
IList<Operation> IJsonPatchDocument.GetOperations()
{
var allOps = new List<Operation>();
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;
}
}
}

View File

@ -0,0 +1,700 @@
// Copyright (c) .NET Foundation. 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<TModel> : IJsonPatchDocument where TModel : class
{
public List<Operation<TModel>> Operations { get; private set; }
[JsonIgnore]
public IContractResolver ContractResolver { get; set; }
public JsonPatchDocument()
{
Operations = new List<Operation<TModel>>();
ContractResolver = new DefaultContractResolver();
}
// Create from list of operations
public JsonPatchDocument(List<Operation<TModel>> operations, IContractResolver contractResolver)
{
if (operations == null)
{
throw new ArgumentNullException(nameof(operations));
}
if (contractResolver == null)
{
throw new ArgumentNullException(nameof(contractResolver));
}
Operations = operations;
ContractResolver = contractResolver;
}
/// <summary>
/// Add operation. Will result in, for example,
/// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
/// </summary>
/// <typeparam name="TProp">value type</typeparam>
/// <param name="path">target location</param>
/// <param name="value">value</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Add<TProp>(Expression<Func<TModel, TProp>> path, TProp value)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"add",
ExpressionHelpers.GetPath(path).ToLowerInvariant(),
from: null,
value: value));
return this;
}
/// <summary>
/// Add value to list at given position
/// </summary>
/// <typeparam name="TProp">value type</typeparam>
/// <param name="path">target location</param>
/// <param name="value">value</param>
/// <param name="position">position</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Add<TProp>(
Expression<Func<TModel, IList<TProp>>> path,
TProp value,
int position)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"add",
ExpressionHelpers.GetPath(path).ToLowerInvariant() + "/" + position,
from: null,
value: value));
return this;
}
/// <summary>
/// At value at end of list
/// </summary>
/// <typeparam name="TProp">value type</typeparam>
/// <param name="path">target location</param>
/// <param name="value">value</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Add<TProp>(Expression<Func<TModel, IList<TProp>>> path, TProp value)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"add",
ExpressionHelpers.GetPath(path).ToLowerInvariant() + "/-",
from: null,
value: value));
return this;
}
/// <summary>
/// Remove value at target location. Will result in, for example,
/// { "op": "remove", "path": "/a/b/c" }
/// </summary>
/// <param name="path">target location</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Remove<TProp>(Expression<Func<TModel, TProp>> path)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>("remove", ExpressionHelpers.GetPath(path).ToLowerInvariant(), from: null));
return this;
}
/// <summary>
/// Remove value from list at given position
/// </summary>
/// <typeparam name="TProp">value type</typeparam>
/// <param name="path">target location</param>
/// <param name="position">position</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Remove<TProp>(Expression<Func<TModel, IList<TProp>>> path, int position)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"remove",
ExpressionHelpers.GetPath(path).ToLowerInvariant() + "/" + position,
from: null));
return this;
}
/// <summary>
/// Remove value from end of list
/// </summary>
/// <typeparam name="TProp">value type</typeparam>
/// <param name="path">target location</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Remove<TProp>(Expression<Func<TModel, IList<TProp>>> path)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"remove",
ExpressionHelpers.GetPath(path).ToLowerInvariant() + "/-",
from: null));
return this;
}
/// <summary>
/// Replace value. Will result in, for example,
/// { "op": "replace", "path": "/a/b/c", "value": 42 }
/// </summary>
/// <param name="path">target location</param>
/// <param name="value">value</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Replace<TProp>(Expression<Func<TModel, TProp>> path, TProp value)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"replace",
ExpressionHelpers.GetPath(path).ToLowerInvariant(),
from: null,
value: value));
return this;
}
/// <summary>
/// Replace value in a list at given position
/// </summary>
/// <typeparam name="TProp">value type</typeparam>
/// <param name="path">target location</param>
/// <param name="value">value</param>
/// <param name="position">position</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Replace<TProp>(Expression<Func<TModel, IList<TProp>>> path,
TProp value, int position)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"replace",
ExpressionHelpers.GetPath(path).ToLowerInvariant() + "/" + position,
from: null,
value: value));
return this;
}
/// <summary>
/// Replace value at end of a list
/// </summary>
/// <typeparam name="TProp">value type</typeparam>
/// <param name="path">target location</param>
/// <param name="value">value</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Replace<TProp>(Expression<Func<TModel, IList<TProp>>> path, TProp value)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"replace",
ExpressionHelpers.GetPath(path).ToLowerInvariant() + "/-",
from: null,
value: value));
return this;
}
/// <summary>
/// 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" }
/// </summary>
/// <param name="from">source location</param>
/// <param name="path">target location</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Move<TProp>(
Expression<Func<TModel, TProp>> from,
Expression<Func<TModel, TProp>> path)
{
if (from == null)
{
throw new ArgumentNullException(nameof(from));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"move",
ExpressionHelpers.GetPath(path).ToLowerInvariant(),
ExpressionHelpers.GetPath(from).ToLowerInvariant()));
return this;
}
/// <summary>
/// Move from a position in a list to a new location
/// </summary>
/// <typeparam name="TProp"></typeparam>
/// <param name="from">source location</param>
/// <param name="positionFrom">position</param>
/// <param name="path">target location</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Move<TProp>(
Expression<Func<TModel, IList<TProp>>> from,
int positionFrom,
Expression<Func<TModel, TProp>> path)
{
if (from == null)
{
throw new ArgumentNullException(nameof(from));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"move",
ExpressionHelpers.GetPath(path).ToLowerInvariant(),
ExpressionHelpers.GetPath(from).ToLowerInvariant() + "/" + positionFrom));
return this;
}
/// <summary>
/// Move from a property to a location in a list
/// </summary>
/// <typeparam name="TProp"></typeparam>
/// <param name="from">source location</param>
/// <param name="path">target location</param>
/// <param name="positionTo">position</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Move<TProp>(
Expression<Func<TModel, TProp>> from,
Expression<Func<TModel, IList<TProp>>> path,
int positionTo)
{
if (from == null)
{
throw new ArgumentNullException(nameof(from));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"move",
ExpressionHelpers.GetPath(path).ToLowerInvariant() + "/" + positionTo,
ExpressionHelpers.GetPath(from).ToLowerInvariant()));
return this;
}
/// <summary>
/// Move from a position in a list to another location in a list
/// </summary>
/// <typeparam name="TProp"></typeparam>
/// <param name="from">source location</param>
/// <param name="positionFrom">position (source)</param>
/// <param name="path">target location</param>
/// <param name="positionTo">position (target)</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Move<TProp>(
Expression<Func<TModel, IList<TProp>>> from,
int positionFrom,
Expression<Func<TModel, IList<TProp>>> path,
int positionTo)
{
if (from == null)
{
throw new ArgumentNullException(nameof(from));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"move",
ExpressionHelpers.GetPath(path).ToLowerInvariant() + "/" + positionTo,
ExpressionHelpers.GetPath(from).ToLowerInvariant() + "/" + positionFrom));
return this;
}
/// <summary>
/// Move from a position in a list to the end of another list
/// </summary>
/// <typeparam name="TProp"></typeparam>
/// <param name="from">source location</param>
/// <param name="positionFrom">position</param>
/// <param name="path">target location</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Move<TProp>(
Expression<Func<TModel, IList<TProp>>> from,
int positionFrom,
Expression<Func<TModel, IList<TProp>>> path)
{
if (from == null)
{
throw new ArgumentNullException(nameof(from));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"move",
ExpressionHelpers.GetPath(path).ToLowerInvariant() + "/-",
ExpressionHelpers.GetPath(from).ToLowerInvariant() + "/" + positionFrom));
return this;
}
/// <summary>
/// Move to the end of a list
/// </summary>
/// <typeparam name="TProp"></typeparam>
/// <param name="from">source location</param>
/// <param name="path">target location</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Move<TProp>(
Expression<Func<TModel, TProp>> from,
Expression<Func<TModel, IList<TProp>>> path)
{
if (from == null)
{
throw new ArgumentNullException(nameof(from));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"move",
ExpressionHelpers.GetPath(path).ToLowerInvariant() + "/-",
ExpressionHelpers.GetPath(from).ToLowerInvariant()));
return this;
}
/// <summary>
/// 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" }
/// </summary>
/// <param name="from">source location</param>
/// <param name="path">target location</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Copy<TProp>(
Expression<Func<TModel, TProp>> from,
Expression<Func<TModel, TProp>> path)
{
if (from == null)
{
throw new ArgumentNullException(nameof(from));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"copy",
ExpressionHelpers.GetPath(path).ToLowerInvariant()
, ExpressionHelpers.GetPath(from).ToLowerInvariant()));
return this;
}
/// <summary>
/// Copy from a position in a list to a new location
/// </summary>
/// <typeparam name="TProp"></typeparam>
/// <param name="from">source location</param>
/// <param name="positionFrom">position</param>
/// <param name="path">target location</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Copy<TProp>(
Expression<Func<TModel, IList<TProp>>> from,
int positionFrom,
Expression<Func<TModel, TProp>> path)
{
if (from == null)
{
throw new ArgumentNullException(nameof(from));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"copy",
ExpressionHelpers.GetPath(path).ToLowerInvariant(),
ExpressionHelpers.GetPath(from).ToLowerInvariant() + "/" + positionFrom));
return this;
}
/// <summary>
/// Copy from a property to a location in a list
/// </summary>
/// <typeparam name="TProp"></typeparam>
/// <param name="from">source location</param>
/// <param name="path">target location</param>
/// <param name="positionTo">position</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Copy<TProp>(
Expression<Func<TModel, TProp>> from,
Expression<Func<TModel, IList<TProp>>> path,
int positionTo)
{
if (from == null)
{
throw new ArgumentNullException(nameof(from));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"copy",
ExpressionHelpers.GetPath(path).ToLowerInvariant() + "/" + positionTo,
ExpressionHelpers.GetPath(from).ToLowerInvariant()));
return this;
}
/// <summary>
/// Copy from a position in a list to a new location in a list
/// </summary>
/// <typeparam name="TProp"></typeparam>
/// <param name="from">source location</param>
/// <param name="positionFrom">position (source)</param>
/// <param name="path">target location</param>
/// <param name="positionTo">position (target)</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Copy<TProp>(
Expression<Func<TModel, IList<TProp>>> from,
int positionFrom,
Expression<Func<TModel, IList<TProp>>> path,
int positionTo)
{
if (from == null)
{
throw new ArgumentNullException(nameof(from));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"copy",
ExpressionHelpers.GetPath(path).ToLowerInvariant() + "/" + positionTo,
ExpressionHelpers.GetPath(from).ToLowerInvariant() + "/" + positionFrom));
return this;
}
/// <summary>
/// Copy from a position in a list to the end of another list
/// </summary>
/// <typeparam name="TProp"></typeparam>
/// <param name="from">source location</param>
/// <param name="positionFrom">position</param>
/// <param name="path">target location</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Copy<TProp>(
Expression<Func<TModel, IList<TProp>>> from,
int positionFrom,
Expression<Func<TModel, IList<TProp>>> path)
{
if (from == null)
{
throw new ArgumentNullException(nameof(from));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"copy",
ExpressionHelpers.GetPath(path).ToLowerInvariant() + "/-",
ExpressionHelpers.GetPath(from).ToLowerInvariant() + "/" + positionFrom));
return this;
}
/// <summary>
/// Copy to the end of a list
/// </summary>
/// <typeparam name="TProp"></typeparam>
/// <param name="from">source location</param>
/// <param name="path">target location</param>
/// <returns></returns>
public JsonPatchDocument<TModel> Copy<TProp>(
Expression<Func<TModel, TProp>> from,
Expression<Func<TModel, IList<TProp>>> path)
{
if (from == null)
{
throw new ArgumentNullException(nameof(from));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Operations.Add(new Operation<TModel>(
"copy",
ExpressionHelpers.GetPath(path).ToLowerInvariant() + "/-",
ExpressionHelpers.GetPath(from).ToLowerInvariant()));
return this;
}
/// <summary>
/// Apply this JsonPatchDocument
/// </summary>
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
public void ApplyTo(TModel objectToApplyTo)
{
if (objectToApplyTo == null)
{
throw new ArgumentNullException(nameof(objectToApplyTo));
}
ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction: null));
}
/// <summary>
/// Apply this JsonPatchDocument
/// </summary>
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
/// <param name="logErrorAction">Action to log errors</param>
public void ApplyTo(TModel objectToApplyTo, Action<JsonPatchError> logErrorAction)
{
if (objectToApplyTo == null)
{
throw new ArgumentNullException(nameof(objectToApplyTo));
}
ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction));
}
/// <summary>
/// Apply this JsonPatchDocument
/// </summary>
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
/// <param name="adapter">IObjectAdapter instance to use when applying</param>
public void ApplyTo(TModel objectToApplyTo, IObjectAdapter adapter)
{
if (objectToApplyTo == null)
{
throw new ArgumentNullException(nameof(objectToApplyTo));
}
if (adapter == null)
{
throw new ArgumentNullException(nameof(adapter));
}
// apply each operation in order
foreach (var op in Operations)
{
op.Apply(objectToApplyTo, adapter);
}
}
IList<Operation> IJsonPatchDocument.GetOperations()
{
var allOps = new List<Operation>();
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;
}
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.JsonPatch.Operations;
namespace Microsoft.AspNet.JsonPatch
{
/// <summary>
/// Captures error message and the related entity and the operation that caused it.
/// </summary>
public class JsonPatchError
{
/// <summary>
/// Initializes a new instance of <see cref="JsonPatchError"/>.
/// </summary>
/// <param name="affectedObject">The object that is affected by the error.</param>
/// <param name="operation">The <see cref="Operation"/> that caused the error.</param>
/// <param name="errorMessage">The error message.</param>
public JsonPatchError(
object affectedObject,
Operation operation,
string errorMessage)
{
if (errorMessage == null)
{
throw new ArgumentNullException(nameof(errorMessage));
}
AffectedObject = affectedObject;
Operation = operation;
ErrorMessage = errorMessage;
}
/// <summary>
/// Gets the object that is affected by the error.
/// </summary>
public object AffectedObject { get; }
/// <summary>
/// Gets the <see cref="Operation"/> that caused the error.
/// </summary>
public Operation Operation { get; }
/// <summary>
/// Gets the error message.
/// </summary>
public string ErrorMessage { get; }
}
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>4d55f4d8-633b-462f-a5b1-feb84bd2d534</ProjectGuid>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,74 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.JsonPatch.Adapters;
using Newtonsoft.Json;
namespace Microsoft.AspNet.JsonPatch.Operations
{
public class Operation : OperationBase
{
[JsonProperty("value")]
public object value { get; set; }
public Operation()
{
}
public Operation(string op, string path, string from, object value)
: base(op, path, from)
{
this.value = value;
}
public Operation(string op, string path, string from)
: base(op, path, from)
{
}
public void Apply(object objectToApplyTo, IObjectAdapter adapter)
{
if (objectToApplyTo == null)
{
throw new ArgumentNullException(nameof(objectToApplyTo));
}
if (adapter == null)
{
throw new ArgumentNullException(nameof(adapter));
}
switch (OperationType)
{
case OperationType.Add:
adapter.Add(this, objectToApplyTo);
break;
case OperationType.Remove:
adapter.Remove(this, objectToApplyTo);
break;
case OperationType.Replace:
adapter.Replace(this, objectToApplyTo);
break;
case OperationType.Move:
adapter.Move(this, objectToApplyTo);
break;
case OperationType.Copy:
adapter.Copy(this, objectToApplyTo);
break;
case OperationType.Test:
throw new NotSupportedException(Resources.TestOperationNotSupported);
default:
break;
}
}
public bool ShouldSerializevalue()
{
return (OperationType == OperationType.Add
|| OperationType == OperationType.Replace
|| OperationType == OperationType.Test);
}
}
}

View File

@ -0,0 +1,57 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Newtonsoft.Json;
namespace Microsoft.AspNet.JsonPatch.Operations
{
public class OperationBase
{
[JsonIgnore]
public OperationType OperationType
{
get
{
return (OperationType)Enum.Parse(typeof(OperationType), op, true);
}
}
[JsonProperty("path")]
public string path { get; set; }
[JsonProperty("op")]
public string op { get; set; }
[JsonProperty("from")]
public string from { get; set; }
public OperationBase()
{
}
public OperationBase(string op, string path, string from)
{
if (op == null)
{
throw new ArgumentNullException(nameof(op));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
this.op = op;
this.path = path;
this.from = from;
}
public bool ShouldSerializefrom()
{
return (OperationType == OperationType.Move
|| OperationType == OperationType.Copy);
}
}
}

View File

@ -0,0 +1,83 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.JsonPatch.Adapters;
namespace Microsoft.AspNet.JsonPatch.Operations
{
public class Operation<TModel> : Operation where TModel : class
{
public Operation()
{
}
public Operation(string op, string path, string from, object value)
: base(op, path, from)
{
if (op == null)
{
throw new ArgumentNullException(nameof(op));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
this.value = value;
}
public Operation(string op, string path, string from)
: base(op, path, from)
{
if (op == null)
{
throw new ArgumentNullException(nameof(op));
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
}
public void Apply(TModel objectToApplyTo, IObjectAdapter adapter)
{
if (objectToApplyTo == null)
{
throw new ArgumentNullException(nameof(objectToApplyTo));
}
if (adapter == null)
{
throw new ArgumentNullException(nameof(adapter));
}
switch (OperationType)
{
case OperationType.Add:
adapter.Add(this, objectToApplyTo);
break;
case OperationType.Remove:
adapter.Remove(this, objectToApplyTo);
break;
case OperationType.Replace:
adapter.Replace(this, objectToApplyTo);
break;
case OperationType.Move:
adapter.Move(this, objectToApplyTo);
break;
case OperationType.Copy:
adapter.Copy(this, objectToApplyTo);
break;
case OperationType.Test:
throw new NotSupportedException(Resources.TestOperationNotSupported);
default:
break;
}
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.JsonPatch.Operations
{
public enum OperationType
{
Add,
Remove,
Replace,
Move,
Copy,
Test
}
}

View File

@ -0,0 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
using System.Resources;
[assembly: AssemblyMetadata("Serviceable", "True")]
[assembly: NeutralResourcesLanguage("en-us")]

View File

@ -0,0 +1,270 @@
// <auto-generated />
namespace Microsoft.AspNet.JsonPatch
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNet.JsonPatch.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The type of the property at path '{0}' could not be determined.
/// </summary>
internal static string CannotDeterminePropertyType
{
get { return GetString("CannotDeterminePropertyType"); }
}
/// <summary>
/// The type of the property at path '{0}' could not be determined.
/// </summary>
internal static string FormatCannotDeterminePropertyType(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("CannotDeterminePropertyType"), p0);
}
/// <summary>
/// The property at '{0}' could not be read.
/// </summary>
internal static string CannotReadProperty
{
get { return GetString("CannotReadProperty"); }
}
/// <summary>
/// The property at '{0}' could not be read.
/// </summary>
internal static string FormatCannotReadProperty(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("CannotReadProperty"), p0);
}
/// <summary>
/// The property at path '{0}' could not be updated.
/// </summary>
internal static string CannotUpdateProperty
{
get { return GetString("CannotUpdateProperty"); }
}
/// <summary>
/// The property at path '{0}' could not be updated.
/// </summary>
internal static string FormatCannotUpdateProperty(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("CannotUpdateProperty"), p0);
}
/// <summary>
/// The key '{0}' was not found.
/// </summary>
internal static string DictionaryKeyNotFound
{
get { return GetString("DictionaryKeyNotFound"); }
}
/// <summary>
/// The key '{0}' was not found.
/// </summary>
internal static string FormatDictionaryKeyNotFound(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("DictionaryKeyNotFound"), p0);
}
/// <summary>
/// For operation '{0}' on array property at path '{1}', the index is larger than the array size.
/// </summary>
internal static string InvalidIndexForArrayProperty
{
get { return GetString("InvalidIndexForArrayProperty"); }
}
/// <summary>
/// For operation '{0}' on array property at path '{1}', the index is larger than the array size.
/// </summary>
internal static string FormatInvalidIndexForArrayProperty(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidIndexForArrayProperty"), p0, p1);
}
/// <summary>
/// The type '{0}' was malformed and could not be parsed.
/// </summary>
internal static string InvalidJsonPatchDocument
{
get { return GetString("InvalidJsonPatchDocument"); }
}
/// <summary>
/// The type '{0}' was malformed and could not be parsed.
/// </summary>
internal static string FormatInvalidJsonPatchDocument(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidJsonPatchDocument"), p0);
}
/// <summary>
/// For operation '{0}', the provided path is invalid for array property at path '{1}'.
/// </summary>
internal static string InvalidPathForArrayProperty
{
get { return GetString("InvalidPathForArrayProperty"); }
}
/// <summary>
/// For operation '{0}', the provided path is invalid for array property at path '{1}'.
/// </summary>
internal static string FormatInvalidPathForArrayProperty(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidPathForArrayProperty"), p0, p1);
}
/// <summary>
/// The provided string '{0}' is an invalid path.
/// </summary>
internal static string InvalidValueForPath
{
get { return GetString("InvalidValueForPath"); }
}
/// <summary>
/// The provided string '{0}' is an invalid path.
/// </summary>
internal static string FormatInvalidValueForPath(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidValueForPath"), p0);
}
/// <summary>
/// The value '{0}' is invalid for property at path '{1}'.
/// </summary>
internal static string InvalidValueForProperty
{
get { return GetString("InvalidValueForProperty"); }
}
/// <summary>
/// The value '{0}' is invalid for property at path '{1}'.
/// </summary>
internal static string FormatInvalidValueForProperty(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidValueForProperty"), p0, p1);
}
/// <summary>
/// For operation '{0}' on array property at path '{1}', the index is negative.
/// </summary>
internal static string NegativeIndexForArrayProperty
{
get { return GetString("NegativeIndexForArrayProperty"); }
}
/// <summary>
/// For operation '{0}' on array property at path '{1}', the index is negative.
/// </summary>
internal static string FormatNegativeIndexForArrayProperty(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("NegativeIndexForArrayProperty"), p0, p1);
}
/// <summary>
/// '{0}' must be of type '{1}'.
/// </summary>
internal static string ParameterMustMatchType
{
get { return GetString("ParameterMustMatchType"); }
}
/// <summary>
/// '{0}' must be of type '{1}'.
/// </summary>
internal static string FormatParameterMustMatchType(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ParameterMustMatchType"), p0, p1);
}
/// <summary>
/// The property at path '{0}' could not be added.
/// </summary>
internal static string PropertyCannotBeAdded
{
get { return GetString("PropertyCannotBeAdded"); }
}
/// <summary>
/// The property at path '{0}' could not be added.
/// </summary>
internal static string FormatPropertyCannotBeAdded(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyCannotBeAdded"), p0);
}
/// <summary>
/// The property at path '{0}' could not be removed.
/// </summary>
internal static string PropertyCannotBeRemoved
{
get { return GetString("PropertyCannotBeRemoved"); }
}
/// <summary>
/// The property at path '{0}' could not be removed.
/// </summary>
internal static string FormatPropertyCannotBeRemoved(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyCannotBeRemoved"), p0);
}
/// <summary>
/// Property does not exist at path '{0}'.
/// </summary>
internal static string PropertyDoesNotExist
{
get { return GetString("PropertyDoesNotExist"); }
}
/// <summary>
/// Property does not exist at path '{0}'.
/// </summary>
internal static string FormatPropertyDoesNotExist(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyDoesNotExist"), p0);
}
/// <summary>
/// The test operation is not supported.
/// </summary>
internal static string TestOperationNotSupported
{
get { return GetString("TestOperationNotSupported"); }
}
/// <summary>
/// The test operation is not supported.
/// </summary>
internal static string FormatTestOperationNotSupported()
{
return GetString("TestOperationNotSupported");
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CannotDeterminePropertyType" xml:space="preserve">
<value>The type of the property at path '{0}' could not be determined.</value>
</data>
<data name="CannotReadProperty" xml:space="preserve">
<value>The property at '{0}' could not be read.</value>
</data>
<data name="CannotUpdateProperty" xml:space="preserve">
<value>The property at path '{0}' could not be updated.</value>
</data>
<data name="DictionaryKeyNotFound" xml:space="preserve">
<value>The key '{0}' was not found.</value>
</data>
<data name="InvalidIndexForArrayProperty" xml:space="preserve">
<value>For operation '{0}' on array property at path '{1}', the index is larger than the array size.</value>
</data>
<data name="InvalidJsonPatchDocument" xml:space="preserve">
<value>The type '{0}' was malformed and could not be parsed.</value>
</data>
<data name="InvalidPathForArrayProperty" xml:space="preserve">
<value>For operation '{0}', the provided path is invalid for array property at path '{1}'.</value>
</data>
<data name="InvalidValueForPath" xml:space="preserve">
<value>The provided string '{0}' is an invalid path.</value>
</data>
<data name="InvalidValueForProperty" xml:space="preserve">
<value>The value '{0}' is invalid for property at path '{1}'.</value>
</data>
<data name="NegativeIndexForArrayProperty" xml:space="preserve">
<value>For operation '{0}' on array property at path '{1}', the index is negative.</value>
</data>
<data name="ParameterMustMatchType" xml:space="preserve">
<value>'{0}' must be of type '{1}'.</value>
</data>
<data name="PropertyCannotBeAdded" xml:space="preserve">
<value>The property at path '{0}' could not be added.</value>
</data>
<data name="PropertyCannotBeRemoved" xml:space="preserve">
<value>The property at path '{0}' could not be removed.</value>
</data>
<data name="PropertyDoesNotExist" xml:space="preserve">
<value>Property does not exist at path '{0}'.</value>
</data>
<data name="TestOperationNotSupported" xml:space="preserve">
<value>The test operation is not supported.</value>
</data>
</root>

View File

@ -0,0 +1,25 @@
{
"version": "1.0.0-*",
"repository": {
"type": "git",
"url": "git://github.com/aspnet/mvc"
},
"dependencies": {
"Newtonsoft.Json": "6.0.6"
},
"frameworks": {
"dnx451": { },
"dnxcore50": {
"dependencies": {
"Microsoft.CSharp": "4.0.1-beta-*",
"System.Collections.Concurrent": "4.0.11-beta-*",
"System.ComponentModel.TypeConverter": "4.0.1-beta-*",
"System.Globalization": "4.0.11-beta-*",
"System.Reflection.Extensions": "4.0.1-beta-*",
"System.Resources.ResourceManager": "4.0.1-beta-*",
"System.Runtime.Extensions": "4.0.11-beta-*",
"System.Text.Encoding.Extensions": "4.0.11-beta-*"
}
}
}
}

View File

@ -0,0 +1,569 @@
// Copyright (c) .NET Foundation. 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.Dynamic;
using Microsoft.AspNet.JsonPatch.Exceptions;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class AddOperationTests
{
[Fact]
public void AddNewPropertyShouldFailIfRootIsNotAnExpandoObject()
{
dynamic doc = new
{
Test = 1
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("NewInt", 1);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/NewInt' could not be added.",
exception.Message);
}
[Fact]
public void AddNewProperty()
{
dynamic obj = new ExpandoObject();
obj.Test = 1;
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("NewInt", 1);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(obj);
Assert.Equal(1, obj.NewInt);
Assert.Equal(1, obj.Test);
}
[Fact]
public void AddNewPropertyToNestedAnonymousObjectShouldFail()
{
dynamic doc = new
{
Test = 1,
nested = new { }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("Nested/NewInt", 1);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/Nested/NewInt' could not be added.",
exception.Message);
}
[Fact]
public void AddNewPropertyToTypedObjectShouldFail()
{
dynamic doc = new
{
Test = 1,
nested = new NestedDTO()
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("Nested/NewInt", 1);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/Nested/NewInt' could not be added.",
exception.Message);
}
[Fact]
public void AddToExistingPropertyOnNestedObject()
{
dynamic doc = new
{
Test = 1,
nested = new NestedDTO()
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("Nested/StringProperty", "A");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.nested.StringProperty);
Assert.Equal(1, doc.Test);
}
[Fact]
public void AddNewPropertyToExpandoOject()
{
dynamic doc = new
{
Test = 1,
nested = new ExpandoObject()
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("Nested/NewInt", 1);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(1, doc.nested.NewInt);
Assert.Equal(1, doc.Test);
}
[Fact]
public void AddNewPropertyToExpandoOjectInTypedObject()
{
var doc = new NestedDTO()
{
DynamicProperty = new ExpandoObject()
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("DynamicProperty/NewInt", 1);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(1, doc.DynamicProperty.NewInt);
}
[Fact]
public void AddNewPropertyToTypedObjectInExpandoObject()
{
dynamic dynamicProperty = new ExpandoObject();
dynamicProperty.StringProperty = "A";
var doc = new NestedDTO()
{
DynamicProperty = dynamicProperty
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("DynamicProperty/StringProperty", "B");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("B", doc.DynamicProperty.StringProperty);
}
[Fact]
public void AddNewPropertyToAnonymousObjectShouldFail()
{
dynamic doc = new
{
Test = 1
};
dynamic valueToAdd = new { IntValue = 1, StringValue = "test", GuidValue = Guid.NewGuid() };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("ComplexProperty", valueToAdd);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/ComplexProperty' could not be added.",
exception.Message);
}
[Fact]
public void AddResultsReplaceShouldFailOnAnonymousDueToNoSetter()
{
var doc = new
{
StringProperty = "A"
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("StringProperty", "B");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/StringProperty' could not be updated.",
exception.Message);
}
[Fact]
public void AddResultsShouldReplace()
{
dynamic doc = new ExpandoObject();
doc.StringProperty = "A";
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("StringProperty", "B");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("B", doc.StringProperty);
}
[Fact]
public void AddResultsShouldReplaceInNested()
{
dynamic doc = new ExpandoObject();
doc.InBetweenFirst = new ExpandoObject();
doc.InBetweenFirst.InBetweenSecond = new ExpandoObject();
doc.InBetweenFirst.InBetweenSecond.StringProperty = "A";
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("/InBetweenFirst/InBetweenSecond/StringProperty", "B");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("B", doc.InBetweenFirst.InBetweenSecond.StringProperty);
}
[Fact]
public void AddResultsShouldReplaceInNestedInDynamic()
{
dynamic doc = new ExpandoObject();
doc.Nested = new NestedDTO();
doc.Nested.DynamicProperty = new ExpandoObject();
doc.Nested.DynamicProperty.InBetweenFirst = new ExpandoObject();
doc.Nested.DynamicProperty.InBetweenFirst.InBetweenSecond = new ExpandoObject();
doc.Nested.DynamicProperty.InBetweenFirst.InBetweenSecond.StringProperty = "A";
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("/Nested/DynamicProperty/InBetweenFirst/InBetweenSecond/StringProperty", "B");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("B", doc.Nested.DynamicProperty.InBetweenFirst.InBetweenSecond.StringProperty);
}
[Fact]
public void ShouldNotBeAbleToAddToNonExistingPropertyThatIsNotTheRoot()
{
//Adding to a Nonexistent Target
//
// An example target JSON document:
// { "foo": "bar" }
// A JSON Patch document:
// [
// { "op": "add", "path": "/baz/bat", "value": "qux" }
// ]
// This JSON Patch document, applied to the target JSON document above,
// would result in an error (therefore, it would not be applied),
// because the "add" operation's target location that references neither
// the root of the document, nor a member of an existing object, nor a
// member of an existing array.
var doc = new NestedDTO()
{
DynamicProperty = new ExpandoObject()
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("DynamicProperty/OtherProperty/IntProperty", 1);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/DynamicProperty/OtherProperty/IntProperty' could not be added.",
exception.Message);
}
[Fact]
public void ShouldNotBeAbleToAddToNonExistingPropertyInNestedPropertyThatIsNotTheRoot()
{
//Adding to a Nonexistent Target
//
// An example target JSON document:
// { "foo": "bar" }
// A JSON Patch document:
// [
// { "op": "add", "path": "/baz/bat", "value": "qux" }
// ]
// This JSON Patch document, applied to the target JSON document above,
// would result in an error (therefore, it would not be applied),
// because the "add" operation's target location that references neither
// the root of the document, nor a member of an existing object, nor a
// member of an existing array.
var doc = new
{
Foo = "bar"
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("baz/bat", "qux");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/baz/bat' could not be added.",
exception.Message);
}
[Fact]
public void ShouldReplacePropertyWithDifferentCase()
{
dynamic doc = new ExpandoObject();
doc.StringProperty = "A";
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("stringproperty", "B");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("B", doc.StringProperty);
}
[Fact]
public void AddToList()
{
var doc = new
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/0", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, doc.IntegerList);
}
[Fact]
public void AddToListNegativePosition()
{
dynamic doc = new ExpandoObject();
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/-1", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'add' on array property at path '/IntegerList/-1', the index is negative.",
exception.Message);
}
[Fact]
public void ShouldAddToListWithDifferentCase()
{
var doc = new
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("integerlist/0", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, doc.IntegerList);
}
[Fact]
public void AddToListInvalidPositionTooLarge()
{
var doc = new
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/4", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'add' on array property at path '/IntegerList/4', the index is larger than the array size.",
exception.Message);
}
[Fact]
public void AddToListAtEndWithSerialization()
{
var doc = new
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/3", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2, 3, 4 }, doc.IntegerList);
}
[Fact]
public void AddToListAtBeginning()
{
var doc = new
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/0", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, doc.IntegerList);
}
[Fact]
public void AddToListInvalidPositionTooSmall()
{
var doc = new
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/-1", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'add' on array property at path '/IntegerList/-1', the index is negative.",
exception.Message);
}
[Fact]
public void AddToListAppend()
{
var doc = new
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/-", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2, 3, 4 }, doc.IntegerList);
}
}
}

View File

@ -0,0 +1,121 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNet.JsonPatch.Exceptions;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class AddTypedOperationTests
{
[Fact]
public void AddToListNegativePosition()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/-1", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'add' on array property at path '/IntegerList/-1', the index is negative.",
exception.Message);
}
[Fact]
public void AddToListInList()
{
var doc = new SimpleDTOWithNestedDTO()
{
ListOfSimpleDTO = new List<SimpleDTO>()
{
new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("ListOfSimpleDTO/0/IntegerList/0", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, doc.ListOfSimpleDTO[0].IntegerList);
}
[Fact]
public void AddToListInListInvalidPositionTooSmall()
{
var doc = new SimpleDTOWithNestedDTO()
{
ListOfSimpleDTO = new List<SimpleDTO>()
{
new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("ListOfSimpleDTO/-1/IntegerList/0", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/ListOfSimpleDTO/-1/IntegerList/0' could not be added.",
exception.Message);
}
[Fact]
public void AddToListInListInvalidPositionTooLarge()
{
var doc = new SimpleDTOWithNestedDTO()
{
ListOfSimpleDTO = new List<SimpleDTO>()
{
new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("ListOfSimpleDTO/20/IntegerList/0", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/ListOfSimpleDTO/20/IntegerList/0' could not be added.",
exception.Message);
}
}
}

View File

@ -0,0 +1,245 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Dynamic;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class CopyOperationTests
{
[Fact]
public void Copy()
{
dynamic doc = new ExpandoObject();
doc.StringProperty = "A";
doc.AnotherStringProperty = "B";
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("StringProperty", "AnotherStringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.AnotherStringProperty);
}
[Fact]
public void CopyInList()
{
dynamic doc = new ExpandoObject();
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("IntegerList/0", "IntegerList/1");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 1, 2, 3 }, doc.IntegerList);
}
[Fact]
public void CopyFromListToEndOfList()
{
dynamic doc = new ExpandoObject();
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("IntegerList/0", "IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2, 3, 1 }, doc.IntegerList);
}
[Fact]
public void CopyFromListToNonList()
{
dynamic doc = new ExpandoObject();
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("IntegerList/0", "IntegerValue");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(1, doc.IntegerValue);
}
[Fact]
public void CopyFromNonListToList()
{
dynamic doc = new ExpandoObject();
doc.IntegerValue = 5;
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("IntegerValue", "IntegerList/0");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 5, 1, 2, 3 }, doc.IntegerList);
}
[Fact]
public void CopyToEndOfList()
{
dynamic doc = new ExpandoObject();
doc.IntegerValue = 5;
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("IntegerValue", "IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2, 3, 5 }, doc.IntegerList);
}
[Fact]
public void NestedCopy()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
StringProperty = "A",
AnotherStringProperty = "B"
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("SimpleDTO/StringProperty", "SimpleDTO/AnotherStringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.SimpleDTO.AnotherStringProperty);
}
[Fact]
public void NestedCopyInList()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/1");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 1, 2, 3 }, doc.SimpleDTO.IntegerList);
}
[Fact]
public void NestedCopyFromListToEndOfList()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2, 3, 1 }, doc.SimpleDTO.IntegerList);
}
[Fact]
public void NestedCopyFromListToNonList()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerValue");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(1, doc.SimpleDTO.IntegerValue);
}
[Fact]
public void NestedCopyFromNonListToList()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerValue = 5,
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/0");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 5, 1, 2, 3 }, doc.SimpleDTO.IntegerList);
}
[Fact]
public void NestedCopyToEndOfList()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerValue = 5,
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2, 3, 5 }, doc.SimpleDTO.IntegerList);
}
}
}

View File

@ -0,0 +1,268 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class CopyTypedOperationTests
{
[Fact]
public void Copy()
{
var doc = new SimpleDTO()
{
StringProperty = "A",
AnotherStringProperty = "B"
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("StringProperty", "AnotherStringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.AnotherStringProperty);
}
[Fact]
public void CopyInList()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("IntegerList/0", "IntegerList/1");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 1, 2, 3 }, doc.IntegerList);
}
[Fact]
public void CopyFromListToEndOfList()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("IntegerList/0", "IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2, 3, 1 }, doc.IntegerList);
}
[Fact]
public void CopyFromListToNonList()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("IntegerList/0", "IntegerValue");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(1, doc.IntegerValue);
}
[Fact]
public void CopyFromNonListToList()
{
var doc = new SimpleDTO()
{
IntegerValue = 5,
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("IntegerValue", "IntegerList/0");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 5, 1, 2, 3 }, doc.IntegerList);
}
[Fact]
public void CopyToEndOfList()
{
var doc = new SimpleDTO()
{
IntegerValue = 5,
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("IntegerValue", "IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2, 3, 5 }, doc.IntegerList);
}
[Fact]
public void NestedCopy()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
StringProperty = "A",
AnotherStringProperty = "B"
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("SimpleDTO/StringProperty", "SimpleDTO/AnotherStringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.SimpleDTO.AnotherStringProperty);
}
[Fact]
public void NestedCopyInList()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/1");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 1, 2, 3 }, doc.SimpleDTO.IntegerList);
}
[Fact]
public void NestedCopyFromListToEndOfList()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2, 3, 1 }, doc.SimpleDTO.IntegerList);
}
[Fact]
public void NestedCopyFromListToNonList()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerValue");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(1, doc.SimpleDTO.IntegerValue);
}
[Fact]
public void NestedCopyFromNonListToList()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerValue = 5,
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/0");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 5, 1, 2, 3 }, doc.SimpleDTO.IntegerList);
}
[Fact]
public void NestedCopyToEndOfList()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerValue = 5,
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2, 3, 5 }, doc.SimpleDTO.IntegerList);
}
}
}

View File

@ -0,0 +1,338 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Dynamic;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class MoveOperationTests
{
[Fact]
public void Move()
{
dynamic doc = new ExpandoObject();
doc.StringProperty = "A";
doc.AnotherStringProperty = "B";
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("StringProperty", "AnotherStringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.AnotherStringProperty);
var cont = doc as IDictionary<string, object>;
object valueFromDictionary;
cont.TryGetValue("StringProperty", out valueFromDictionary);
Assert.Null(valueFromDictionary);
}
[Fact]
public void MoveToNonExisting()
{
dynamic doc = new ExpandoObject();
doc.StringProperty = "A";
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("StringProperty", "AnotherStringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.AnotherStringProperty);
var cont = doc as IDictionary<string, object>;
object valueFromDictionary;
cont.TryGetValue("StringProperty", out valueFromDictionary);
Assert.Null(valueFromDictionary);
}
[Fact]
public void MoveDynamicToTyped()
{
dynamic doc = new ExpandoObject();
doc.StringProperty = "A";
doc.SimpleDTO = new SimpleDTO() { AnotherStringProperty = "B" };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("StringProperty", "SimpleDTO/AnotherStringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.SimpleDTO.AnotherStringProperty);
var cont = doc as IDictionary<string, object>;
object valueFromDictionary;
cont.TryGetValue("StringProperty", out valueFromDictionary);
Assert.Null(valueFromDictionary);
}
[Fact]
public void MoveTypedToDynamic()
{
dynamic doc = new ExpandoObject();
doc.StringProperty = "A";
doc.SimpleDTO = new SimpleDTO() { AnotherStringProperty = "B" };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("SimpleDTO/AnotherStringProperty", "StringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("B", doc.StringProperty);
Assert.Equal(null, doc.SimpleDTO.AnotherStringProperty);
}
[Fact]
public void NestedMove()
{
dynamic doc = new ExpandoObject();
doc.Nested = new SimpleDTO()
{
StringProperty = "A",
AnotherStringProperty = "B"
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("Nested/StringProperty", "Nested/AnotherStringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.Nested.AnotherStringProperty);
Assert.Equal(null, doc.Nested.StringProperty);
}
[Fact]
public void MoveInList()
{
dynamic doc = new ExpandoObject();
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("IntegerList/0", "IntegerList/1");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 2, 1, 3 }, doc.IntegerList);
}
[Fact]
public void NestedMoveInList()
{
dynamic doc = new ExpandoObject();
doc.Nested = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("Nested/IntegerList/0", "Nested/IntegerList/1");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 2, 1, 3 }, doc.Nested.IntegerList);
}
[Fact]
public void MoveFromListToEndOfList()
{
dynamic doc = new ExpandoObject();
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("IntegerList/0", "IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 2, 3, 1 }, doc.IntegerList);
}
[Fact]
public void NestedMoveFromListToEndOfList()
{
dynamic doc = new ExpandoObject();
doc.Nested = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("Nested/IntegerList/0", "Nested/IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 2, 3, 1 }, doc.Nested.IntegerList);
}
[Fact]
public void MoveFomListToNonList()
{
dynamic doc = new ExpandoObject();
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("IntegerList/0", "IntegerValue");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 2, 3 }, doc.IntegerList);
Assert.Equal(1, doc.IntegerValue);
}
[Fact]
public void NestedMoveFomListToNonList()
{
dynamic doc = new ExpandoObject();
doc.Nested = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("Nested/IntegerList/0", "Nested/IntegerValue");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 2, 3 }, doc.Nested.IntegerList);
Assert.Equal(1, doc.Nested.IntegerValue);
}
[Fact]
public void MoveFromNonListToList()
{
dynamic doc = new ExpandoObject();
doc.IntegerValue = 5;
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("IntegerValue", "IntegerList/0");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
var cont = doc as IDictionary<string, object>;
object valueFromDictionary;
cont.TryGetValue("IntegerValue", out valueFromDictionary);
Assert.Null(valueFromDictionary);
Assert.Equal(new List<int>() { 5, 1, 2, 3 }, doc.IntegerList);
}
[Fact]
public void NestedMoveFromNonListToList()
{
dynamic doc = new ExpandoObject();
doc.Nested = new SimpleDTO()
{
IntegerValue = 5,
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("Nested/IntegerValue", "Nested/IntegerList/0");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(0, doc.Nested.IntegerValue);
Assert.Equal(new List<int>() { 5, 1, 2, 3 }, doc.Nested.IntegerList);
}
[Fact]
public void MoveToEndOfList()
{
dynamic doc = new ExpandoObject();
doc.IntegerValue = 5;
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("IntegerValue", "IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
var cont = doc as IDictionary<string, object>;
object valueFromDictionary;
cont.TryGetValue("IntegerValue", out valueFromDictionary);
Assert.Null(valueFromDictionary);
Assert.Equal(new List<int>() { 1, 2, 3, 5 }, doc.IntegerList);
}
[Fact]
public void NestedMoveToEndOfList()
{
dynamic doc = new ExpandoObject();
doc.Nested = new SimpleDTO()
{
IntegerValue = 5,
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("Nested/IntegerValue", "Nested/IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(0, doc.Nested.IntegerValue);
Assert.Equal(new List<int>() { 1, 2, 3, 5 }, doc.Nested.IntegerList);
}
}
}

View File

@ -0,0 +1,138 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class MoveTypedOperationTests
{
[Fact]
public void Move()
{
var doc = new SimpleDTO()
{
StringProperty = "A",
AnotherStringProperty = "B"
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("StringProperty", "AnotherStringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.AnotherStringProperty);
Assert.Equal(null, doc.StringProperty);
}
[Fact]
public void MoveInList()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("IntegerList/0", "IntegerList/1");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 2, 1, 3 }, doc.IntegerList);
}
[Fact]
public void MoveFromListToEndOfList()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("IntegerList/0", "IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 2, 3, 1 }, doc.IntegerList);
}
[Fact]
public void MoveFomListToNonList()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("IntegerList/0", "IntegerValue");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 2, 3 }, doc.IntegerList);
Assert.Equal(1, doc.IntegerValue);
}
[Fact]
public void MoveFromNonListToList()
{
var doc = new SimpleDTO()
{
IntegerValue = 5,
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("IntegerValue", "IntegerList/0");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(0, doc.IntegerValue);
Assert.Equal(new List<int>() { 5, 1, 2, 3 }, doc.IntegerList);
}
[Fact]
public void MoveToEndOfList()
{
var doc = new SimpleDTO()
{
IntegerValue = 5,
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Move("IntegerValue", "IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(0, doc.IntegerValue);
Assert.Equal(new List<int>() { 1, 2, 3, 5 }, doc.IntegerList);
}
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class NestedDTO
{
public string StringProperty { get; set; }
public dynamic DynamicProperty { get; set; }
}
}

View File

@ -0,0 +1,95 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.JsonPatch.Exceptions;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class PatchDocumentTests
{
[Fact]
public void InvalidPathAtBeginningShouldThrowException()
{
JsonPatchDocument patchDoc = new JsonPatchDocument();
var exception = Assert.Throws<JsonPatchException>(() =>
{
patchDoc.Add("//NewInt", 1);
});
Assert.Equal(
"The provided string '//NewInt' is an invalid path.",
exception.Message);
}
[Fact]
public void InvalidPathAtEndShouldThrowException()
{
JsonPatchDocument patchDoc = new JsonPatchDocument();
var exception = Assert.Throws<JsonPatchException>(() =>
{
patchDoc.Add("NewInt//", 1);
});
Assert.Equal(
"The provided string 'NewInt//' is an invalid path.",
exception.Message);
}
[Fact]
public void InvalidPathWithDotShouldThrowException()
{
JsonPatchDocument patchDoc = new JsonPatchDocument();
var exception = Assert.Throws<JsonPatchException>(() =>
{
patchDoc.Add("NewInt.Test", 1);
});
Assert.Equal(
"The provided string 'NewInt.Test' is an invalid path.",
exception.Message);
}
[Fact]
public void NonGenericPatchDocToGenericMustSerialize()
{
var doc = new SimpleDTO()
{
StringProperty = "A",
AnotherStringProperty = "B"
};
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("StringProperty", "AnotherStringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<SimpleDTO>>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.AnotherStringProperty);
}
[Fact]
public void GenericPatchDocToNonGenericMustSerialize()
{
var doc = new SimpleDTO()
{
StringProperty = "A",
AnotherStringProperty = "B"
};
JsonPatchDocument<SimpleDTO> patchDocTyped = new JsonPatchDocument<SimpleDTO>();
patchDocTyped.Copy<string>(o => o.StringProperty, o => o.AnotherStringProperty);
JsonPatchDocument patchDocUntyped = new JsonPatchDocument();
patchDocUntyped.Copy("StringProperty", "AnotherStringProperty");
var serializedTyped = JsonConvert.SerializeObject(patchDocTyped);
var serializedUntyped = JsonConvert.SerializeObject(patchDocUntyped);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serializedTyped);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.AnotherStringProperty);
}
}
}

View File

@ -0,0 +1,302 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Dynamic;
using Microsoft.AspNet.JsonPatch.Exceptions;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class RemoveOperationTests
{
[Fact]
public void RemovePropertyShouldFailIfRootIsAnonymous()
{
dynamic doc = new
{
Test = 1
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("Test");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/Test' could not be updated.",
exception.Message);
}
[Fact]
public void RemovePropertyShouldFailIfItDoesntExist()
{
dynamic doc = new ExpandoObject();
doc.Test = 1;
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("NonExisting");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/NonExisting' could not be removed.",
exception.Message);
}
[Fact]
public void RemovePropertyFromExpandoObject()
{
dynamic obj = new ExpandoObject();
obj.Test = 1;
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("Test");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(obj);
var cont = obj as IDictionary<string, object>;
object valueFromDictionary;
cont.TryGetValue("Test", out valueFromDictionary);
Assert.Null(valueFromDictionary);
}
[Fact]
public void RemovePropertyFromExpandoObjectMixedCase()
{
dynamic obj = new ExpandoObject();
obj.Test = 1;
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("test");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(obj);
var cont = obj as IDictionary<string, object>;
object valueFromDictionary;
cont.TryGetValue("Test", out valueFromDictionary);
Assert.Null(valueFromDictionary);
}
[Fact]
public void RemoveNestedPropertyFromExpandoObject()
{
dynamic obj = new ExpandoObject();
obj.Test = new ExpandoObject();
obj.Test.AnotherTest = "A";
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("Test");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(obj);
var cont = obj as IDictionary<string, object>;
object valueFromDictionary;
cont.TryGetValue("Test", out valueFromDictionary);
Assert.Null(valueFromDictionary);
}
[Fact]
public void RemoveNestedPropertyFromExpandoObjectMixedCase()
{
dynamic obj = new ExpandoObject();
obj.Test = new ExpandoObject();
obj.Test.AnotherTest = "A";
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("test");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(obj);
var cont = obj as IDictionary<string, object>;
object valueFromDictionary;
cont.TryGetValue("Test", out valueFromDictionary);
Assert.Null(valueFromDictionary);
}
[Fact]
public void NestedRemove()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
StringProperty = "A"
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/StringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(null, doc.SimpleDTO.StringProperty);
}
[Fact]
public void NestedRemoveMixedCase()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
StringProperty = "A"
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("Simpledto/stringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(null, doc.SimpleDTO.StringProperty);
}
[Fact]
public void NestedRemoveFromList()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/2");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2 }, doc.SimpleDTO.IntegerList);
}
[Fact]
public void NestedRemoveFromListMixedCase()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/Integerlist/2");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2 }, doc.SimpleDTO.IntegerList);
}
[Fact]
public void NestedRemoveFromListInvalidPositionTooLarge()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/3");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'remove' on array property at path '/SimpleDTO/IntegerList/3', the index is larger than the array size.",
exception.Message);
}
[Fact]
public void NestedRemoveFromListInvalidPositionTooSmall()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/-1");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'remove' on array property at path '/SimpleDTO/IntegerList/-1', the index is negative.",
exception.Message);
}
[Fact]
public void NestedRemoveFromEndOfList()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2 }, doc.SimpleDTO.IntegerList);
}
}
}

View File

@ -0,0 +1,244 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNet.JsonPatch.Exceptions;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class RemoveTypedOperationTests
{
[Fact]
public void Remove()
{
var doc = new SimpleDTO()
{
StringProperty = "A"
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("StringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(null, doc.StringProperty);
}
[Fact]
public void RemoveFromList()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("IntegerList/2");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2 }, doc.IntegerList);
}
[Fact]
public void RemoveFromListInvalidPositionTooLarge()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("IntegerList/3");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'remove' on array property at path '/IntegerList/3', the index is larger than the array size.",
exception.Message);
}
[Fact]
public void RemoveFromListInvalidPositionTooSmall()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("IntegerList/-1");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'remove' on array property at path '/IntegerList/-1', the index is negative.",
exception.Message);
}
[Fact]
public void RemoveFromEndOfList()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2 }, doc.IntegerList);
}
[Fact]
public void NestedRemove()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
StringProperty = "A"
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/StringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(null, doc.SimpleDTO.StringProperty);
}
[Fact]
public void NestedRemoveFromList()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/2");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2 }, doc.SimpleDTO.IntegerList);
}
[Fact]
public void NestedRemoveFromListInvalidPositionTooLarge()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/3");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'remove' on array property at path '/SimpleDTO/IntegerList/3', the index is larger than the array size.",
exception.Message);
}
[Fact]
public void NestedRemoveFromListInvalidPositionTooSmall()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/-1");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'remove' on array property at path '/SimpleDTO/IntegerList/-1', the index is negative.",
exception.Message);
}
[Fact]
public void NestedRemoveFromEndOfList()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2 }, doc.SimpleDTO.IntegerList);
}
}
}

View File

@ -0,0 +1,242 @@
// Copyright (c) .NET Foundation. 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.Collections.ObjectModel;
using System.Dynamic;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class ReplaceOperationTests
{
[Fact]
public void ReplaceGuidTest()
{
dynamic doc = new SimpleDTO()
{
GuidValue = Guid.NewGuid()
};
var newGuid = Guid.NewGuid();
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("GuidValue", newGuid);
// serialize & deserialize
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserizalized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserizalized.ApplyTo(doc);
Assert.Equal(newGuid, doc.GuidValue);
}
[Fact]
public void ReplaceGuidTestExpandoObject()
{
dynamic doc = new ExpandoObject();
doc.GuidValue = Guid.NewGuid();
var newGuid = Guid.NewGuid();
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("GuidValue", newGuid);
// serialize & deserialize
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserizalized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserizalized.ApplyTo(doc);
Assert.Equal(newGuid, doc.GuidValue);
}
[Fact]
public void ReplaceGuidTestExpandoObjectInAnonymous()
{
dynamic nestedObject = new ExpandoObject();
nestedObject.GuidValue = Guid.NewGuid();
dynamic doc = new
{
NestedObject = nestedObject
};
var newGuid = Guid.NewGuid();
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("nestedobject/GuidValue", newGuid);
// serialize & deserialize
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserizalized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserizalized.ApplyTo(doc);
Assert.Equal(newGuid, doc.NestedObject.GuidValue);
}
[Fact]
public void ReplaceNestedObjectTest()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerValue = 5,
IntegerList = new List<int>() { 1, 2, 3 }
};
var newDTO = new SimpleDTO()
{
DoubleValue = 1
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("SimpleDTO", newDTO);
// serialize & deserialize
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(1, doc.SimpleDTO.DoubleValue);
Assert.Equal(0, doc.SimpleDTO.IntegerValue);
Assert.Equal(null, doc.SimpleDTO.IntegerList);
}
[Fact]
public void ReplaceInList()
{
dynamic doc = new ExpandoObject();
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("IntegerList/0", 5);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 5, 2, 3 }, doc.IntegerList);
}
[Fact]
public void ReplaceFullList()
{
dynamic doc = new ExpandoObject();
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("IntegerList", new List<int>() { 4, 5, 6 });
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 4, 5, 6 }, doc.IntegerList);
}
[Fact]
public void ReplaceInListInList()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTOList = new List<SimpleDTO>() {
new SimpleDTO() {
IntegerList = new List<int>(){1,2,3}
}};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("SimpleDTOList/0/IntegerList/0", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(4, doc.SimpleDTOList[0].IntegerList[0]);
}
[Fact]
public void ReplaceInListInListAtEnd()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTOList = new List<SimpleDTO>() {
new SimpleDTO() {
IntegerList = new List<int>(){1,2,3}
}};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("SimpleDTOList/0/IntegerList/-", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(4, doc.SimpleDTOList[0].IntegerList[2]);
}
[Fact]
public void ReplaceFullListFromEnumerable()
{
dynamic doc = new ExpandoObject();
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("IntegerList", new List<int>() { 4, 5, 6 });
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 4, 5, 6 }, doc.IntegerList);
}
[Fact]
public void ReplaceFullListWithCollection()
{
dynamic doc = new ExpandoObject();
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("IntegerList", new Collection<int>() { 4, 5, 6 });
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 4, 5, 6 }, doc.IntegerList);
}
[Fact]
public void ReplaceAtEndOfList()
{
dynamic doc = new ExpandoObject();
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("IntegerList/-", 5);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2, 5 }, doc.IntegerList);
}
}
}

View File

@ -0,0 +1,191 @@
// Copyright (c) .NET Foundation. 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.Collections.ObjectModel;
using System.Linq;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class ReplaceTypedOperationTests
{
[Fact]
public void ReplaceGuidTest()
{
var doc = new SimpleDTO()
{
GuidValue = Guid.NewGuid()
};
var newGuid = Guid.NewGuid();
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("GuidValue", newGuid);
// serialize & deserialize
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserizalized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserizalized.ApplyTo(doc);
Assert.Equal(newGuid, doc.GuidValue);
}
[Fact]
public void SerializeAndReplaceNestedObjectTest()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerValue = 5,
IntegerList = new List<int>() { 1, 2, 3 }
}
};
var newDTO = new SimpleDTO()
{
DoubleValue = 1
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("SimpleDTO", newDTO);
// serialize & deserialize
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(1, doc.SimpleDTO.DoubleValue);
Assert.Equal(0, doc.SimpleDTO.IntegerValue);
Assert.Equal(null, doc.SimpleDTO.IntegerList);
}
[Fact]
public void ReplaceInList()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("IntegerList/0", 5);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 5, 2, 3 }, doc.IntegerList);
}
[Fact]
public void ReplaceFullList()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("IntegerList", new List<int>() { 4, 5, 6 });
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 4, 5, 6 }, doc.IntegerList);
}
[Fact]
public void ReplaceInListInList()
{
var doc = new SimpleDTO()
{
SimpleDTOList = new List<SimpleDTO>() {
new SimpleDTO() {
IntegerList = new List<int>(){1,2,3}
}}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("SimpleDTOList/0/IntegerList/0", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(4, doc.SimpleDTOList.First().IntegerList.First());
}
[Fact]
public void ReplaceFullListFromEnumerable()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("IntegerList", new List<int>() { 4, 5, 6 });
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 4, 5, 6 }, doc.IntegerList);
}
[Fact]
public void ReplaceFullListWithCollection()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("IntegerList", new Collection<int>() { 4, 5, 6 });
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 4, 5, 6 }, doc.IntegerList);
}
[Fact]
public void ReplaceAtEndOfList()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("IntegerList/-", 5);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2, 5 }, doc.IntegerList);
}
}
}

View File

@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. 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;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class SimpleDTO
{
public List<SimpleDTO> SimpleDTOList { get; set; }
public List<int> IntegerList { get; set; }
public int IntegerValue { get; set; }
public string StringProperty { get; set; }
public string AnotherStringProperty { get; set; }
public decimal DecimalValue { get; set; }
public double DoubleValue { get; set; }
public float FloatValue { get; set; }
public Guid GuidValue { get; set; }
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class SimpleDTOWithNestedDTO
{
public int IntegerValue { get; set; }
public NestedDTO NestedDTO { get; set; }
public SimpleDTO SimpleDTO { get; set; }
public List<SimpleDTO> ListOfSimpleDTO { get; set; }
public SimpleDTOWithNestedDTO()
{
NestedDTO = new NestedDTO();
SimpleDTO = new SimpleDTO();
ListOfSimpleDTO = new List<SimpleDTO>();
}
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>81c20848-e063-4e12-ac40-0b55a532c16c</ProjectGuid>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.JsonPatch.Test
{
public class NestedDTO
{
public string StringProperty { get; set; }
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. 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;
using System.Collections.Generic;
namespace Microsoft.AspNet.JsonPatch.Test
{
public class SimpleDTO
{
public List<int> IntegerList { get; set; }
public IList<int> IntegerIList { get; set; }
public int IntegerValue { get; set; }
public string StringProperty { get; set; }
public string AnotherStringProperty { get; set; }
public decimal DecimalValue { get; set; }
public double DoubleValue { get; set; }
public float FloatValue { get; set; }
public Guid GuidValue { get; set; }
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNet.JsonPatch.Test
{
public class SimpleDTOWithNestedDTO
{
public int IntegerValue { get; set; }
public NestedDTO NestedDTO { get; set; }
public SimpleDTO SimpleDTO { get; set; }
public List<SimpleDTO> SimpleDTOList { get; set; }
public IList<SimpleDTO> SimpleDTOIList { get; set; }
public SimpleDTOWithNestedDTO()
{
this.NestedDTO = new NestedDTO();
this.SimpleDTO = new SimpleDTO();
this.SimpleDTOList = new List<SimpleDTO>();
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.JsonPatch.Test
{
public class TestErrorLogger<T> where T: class
{
public string ErrorMessage { get; set; }
public void LogErrorMessage(JsonPatchError patchError)
{
ErrorMessage = patchError.ErrorMessage;
}
}
}

View File

@ -0,0 +1,18 @@
{
"compilationOptions": {
"warningsAsErrors": true
},
"dependencies": {
"Microsoft.AspNet.JsonPatch": "1.0.0-*",
"Microsoft.AspNet.Testing": "1.0.0-*",
"Moq": "4.2.1312.1622",
"Newtonsoft.Json": "6.0.6",
"xunit.runner.aspnet": "2.0.0-aspnet-*"
},
"commands": {
"test": "xunit.runner.aspnet"
},
"frameworks": {
"dnx451": { }
}
}