├── .gitattributes ├── .gitignore ├── LICENSE.md ├── Marvin.JsonPatch.Dynamic.sln ├── README.md ├── src └── Marvin.JsonPatch.Dynamic │ ├── Adapters │ ├── IObjectAdapter.cs │ └── ObjectAdapter.cs │ ├── Converters │ └── JsonPatchDocumentConverter.cs │ ├── Helpers │ ├── ActualPropertyPathResult.cs │ ├── ConversionResult.cs │ ├── ExpandoObjectDictionaryExtensions.cs │ ├── GetValueResult.cs │ ├── JsonPatchProperty.cs │ ├── ObjectTreeAnalysisResult.cs │ ├── PathHelpers.cs │ ├── PropertyHelpers.cs │ └── RemovedPropertyTypeResult.cs │ ├── JsonPatchDocument.cs │ ├── Marvin.JsonPatch.Dynamic.csproj │ ├── Marvin.JsonPatch.Dynamic.nuspec │ ├── Operations │ └── OperationExtensions.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── app.config │ └── packages.config └── test └── Marvin.JsonPatch.Dynamic.XUnitTest ├── AddOperationTests.cs ├── AddTypedOperationTests.cs ├── CaseTransformTypeTests.cs ├── CopyOperationTests.cs ├── CopyTypedOperationTests.cs ├── JsonPropertyDTO.cs ├── JsonPropertyTests.cs ├── JsonPropertyWithAnotherNameDTO.cs ├── Marvin.JsonPatch.Dynamic.XUnitTest.csproj ├── MoveOperationTests.cs ├── MoveTypedOperationTests.cs ├── NestedDTO.cs ├── PatchDocumentTests.cs ├── Properties └── AssemblyInfo.cs ├── RemoveOperationTests.cs ├── RemoveTypedOperationTests.cs ├── ReplaceOperationTests.cs ├── ReplaceTypedOperationTests.cs ├── SimpleDTO.cs ├── SimpleDTOWithNestedDTO.cs └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | 18 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 19 | !packages/*/build/ 20 | 21 | # MSTest test Results 22 | [Tt]est[Rr]esult*/ 23 | [Bb]uild[Ll]og.* 24 | 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.tmp_proj 41 | *.log 42 | *.vspscc 43 | *.vssscc 44 | .builds 45 | *.pidb 46 | *.log 47 | *.scc 48 | 49 | # Visual C++ cache files 50 | ipch/ 51 | *.aps 52 | *.ncb 53 | *.opensdf 54 | *.sdf 55 | *.cachefile 56 | 57 | # Visual Studio profiler 58 | *.psess 59 | *.vsp 60 | *.vspx 61 | 62 | # Guidance Automation Toolkit 63 | *.gpState 64 | 65 | # ReSharper is a .NET coding add-in 66 | _ReSharper*/ 67 | *.[Rr]e[Ss]harper 68 | 69 | # TeamCity is a build add-in 70 | _TeamCity* 71 | 72 | # DotCover is a Code Coverage Tool 73 | *.dotCover 74 | 75 | # NCrunch 76 | *.ncrunch* 77 | .*crunch*.local.xml 78 | 79 | # Installshield output folder 80 | [Ee]xpress/ 81 | 82 | # DocProject is a documentation generator add-in 83 | DocProject/buildhelp/ 84 | DocProject/Help/*.HxT 85 | DocProject/Help/*.HxC 86 | DocProject/Help/*.hhc 87 | DocProject/Help/*.hhk 88 | DocProject/Help/*.hhp 89 | DocProject/Help/Html2 90 | DocProject/Help/html 91 | 92 | # Click-Once directory 93 | publish/ 94 | 95 | # Publish Web Output 96 | *.Publish.xml 97 | 98 | # NuGet Packages Directory 99 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 100 | #packages/ 101 | 102 | # Windows Azure Build Output 103 | csx 104 | *.build.csdef 105 | 106 | # Windows Store app package directory 107 | AppPackages/ 108 | 109 | # Others 110 | sql/ 111 | *.Cache 112 | ClientBin/ 113 | [Ss]tyle[Cc]op.* 114 | ~$* 115 | *~ 116 | *.dbmdl 117 | *.[Pp]ublish.xml 118 | *.pfx 119 | *.publishsettings 120 | 121 | # RIA/Silverlight projects 122 | Generated_Code/ 123 | 124 | # Backup & report files from converting an old project file to a newer 125 | # Visual Studio version. Backup files are not needed, because we have git ;-) 126 | _UpgradeReport_Files/ 127 | Backup*/ 128 | UpgradeLog*.XML 129 | UpgradeLog*.htm 130 | 131 | # SQL Server files 132 | App_Data/*.mdf 133 | App_Data/*.ldf 134 | 135 | 136 | #LightSwitch generated files 137 | GeneratedArtifacts/ 138 | _Pvt_Extensions/ 139 | ModelManifest.xml 140 | 141 | # ========================= 142 | # Windows detritus 143 | # ========================= 144 | 145 | # Windows image file caches 146 | Thumbs.db 147 | ehthumbs.db 148 | 149 | # Folder config file 150 | Desktop.ini 151 | 152 | # Recycle Bin used on file shares 153 | $RECYCLE.BIN/ 154 | 155 | # Mac desktop service store files 156 | .DS_Store 157 | packages/ 158 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kevin Dockx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Marvin.JsonPatch.Dynamic.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Marvin.JsonPatch.Dynamic", "src\Marvin.JsonPatch.Dynamic\Marvin.JsonPatch.Dynamic.csproj", "{C8DC479F-2216-4B8F-BC26-23E88B6DA6D5}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Marvin.JsonPatch.Dynamic.XUnitTest", "test\Marvin.JsonPatch.Dynamic.XUnitTest\Marvin.JsonPatch.Dynamic.XUnitTest.csproj", "{5E292FEB-38D6-40F4-B6BC-29F7F9AF255A}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {C8DC479F-2216-4B8F-BC26-23E88B6DA6D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {C8DC479F-2216-4B8F-BC26-23E88B6DA6D5}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {C8DC479F-2216-4B8F-BC26-23E88B6DA6D5}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {C8DC479F-2216-4B8F-BC26-23E88B6DA6D5}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {5E292FEB-38D6-40F4-B6BC-29F7F9AF255A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {5E292FEB-38D6-40F4-B6BC-29F7F9AF255A}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {5E292FEB-38D6-40F4-B6BC-29F7F9AF255A}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {5E292FEB-38D6-40F4-B6BC-29F7F9AF255A}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON Patch for .NET Dynamic Typing Support 2 | Support for dynamically typed objects for Marvin.JsonPatch (Json Patch Document RFC 6902 implementation for .NET) 3 | 4 | Marvin.JsonPatch.Dynamic adds support for dynamically typed objects to Marvin.JsonPatch (https://github.com/KevinDockx/JsonPatch). 5 | 6 | Marvin.JsonPatch was built to work on staticly typed objects, which is great for most cases. Yet sometimes you'll want to create a patch document without having a class to start from (for example: when integrating with a backend that's out of your control, or when you don't have a shared DTO layer), or you'll want to apply a JsonPatchDocument to a dynamically typed object. 7 | 8 | That's what this component takes care of. It extends Marvin.JsonPatch with new methods on JsonPatchDocument, and it allows you to apply the JsonPatchDocument to dynamically typed objects. 9 | 10 | At client level (on full .NET), you can now create JsonPatchDocuments without knowing the class it will be applied to. That's a typical use case when you're working with dynamically typed objects at API level: you might not have a shared DTO layer between your API and the client. 11 | 12 | 13 | ```csharp 14 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 15 | patchDoc.Add("Age", 34); 16 | 17 | // serialize 18 | var serializedItemToUpdate = JsonConvert.SerializeObject(patchDoc); 19 | 20 | // create the patch request 21 | var method = new HttpMethod("PATCH"); 22 | var request = new HttpRequestMessage(method, "api/persons/" + id) 23 | { 24 | Content = new StringContent(serializedItemToUpdate, 25 | System.Text.Encoding.Unicode, "application/json") 26 | }; 27 | 28 | // send it, using an HttpClient instance 29 | client.SendAsync(request); 30 | ``` 31 | 32 | Applying the previously created JsonPatchDocument at client side at API level, using the ObjectAdapter from the Marvin.JsonPatch.Dynamic namespace, will result in an extra property on the Person object. The non-generic JsonPatchDocument used this adapter by default, but if you wish, you can also pass in an instance of Marvin.JsonPatch.Dynamic.ObjectAdapter to the ApplyTo-method of the generic JsonPatchDocument. 33 | 34 | ```csharp 35 | [Route("api/expenses/{id}")] 36 | [HttpPatch] 37 | public IHttpActionResult Patch(int id, [FromBody]JsonPatchDocument personPatchDocument) 38 | { 39 | // get the person 40 | dynamic person = _repository.GetDynamicPersonWithNameAndFirstName(id); 41 | 42 | // apply the patch document 43 | personPatchDocument.ApplyTo(person); 44 | 45 | // person now has an extra property, Age, with value 34 46 | 47 | } 48 | ``` 49 | 50 | 51 | As Marvin.JsonPatch.Dynamic is an extension to Marvin.JsonPatch, it can also be used to target any object Marvin.JsonPatch can target. 52 | 53 | The fact that you can now also work with dynamics, which allows manipulating the property dictionary at runtime, means that you can now effectively add/remove properties to/from an object at runtime by applying the JsonPatchDocument to it. 54 | 55 | Implemented are Add, Remove, Move, Replace and Copy. 56 | 57 | It also works for nested objects, arrays and objects in an array. 58 | 59 | For example, to add a property Street to an object Address for the second person in an array of people, this should be your patch document: 60 | 61 | ```csharp 62 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 63 | patchDoc.Add("People/1/Address/Street", "My street"); 64 | ``` 65 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Adapters/IObjectAdapter.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | 7 | namespace Marvin.JsonPatch.Dynamic.Adapters 8 | { 9 | public interface IObjectAdapter 10 | { 11 | void Add(Marvin.JsonPatch.Operations.Operation operation, dynamic objectToApplyTo); 12 | void Copy(Marvin.JsonPatch.Operations.Operation operation, dynamic objectToApplyTo); 13 | void Move(Marvin.JsonPatch.Operations.Operation operation, dynamic objectToApplyTo); 14 | void Remove(Marvin.JsonPatch.Operations.Operation operation, dynamic objectToApplyTo); 15 | void Replace(Marvin.JsonPatch.Operations.Operation operation, dynamic objectToApplyTo); 16 | void Test(Marvin.JsonPatch.Operations.Operation operation, dynamic objectToApplyTo); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Converters/JsonPatchDocumentConverter.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Marvin.JsonPatch.Exceptions; 7 | using Marvin.JsonPatch.Operations; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Linq; 10 | using System; 11 | using System.Collections.Generic; 12 | 13 | namespace Marvin.JsonPatch.Dynamic.Converters 14 | { 15 | public class JsonPatchDocumentConverter : JsonConverter 16 | { 17 | public override bool CanConvert(Type objectType) 18 | { 19 | return true; 20 | } 21 | 22 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 23 | { 24 | if (objectType != typeof(JsonPatchDocument)) 25 | { 26 | throw new ArgumentException("ObjectType must be of type JsonPatchDocument", "objectType"); 27 | } 28 | 29 | try 30 | { 31 | if (reader.TokenType == JsonToken.Null) 32 | return null; 33 | 34 | // load jObject 35 | JArray jObject = JArray.Load(reader); 36 | 37 | // Create target object for Json => list of operations 38 | var targetOperations = new List(); 39 | 40 | // Create a new reader for this jObject, and set all properties 41 | // to match the original reader. 42 | JsonReader jObjectReader = jObject.CreateReader(); 43 | jObjectReader.Culture = reader.Culture; 44 | jObjectReader.DateParseHandling = reader.DateParseHandling; 45 | jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling; 46 | jObjectReader.FloatParseHandling = reader.FloatParseHandling; 47 | 48 | // Populate the object properties 49 | serializer.Populate(jObjectReader, targetOperations); 50 | 51 | // container target: the JsonPatchDocument. 52 | var container = Activator.CreateInstance(objectType, targetOperations); 53 | 54 | return container; 55 | } 56 | catch (Exception ex) 57 | { 58 | throw new JsonPatchException("The JsonPatchDocument was malformed and could not be parsed.", ex); 59 | } 60 | } 61 | 62 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 63 | { 64 | if (value is IJsonPatchDocument) 65 | { 66 | var jsonPatchDoc = (IJsonPatchDocument)value; 67 | var lst = jsonPatchDoc.GetOperations(); 68 | 69 | // write out the operations, no envelope 70 | serializer.Serialize(writer, lst); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Helpers/ActualPropertyPathResult.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | namespace Marvin.JsonPatch.Dynamic.Helpers 7 | { 8 | internal class ActualPropertyPathResult 9 | { 10 | public int NumericEnd { get; private set; } 11 | public string PathToProperty { get; set; } 12 | public bool ExecuteAtEnd { get; set; } 13 | 14 | public ActualPropertyPathResult( 15 | int numericEnd, 16 | string pathToProperty, 17 | bool executeAtEnd) 18 | { 19 | NumericEnd = numericEnd; 20 | PathToProperty = pathToProperty; 21 | ExecuteAtEnd = executeAtEnd; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Helpers/ConversionResult.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | namespace Marvin.JsonPatch.Dynamic.Helpers 7 | { 8 | internal class ConversionResult 9 | { 10 | public bool CanBeConverted { get; private set; } 11 | public object ConvertedInstance { get; private set; } 12 | 13 | public ConversionResult(bool canBeConverted, object convertedInstance) 14 | { 15 | CanBeConverted = canBeConverted; 16 | ConvertedInstance = convertedInstance; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Helpers/ExpandoObjectDictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | 9 | namespace Marvin.JsonPatch.Dynamic.Helpers 10 | { 11 | internal static class ExpandoObjectDictionaryExtensions 12 | { 13 | internal static void SetValueForCaseInsensitiveKey(this IDictionary propertyDictionary, 14 | string key, object value) 15 | { 16 | foreach (KeyValuePair kvp in propertyDictionary) 17 | { 18 | if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase)) 19 | { 20 | propertyDictionary[kvp.Key] = value; 21 | break; 22 | } 23 | } 24 | } 25 | 26 | internal static void RemoveValueForCaseInsensitiveKey(this IDictionary propertyDictionary, 27 | string key) 28 | { 29 | string realKey = null; 30 | foreach (KeyValuePair kvp in propertyDictionary) 31 | { 32 | if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase)) 33 | { 34 | realKey = kvp.Key; 35 | break; 36 | } 37 | } 38 | 39 | if (realKey != null) 40 | { 41 | propertyDictionary.Remove(realKey); 42 | } 43 | } 44 | 45 | 46 | internal static object GetValueForCaseInsensitiveKey(this IDictionary propertyDictionary, 47 | string key) 48 | { 49 | foreach (KeyValuePair kvp in propertyDictionary) 50 | { 51 | if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase)) 52 | { 53 | return kvp.Value; 54 | } 55 | } 56 | 57 | throw new ArgumentException("Key not found in dictionary"); 58 | } 59 | 60 | 61 | internal static bool ContainsCaseInsensitiveKey(this IDictionary propertyDictionary, 62 | string key) 63 | { 64 | foreach (KeyValuePair kvp in propertyDictionary) 65 | { 66 | if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase)) 67 | { 68 | return true; 69 | } 70 | } 71 | return false; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Helpers/GetValueResult.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | namespace Marvin.JsonPatch.Dynamic.Helpers 7 | { 8 | /// 9 | /// Return value for the helper method used by Copy/Move. Needed to ensure we can make a different 10 | /// decision in the calling method when the value is null because it cannot be fetched (HasError = true) 11 | /// versus when it actually is null (much like why RemovedPropertyTypeResult is used for returning 12 | /// type in the Remove operation). 13 | /// 14 | public class GetValueResult 15 | { 16 | /// 17 | /// The value of the property we're trying to get 18 | /// 19 | public object PropertyValue { get; private set; } 20 | 21 | /// 22 | /// HasError: true when an error occurred, the operation didn't complete succesfully 23 | /// 24 | public bool HasError { get; set; } 25 | 26 | public GetValueResult(object propertyValue, bool hasError) 27 | { 28 | PropertyValue = propertyValue; 29 | HasError = hasError; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Helpers/JsonPatchProperty.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Newtonsoft.Json.Serialization; 7 | 8 | namespace Marvin.JsonPatch.Dynamic.Helpers 9 | { 10 | /// 11 | /// Metadata for JsonProperty. 12 | /// 13 | public class JsonPatchProperty 14 | { 15 | /// 16 | /// Initializes a new instance. 17 | /// 18 | public JsonPatchProperty(JsonProperty property, object parent) 19 | { 20 | Property = property; 21 | Parent = parent; 22 | } 23 | 24 | /// 25 | /// Gets or sets JsonProperty. 26 | /// 27 | public JsonProperty Property { get; set; } 28 | 29 | /// 30 | /// Gets or sets Parent. 31 | /// 32 | public object Parent { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Helpers/ObjectTreeAnalysisResult.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Newtonsoft.Json.Serialization; 7 | using System; 8 | using System.Collections; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | 12 | namespace Marvin.JsonPatch.Dynamic.Helpers 13 | { 14 | internal class ObjectTreeAnalysisResult 15 | { 16 | // either the property is part of the container dictionary, 17 | // or we have a direct reference to a JsonPatchProperty instance 18 | 19 | public bool UseDynamicLogic { get; private set; } 20 | 21 | public bool IsValidPathForAdd { get; private set; } 22 | 23 | public bool IsValidPathForRemove { get; private set; } 24 | 25 | public IDictionary Container { get; private set; } 26 | 27 | public string PropertyPathInParent {get; private set;} 28 | 29 | public JsonPatchProperty JsonPatchProperty { get; private set; } 30 | 31 | public ObjectTreeAnalysisResult(object objectToSearch, string propertyPath 32 | , IContractResolver contractResolver) 33 | { 34 | // construct the analysis result. 35 | 36 | // split the propertypath, and if necessary, remove the first 37 | // empty item (that's the case when it starts with a "/") 38 | var propertyPathTree = propertyPath.Split( 39 | new char[] {'/'}, 40 | StringSplitOptions.RemoveEmptyEntries).ToList(); 41 | object targetObject = objectToSearch; 42 | 43 | // we've now got a split up property tree "base/property/otherproperty/..." 44 | int lastPosition = 0; 45 | for (int i = 0; i < propertyPathTree.Count; i++) 46 | { 47 | // if the current target object is an ExpandoObject (IDictionary), 48 | // we cannot use the ContractResolver. 49 | 50 | lastPosition = i; 51 | if (targetObject is IDictionary) 52 | { 53 | // find the value in the dictionary 54 | if ((targetObject as IDictionary) 55 | .ContainsCaseInsensitiveKey(propertyPathTree[i])) 56 | { 57 | var possibleNewTargetObject = (targetObject as IDictionary) 58 | .GetValueForCaseInsensitiveKey(propertyPathTree[i]); 59 | 60 | // unless we're at the last item, we should set the targetobject 61 | // to the new object. If we're at the last item, we need to stop 62 | if (i != propertyPathTree.Count - 1) 63 | { 64 | targetObject = possibleNewTargetObject; 65 | } 66 | } 67 | else 68 | { 69 | break; 70 | } 71 | } 72 | else 73 | { 74 | // if the current part of the path is numeric, this means we're trying 75 | // to get the propertyInfo of a specific object in an array. To allow 76 | // for this, the previous value (targetObject) must be an IEnumerable, and 77 | // the position must exist. 78 | 79 | int numericValue = -1; 80 | if (int.TryParse(propertyPathTree[i], out numericValue)) 81 | { 82 | var element = GetElementAtFromObject(targetObject, numericValue); 83 | if (element != null) 84 | { 85 | targetObject = element; 86 | } 87 | else 88 | { 89 | break; 90 | } 91 | } 92 | else 93 | { 94 | 95 | var jsonContract = (JsonObjectContract)contractResolver 96 | .ResolveContract(targetObject.GetType()); 97 | 98 | // does the property exist? 99 | var attemptedProperty = jsonContract.Properties.FirstOrDefault 100 | (p => string.Equals(p.PropertyName, propertyPathTree[i] 101 | , StringComparison.OrdinalIgnoreCase)); 102 | 103 | if (attemptedProperty != null) 104 | { 105 | // unless we're at the last item, we should continue searching. 106 | // If we're at the last item, we need to stop 107 | if (!(i == propertyPathTree.Count - 1)) 108 | { 109 | targetObject = attemptedProperty.ValueProvider.GetValue(targetObject); 110 | } 111 | } 112 | else 113 | { 114 | // property cannot be found, and we're not working with dynamics. 115 | // Stop, and return invalid path. 116 | break; 117 | } 118 | } 119 | } 120 | } 121 | 122 | // two things can happen now. The targetproperty can be an IDictionary - in that 123 | // case, it's valid for add if there's 1 item left in the propertyPathTree. 124 | // 125 | // it can also be a property info. In that case, if there's nothing left in the path 126 | // tree we're at the end, if there's one left we can try and set that. 127 | 128 | if (targetObject is IDictionary) 129 | { 130 | var leftOverPath = propertyPathTree 131 | .GetRange(lastPosition, propertyPathTree.Count - lastPosition); 132 | 133 | UseDynamicLogic = true; 134 | 135 | if (leftOverPath.Count == 1) 136 | { 137 | Container = targetObject as IDictionary; 138 | IsValidPathForAdd = true; 139 | PropertyPathInParent = leftOverPath.Last(); 140 | 141 | // to be able to remove this property, it must exist 142 | IsValidPathForRemove = Container.ContainsCaseInsensitiveKey(PropertyPathInParent); 143 | } 144 | else 145 | { 146 | IsValidPathForAdd = false; 147 | IsValidPathForRemove = false; 148 | } 149 | } 150 | else 151 | { 152 | var leftOverPath = propertyPathTree 153 | .GetRange(lastPosition, propertyPathTree.Count - lastPosition); 154 | 155 | UseDynamicLogic = false; 156 | 157 | if (leftOverPath.Count == 1) 158 | { 159 | var jsonContract = (JsonObjectContract)contractResolver 160 | .ResolveContract(targetObject.GetType()); 161 | 162 | var attemptedProperty = jsonContract.Properties.FirstOrDefault 163 | (p => string.Equals(p.PropertyName, leftOverPath.Last() 164 | , StringComparison.OrdinalIgnoreCase)); 165 | 166 | if (attemptedProperty == null) 167 | { 168 | IsValidPathForAdd = false; 169 | IsValidPathForRemove = false; 170 | } 171 | else 172 | { 173 | IsValidPathForAdd = true; 174 | IsValidPathForRemove = true; 175 | JsonPatchProperty = new Helpers.JsonPatchProperty(attemptedProperty, targetObject); 176 | PropertyPathInParent = leftOverPath.Last(); 177 | } 178 | } 179 | else 180 | { 181 | IsValidPathForAdd = false; 182 | IsValidPathForRemove = false; 183 | } 184 | } 185 | } 186 | 187 | private object GetElementAtFromObject(object targetObject, int numericValue) 188 | { 189 | if (numericValue > -1) 190 | { 191 | // Check if the targetobject is an IEnumerable, 192 | // and if the position is valid. 193 | if (targetObject is IEnumerable) 194 | { 195 | var indexable = ((IEnumerable)targetObject).Cast(); 196 | 197 | if (indexable.Count() >= numericValue) 198 | { 199 | return indexable.ElementAt(numericValue); 200 | } 201 | else { return null; } 202 | } 203 | else { return null; ; } 204 | } 205 | else { return null; } 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Helpers/PathHelpers.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Marvin.JsonPatch.Exceptions; 7 | using Marvin.JsonPatch.Helpers; 8 | using System; 9 | 10 | namespace Marvin.JsonPatch.Dynamic.Helpers 11 | { 12 | internal static class PathHelpers 13 | { 14 | internal static string NormalizePath(string path, CaseTransformType caseTransformType) 15 | { 16 | // check for most common path errors on create. This is not 17 | // absolutely necessary, but it allows us to already catch mistakes 18 | // on creation of the patch document rather than on execute. 19 | 20 | if (path.Contains(".") || path.Contains("//") || path.Contains(" ") || path.Contains("\\")) 21 | { 22 | throw new JsonPatchException(string.Format("Provided string is not a valid path: {0}", path), null); 23 | } 24 | 25 | if (!(path.StartsWith("/"))) 26 | { 27 | path = "/" + path; 28 | } 29 | 30 | switch (caseTransformType) 31 | { 32 | case CaseTransformType.LowerCase: 33 | return path.ToLowerInvariant(); 34 | case CaseTransformType.UpperCase: 35 | return path.ToUpperInvariant(); 36 | case CaseTransformType.CamelCase: 37 | if (path.Length > 1) 38 | { 39 | return "/" + Char.ToLowerInvariant(path[1]) + path.Substring(2); 40 | } 41 | else 42 | { 43 | return path; 44 | } 45 | case CaseTransformType.OriginalCase: 46 | return path; 47 | default: 48 | throw new NotImplementedException(); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Helpers/PropertyHelpers.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Marvin.JsonPatch.Exceptions; 7 | using Marvin.JsonPatch.Operations; 8 | using Newtonsoft.Json; 9 | using System; 10 | using System.Collections; 11 | using System.Collections.Generic; 12 | using System.Reflection; 13 | using System.Linq; 14 | 15 | namespace Marvin.JsonPatch.Dynamic.Helpers 16 | { 17 | internal static class PropertyHelpers 18 | { 19 | internal static ConversionResult ConvertToActualType(Type propertyType, object value) 20 | { 21 | try 22 | { 23 | var o = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), propertyType); 24 | return new ConversionResult(true, o); 25 | } 26 | catch (Exception) 27 | { 28 | return new ConversionResult(false, null); 29 | } 30 | } 31 | 32 | internal static ActualPropertyPathResult GetActualPropertyPath(string propertyPath, object objectToApplyTo, 33 | Operation operationToReport, bool forPath) 34 | { 35 | if (propertyPath.EndsWith("/-")) 36 | { 37 | return new ActualPropertyPathResult(-1, propertyPath.Substring(0, propertyPath.Length - 2), true); 38 | } 39 | else 40 | { 41 | var possibleIndex = propertyPath.Substring(propertyPath.LastIndexOf("/") + 1); 42 | int castedIndex = -1; 43 | if (int.TryParse(possibleIndex, out castedIndex)) 44 | { 45 | // has numeric end. 46 | if (castedIndex > -1) 47 | { 48 | var pathToProperty = propertyPath.Substring( 49 | 0, 50 | propertyPath.LastIndexOf('/' + castedIndex.ToString())); 51 | 52 | return new ActualPropertyPathResult(castedIndex, pathToProperty, false); 53 | } 54 | else 55 | { 56 | string message = forPath ? 57 | string.Format("Patch failed: provided path is invalid, position too small: {0}", 58 | propertyPath) 59 | : string.Format("Patch failed: provided from is invalid, position too small: {0}", 60 | propertyPath); 61 | 62 | // negative position - invalid path 63 | throw new JsonPatchException( 64 | new JsonPatchError( 65 | objectToApplyTo, 66 | operationToReport, 67 | message), 68 | 422); 69 | } 70 | } 71 | return new ActualPropertyPathResult(-1, propertyPath, false); 72 | } 73 | } 74 | 75 | internal static bool IsNonStringArray(Type type) 76 | { 77 | if (GetIListType(type) != null) 78 | { 79 | return true; 80 | } 81 | 82 | return (!(type == typeof(string)) && typeof(IList) 83 | .GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())); 84 | } 85 | 86 | internal static Type GetIListType(Type type) 87 | { 88 | if (type == null) 89 | throw new ArgumentException("Parameter type cannot be null"); 90 | 91 | if (IsGenericListType(type)) 92 | { 93 | return type.GetGenericArguments()[0]; 94 | } 95 | 96 | foreach (Type interfaceType in type.GetTypeInfo().ImplementedInterfaces) 97 | { 98 | if (IsGenericListType(interfaceType)) 99 | { 100 | return interfaceType.GetGenericArguments()[0]; 101 | } 102 | } 103 | return null; 104 | } 105 | 106 | internal static bool IsGenericListType(Type type) 107 | { 108 | if (type == null) 109 | throw new ArgumentException("Parameter type cannot be null"); 110 | 111 | if (type.GetTypeInfo().IsGenericType && 112 | type.GetGenericTypeDefinition() == typeof(IList<>)) 113 | { 114 | return true; 115 | } 116 | return false; 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Helpers/RemovedPropertyTypeResult.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using System; 7 | 8 | namespace Marvin.JsonPatch.Dynamic.Helpers 9 | { 10 | /// 11 | /// Return value for Remove operation. The combination tells us what to do next (if this operation 12 | /// is called from inside another operation, eg: Replace, Copy. 13 | /// 14 | /// Possible combo: 15 | /// - ActualType contains type: operation succesfully completed, can continue when called from inside 16 | /// another operation 17 | /// - ActualType null & HasError true: operation not completed succesfully, should not be allowed to continue 18 | /// - ActualType null & HasError false: operation completed succesfully, but we should not be allowed to 19 | /// continue when called from inside another method as we could not verify the type of the removed property. 20 | /// This happens when the value of an item in an ExpandoObject dictionary is null. 21 | /// 22 | internal class RemovedPropertyTypeResult 23 | { 24 | /// 25 | /// The type of the removed property (value) 26 | /// 27 | public Type ActualType { get; private set; } 28 | 29 | /// 30 | /// HasError: true when an error occurred, the operation didn't complete succesfully 31 | /// 32 | public bool HasError { get; set; } 33 | 34 | public RemovedPropertyTypeResult(Type actualType, bool hasError) 35 | { 36 | ActualType = actualType; 37 | HasError = hasError; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/JsonPatchDocument.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Marvin.JsonPatch.Dynamic.Adapters; 7 | using Marvin.JsonPatch.Dynamic.Converters; 8 | using Marvin.JsonPatch.Dynamic.Helpers; 9 | using Marvin.JsonPatch.Dynamic.Operations; 10 | using Marvin.JsonPatch.Helpers; 11 | using Marvin.JsonPatch.Operations; 12 | using Newtonsoft.Json; 13 | using Newtonsoft.Json.Serialization; 14 | using System.Collections.Generic; 15 | 16 | namespace Marvin.JsonPatch.Dynamic 17 | { 18 | [JsonConverter(typeof(JsonPatchDocumentConverter))] 19 | public class JsonPatchDocument : IJsonPatchDocument 20 | { 21 | public List Operations { get; private set; } 22 | 23 | [JsonIgnore] 24 | public IContractResolver ContractResolver { get; set; } 25 | 26 | [JsonIgnore] 27 | public CaseTransformType CaseTransformType { get; set; } 28 | 29 | 30 | /// 31 | /// Create a new JsonPatchDocument 32 | /// 33 | public JsonPatchDocument() : 34 | this(new List(), new DefaultContractResolver(), CaseTransformType.OriginalCase) 35 | { 36 | } 37 | 38 | /// 39 | /// Create a new JsonPatchDocument, and pass in a custom contract resolver 40 | /// to use when applying the document. 41 | /// 42 | /// A custom IContractResolver 43 | public JsonPatchDocument(IContractResolver contractResolver) 44 | : this(new List(), contractResolver, CaseTransformType.OriginalCase) 45 | { 46 | } 47 | 48 | /// 49 | /// Create a new JsonPatchDocument from a list of operations 50 | /// 51 | /// A list of operations 52 | public JsonPatchDocument(List operations) 53 | : this(operations, new DefaultContractResolver(), CaseTransformType.OriginalCase) 54 | { 55 | } 56 | 57 | /// 58 | /// Create a new JsonPatchDocument and pass in a CaseTransformType 59 | /// 60 | /// Defines the case used when seralizing the object to JSON 61 | public JsonPatchDocument(CaseTransformType caseTransformType) 62 | : this(new List(), new DefaultContractResolver(), caseTransformType) 63 | { 64 | } 65 | 66 | /// 67 | /// Create a new JsonPatchDocument from a list of operations and pass in a CaseTransformType 68 | /// 69 | /// A list of operations 70 | /// Defines the case used when seralizing the object to JSON 71 | public JsonPatchDocument(List operations, CaseTransformType caseTransformType) 72 | : this(operations, new DefaultContractResolver(), caseTransformType) 73 | { 74 | } 75 | 76 | /// 77 | /// Create a new JsonPatchDocument, and pass in a CaseTransformType and 78 | /// custom contract resolver to use when applying the document. 79 | /// 80 | /// A custom IContractResolver 81 | /// Defines the case used when seralizing the object to JSON 82 | public JsonPatchDocument(IContractResolver contractResolver, CaseTransformType caseTransformType) 83 | : this(new List(), contractResolver, caseTransformType) 84 | { 85 | } 86 | 87 | /// 88 | /// Create a new JsonPatchDocument from a list of operations, and pass in a custom contract resolver 89 | /// to use when applying the document. 90 | /// 91 | /// A list of operations 92 | /// A custom IContractResolver 93 | public JsonPatchDocument(List operations, IContractResolver contractResolver) 94 | : this(operations, contractResolver, CaseTransformType.OriginalCase) 95 | { 96 | } 97 | 98 | public JsonPatchDocument(List operations, IContractResolver contractResolver, CaseTransformType caseTransformType) 99 | { 100 | Operations = operations; 101 | ContractResolver = contractResolver; 102 | CaseTransformType = caseTransformType; 103 | } 104 | 105 | public JsonPatchDocument Add(string path, object value) 106 | { 107 | Operations.Add(new Operation("add", PathHelpers.NormalizePath(path, CaseTransformType), null, value)); 108 | return this; 109 | } 110 | 111 | public JsonPatchDocument Remove(string path) 112 | { 113 | Operations.Add(new Operation("remove", PathHelpers.NormalizePath(path, CaseTransformType), null, null)); 114 | return this; 115 | } 116 | 117 | public JsonPatchDocument Replace(string path, object value) 118 | { 119 | Operations.Add(new Operation("replace", PathHelpers.NormalizePath(path, CaseTransformType), null, value)); 120 | return this; 121 | } 122 | 123 | public JsonPatchDocument Move(string from, string path) 124 | { 125 | Operations.Add(new Operation("move", PathHelpers.NormalizePath(path, CaseTransformType), PathHelpers.NormalizePath(from, CaseTransformType))); 126 | return this; 127 | } 128 | 129 | public JsonPatchDocument Copy(string from, string path) 130 | { 131 | Operations.Add(new Operation("copy", PathHelpers.NormalizePath(path, CaseTransformType), PathHelpers.NormalizePath(from, CaseTransformType))); 132 | return this; 133 | } 134 | 135 | public void ApplyTo(T objectToApplyTo) 136 | { 137 | ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver)); 138 | } 139 | 140 | /// 141 | /// Apply the patch document, passing in a custom IObjectAdapter. 142 | /// This method will change the passed-in object. 143 | /// 144 | /// The object to apply the JsonPatchDocument to 145 | /// The IObjectAdapter instance to use 146 | public void ApplyTo(T objectToApplyTo, IObjectAdapter adapter) 147 | { 148 | // apply each operation in order 149 | foreach (var op in Operations) 150 | { 151 | op.Apply(objectToApplyTo, adapter); 152 | } 153 | } 154 | 155 | // return a copy - original operations should not 156 | // be editable through this. 157 | IList IJsonPatchDocument.GetOperations() 158 | { 159 | var allOps = new List(); 160 | 161 | if (Operations != null) 162 | { 163 | foreach (var op in Operations) 164 | { 165 | var untypedOp = new Operation(); 166 | 167 | untypedOp.op = op.op; 168 | untypedOp.value = op.value; 169 | untypedOp.path = op.path; 170 | untypedOp.from = op.from; 171 | 172 | allOps.Add(untypedOp); 173 | } 174 | } 175 | 176 | return allOps; 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Marvin.JsonPatch.Dynamic.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {C8DC479F-2216-4B8F-BC26-23E88B6DA6D5} 8 | Library 9 | Properties 10 | Marvin.JsonPatch.Dynamic 11 | Marvin.JsonPatch.Dynamic 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\..\packages\Marvin.JsonPatch.1.1.0\lib\portable40-net40+win8\Marvin.JsonPatch.dll 35 | 36 | 37 | ..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll 38 | True 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 77 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Marvin.JsonPatch.Dynamic.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | Dynamically typed object support for Marvin.JsonPatch 7 | Kevin Dockx (Marvin) 8 | Kevin Dockx (Marvin) 9 | https://github.com/KevinDockx/JsonPatch.Dynamic 10 | false 11 | Marvin.JsonPatch.Dynamic adds support for dynamically typed objects to Marvin.JsonPatch. 12 | 13 | Marvin.JsonPatch was built to work on staticly typed objects, which is great for most cases. Yet sometimes you'll want to create a patch document without having a static type to start from (for example: when integrating with a backend that's out of your control), or you'll want to apply a JsonPatchDocument to a dynamic object, or an object that has a property which isn't statically typed. 14 | 15 | That's what this component takes care of. It extends Marvin.JsonPatch with new methods on JsonPatchDocument, and it allows you to apply the JsonPatchDocument to dynamically typed objects. 16 | Marvin.JsonPatch.Dynamic adds support for dynamically typed objects to Marvin.JsonPatch (Json Patch Document RFC 6902 implementation for .NET) 17 | Copyright 2016 18 | Web API Dynamic WebAPI HttpPatch Json JsonPatch Partial Update ASP .NET 19 | 20 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Operations/OperationExtensions.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Marvin.JsonPatch.Dynamic.Adapters; 7 | using Marvin.JsonPatch.Operations; 8 | using System; 9 | 10 | namespace Marvin.JsonPatch.Dynamic.Operations 11 | { 12 | public static class OperationExtensions 13 | { 14 | internal static void Apply(this Operation operation, object objectToApplyTo, IObjectAdapter adapter) 15 | { 16 | if (objectToApplyTo == null) 17 | { 18 | throw new NullReferenceException("objectToApplyTo cannot be null"); 19 | } 20 | if (adapter == null) 21 | { 22 | throw new NullReferenceException("adapter cannot be null"); 23 | } 24 | 25 | switch (operation.OperationType) 26 | { 27 | case OperationType.Add: 28 | adapter.Add(operation, objectToApplyTo); 29 | break; 30 | case OperationType.Remove: 31 | adapter.Remove(operation, objectToApplyTo); 32 | break; 33 | case OperationType.Replace: 34 | adapter.Replace(operation, objectToApplyTo); 35 | break; 36 | case OperationType.Move: 37 | adapter.Move(operation, objectToApplyTo); 38 | break; 39 | case OperationType.Copy: 40 | adapter.Copy(operation, objectToApplyTo); 41 | break; 42 | case OperationType.Test: 43 | throw new NotImplementedException("Test is currently not implemented."); 44 | default: 45 | break; 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("Dynamically typed object support for Marvin.JsonPatch")] 10 | [assembly: AssemblyDescription("Dynamically typed object support for Marvin.JsonPatch (Json Patch Document RFC 6902 implementation for .NET)")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("Kevin Dockx (Marvin)")] 13 | [assembly: AssemblyProduct("Dynamically typep object support for Marvin.JsonPatch")] 14 | [assembly: AssemblyCopyright("Copyright © 2016")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: NeutralResourcesLanguage("en")] 18 | 19 | // Version information for an assembly consists of the following four values: 20 | // 21 | // Major Version 22 | // Minor Version 23 | // Build Number 24 | // Revision 25 | // 26 | // You can specify all the values or you can default the Build and Revision Numbers 27 | // by using the '*' as shown below: 28 | // [assembly: AssemblyVersion("1.0.*")] 29 | [assembly: AssemblyVersion("1.0.0")] 30 | [assembly: AssemblyFileVersion("1.0.0")] 31 | [assembly: AssemblyInformationalVersion("1.0.0")] 32 | 33 | 34 | // Setting ComVisible to false makes the types in this assembly not visible 35 | // to COM components. If you need to access a type in this assembly from 36 | // COM, set the ComVisible attribute to true on that type. 37 | [assembly: ComVisible(false)] 38 | 39 | // The following GUID is for the ID of the typelib if this project is exposed to COM 40 | [assembly: Guid("fd9c7544-9f71-447a-add4-b96d1f8d28fa")] 41 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Marvin.JsonPatch.Dynamic/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/AddOperationTests.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Marvin.JsonPatch.Dynamic; 7 | using Marvin.JsonPatch.Exceptions; 8 | using Newtonsoft.Json; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Dynamic; 12 | using System.Linq; 13 | using System.Text; 14 | using System.Threading.Tasks; 15 | using Xunit; 16 | 17 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 18 | { 19 | public class AddOperationTests 20 | { 21 | [Fact] 22 | public void AddNewPropertyShouldFailIfRootIsNotAnExpandoObject() 23 | { 24 | dynamic doc = new 25 | { 26 | Test = 1 27 | }; 28 | 29 | // create patch 30 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 31 | patchDoc.Add("NewInt", 1); 32 | 33 | var serialized = JsonConvert.SerializeObject(patchDoc); 34 | var deserialized = JsonConvert.DeserializeObject(serialized); 35 | 36 | var exception = Assert.Throws(() => 37 | { 38 | deserialized.ApplyTo(doc); 39 | }); 40 | 41 | //Assert.Equal( 42 | // "The property at path '/newint' could not be added.", 43 | // exception.Message); 44 | } 45 | 46 | [Fact] 47 | public void AddNewProperty() 48 | { 49 | dynamic obj = new ExpandoObject(); 50 | obj.Test = 1; 51 | 52 | // create patch 53 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 54 | patchDoc.Add("NewInt", 1); 55 | 56 | var serialized = JsonConvert.SerializeObject(patchDoc); 57 | var deserialized = JsonConvert.DeserializeObject(serialized); 58 | 59 | deserialized.ApplyTo(obj); 60 | 61 | Assert.Equal(1, obj.NewInt); 62 | Assert.Equal(1, obj.Test); 63 | } 64 | 65 | [Fact] 66 | public void AddNewPropertyToNestedAnonymousObjectShouldFail() 67 | { 68 | dynamic doc = new 69 | { 70 | Test = 1, 71 | nested = new { } 72 | }; 73 | 74 | // create patch 75 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 76 | patchDoc.Add("Nested/NewInt", 1); 77 | 78 | var serialized = JsonConvert.SerializeObject(patchDoc); 79 | var deserialized = JsonConvert.DeserializeObject(serialized); 80 | 81 | var exception = Assert.Throws(() => 82 | { 83 | deserialized.ApplyTo(doc); 84 | }); 85 | //Assert.Equal( 86 | // "The property at path '/nested/newint' could not be added.", 87 | // exception.Message); 88 | } 89 | 90 | [Fact] 91 | public void AddNewPropertyToTypedObjectShouldFail() 92 | { 93 | dynamic doc = new 94 | { 95 | Test = 1, 96 | nested = new NestedDTO() 97 | }; 98 | 99 | // create patch 100 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 101 | patchDoc.Add("Nested/NewInt", 1); 102 | 103 | var serialized = JsonConvert.SerializeObject(patchDoc); 104 | var deserialized = JsonConvert.DeserializeObject(serialized); 105 | 106 | var exception = Assert.Throws(() => 107 | { 108 | deserialized.ApplyTo(doc); 109 | }); 110 | //Assert.Equal( 111 | // "The property at path '/nested/newint' could not be added.", 112 | // exception.Message); 113 | } 114 | 115 | [Fact] 116 | public void AddToExistingPropertyOnNestedObject() 117 | { 118 | dynamic doc = new 119 | { 120 | Test = 1, 121 | nested = new NestedDTO() 122 | }; 123 | 124 | // create patch 125 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 126 | patchDoc.Add("Nested/StringProperty", "A"); 127 | 128 | var serialized = JsonConvert.SerializeObject(patchDoc); 129 | var deserialized = JsonConvert.DeserializeObject(serialized); 130 | 131 | deserialized.ApplyTo(doc); 132 | 133 | Assert.Equal("A", doc.nested.StringProperty); 134 | Assert.Equal(1, doc.Test); 135 | } 136 | 137 | [Fact] 138 | public void AddNewPropertyToExpandoOject() 139 | { 140 | dynamic doc = new 141 | { 142 | Test = 1, 143 | nested = new ExpandoObject() 144 | }; 145 | 146 | // create patch 147 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 148 | patchDoc.Add("Nested/NewInt", 1); 149 | 150 | var serialized = JsonConvert.SerializeObject(patchDoc); 151 | var deserialized = JsonConvert.DeserializeObject(serialized); 152 | 153 | deserialized.ApplyTo(doc); 154 | 155 | Assert.Equal(1, doc.nested.NewInt); 156 | Assert.Equal(1, doc.Test); 157 | } 158 | 159 | [Fact] 160 | public void AddNewPropertyToExpandoOjectInTypedObject() 161 | { 162 | var doc = new NestedDTO() 163 | { 164 | DynamicProperty = new ExpandoObject() 165 | }; 166 | 167 | // create patch 168 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 169 | patchDoc.Add("DynamicProperty/NewInt", 1); 170 | 171 | var serialized = JsonConvert.SerializeObject(patchDoc); 172 | var deserialized = JsonConvert.DeserializeObject(serialized); 173 | 174 | deserialized.ApplyTo(doc); 175 | 176 | Assert.Equal(1, doc.DynamicProperty.NewInt); 177 | } 178 | 179 | [Fact] 180 | public void AddNewPropertyToTypedObjectInExpandoObject() 181 | { 182 | dynamic dynamicProperty = new ExpandoObject(); 183 | dynamicProperty.StringProperty = "A"; 184 | 185 | var doc = new NestedDTO() 186 | { 187 | DynamicProperty = dynamicProperty 188 | }; 189 | 190 | // create patch 191 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 192 | patchDoc.Add("DynamicProperty/StringProperty", "B"); 193 | 194 | var serialized = JsonConvert.SerializeObject(patchDoc); 195 | var deserialized = JsonConvert.DeserializeObject(serialized); 196 | 197 | deserialized.ApplyTo(doc); 198 | 199 | Assert.Equal("B", doc.DynamicProperty.StringProperty); 200 | } 201 | 202 | [Fact] 203 | public void AddNewPropertyToAnonymousObjectShouldFail() 204 | { 205 | dynamic doc = new 206 | { 207 | Test = 1 208 | }; 209 | 210 | dynamic valueToAdd = new { IntValue = 1, StringValue = "test", GuidValue = Guid.NewGuid() }; 211 | 212 | // create patch 213 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 214 | patchDoc.Add("ComplexProperty", valueToAdd); 215 | 216 | var serialized = JsonConvert.SerializeObject(patchDoc); 217 | var deserialized = JsonConvert.DeserializeObject(serialized); 218 | 219 | var exception = Assert.Throws(() => 220 | { 221 | deserialized.ApplyTo(doc); 222 | }); 223 | //Assert.Equal( 224 | // "The property at path '/nested/newint' could not be added.", 225 | // exception.Message); 226 | 227 | 228 | } 229 | 230 | [Fact] 231 | public void AddResultsReplaceShouldFailOnAnonymousDueToNoSetter() 232 | { 233 | var doc = new 234 | { 235 | StringProperty = "A" 236 | }; 237 | 238 | // create patch 239 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 240 | patchDoc.Add("StringProperty", "B"); 241 | 242 | var serialized = JsonConvert.SerializeObject(patchDoc); 243 | var deserialized = JsonConvert.DeserializeObject(serialized); 244 | 245 | var exception = Assert.Throws(() => 246 | { 247 | deserialized.ApplyTo(doc); 248 | }); 249 | //Assert.Equal( 250 | // "The property at path '/nested/newint' could not be added.", 251 | // exception.Message); 252 | } 253 | 254 | [Fact] 255 | public void AddResultsShouldReplace() 256 | { 257 | dynamic doc = new ExpandoObject(); 258 | doc.StringProperty = "A"; 259 | 260 | // create patch 261 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 262 | patchDoc.Add("StringProperty", "B"); 263 | 264 | var serialized = JsonConvert.SerializeObject(patchDoc); 265 | var deserialized = JsonConvert.DeserializeObject(serialized); 266 | 267 | deserialized.ApplyTo(doc); 268 | 269 | Assert.Equal("B", doc.StringProperty); 270 | } 271 | 272 | [Fact] 273 | public void AddResultsShouldReplaceInNested() 274 | { 275 | dynamic doc = new ExpandoObject(); 276 | doc.InBetweenFirst = new ExpandoObject(); 277 | doc.InBetweenFirst.InBetweenSecond = new ExpandoObject(); 278 | doc.InBetweenFirst.InBetweenSecond.StringProperty = "A"; 279 | 280 | // create patch 281 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 282 | patchDoc.Add("/InBetweenFirst/InBetweenSecond/StringProperty", "B"); 283 | 284 | var serialized = JsonConvert.SerializeObject(patchDoc); 285 | var deserialized = JsonConvert.DeserializeObject(serialized); 286 | 287 | deserialized.ApplyTo(doc); 288 | 289 | Assert.Equal("B", doc.InBetweenFirst.InBetweenSecond.StringProperty); 290 | } 291 | 292 | [Fact] 293 | public void AddResultsShouldReplaceInNestedInDynamic() 294 | { 295 | dynamic doc = new ExpandoObject(); 296 | doc.Nested = new NestedDTO(); 297 | doc.Nested.DynamicProperty = new ExpandoObject(); 298 | doc.Nested.DynamicProperty.InBetweenFirst = new ExpandoObject(); 299 | doc.Nested.DynamicProperty.InBetweenFirst.InBetweenSecond = new ExpandoObject(); 300 | doc.Nested.DynamicProperty.InBetweenFirst.InBetweenSecond.StringProperty = "A"; 301 | 302 | // create patch 303 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 304 | patchDoc.Add("/Nested/DynamicProperty/InBetweenFirst/InBetweenSecond/StringProperty", "B"); 305 | 306 | var serialized = JsonConvert.SerializeObject(patchDoc); 307 | var deserialized = JsonConvert.DeserializeObject(serialized); 308 | 309 | deserialized.ApplyTo(doc); 310 | 311 | Assert.Equal("B", doc.Nested.DynamicProperty.InBetweenFirst.InBetweenSecond.StringProperty); 312 | } 313 | 314 | [Fact] 315 | public void ShouldNotBeAbleToAddToNonExistingPropertyThatIsNotTheRoot() 316 | { 317 | //Adding to a Nonexistent Target 318 | // 319 | // An example target JSON document: 320 | // { "foo": "bar" } 321 | // A JSON Patch document: 322 | // [ 323 | // { "op": "add", "path": "/baz/bat", "value": "qux" } 324 | // ] 325 | // This JSON Patch document, applied to the target JSON document above, 326 | // would result in an error (therefore, it would not be applied), 327 | // because the "add" operation's target location that references neither 328 | // the root of the document, nor a member of an existing object, nor a 329 | // member of an existing array. 330 | 331 | var doc = new NestedDTO() 332 | { 333 | DynamicProperty = new ExpandoObject() 334 | }; 335 | 336 | // create patch 337 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 338 | patchDoc.Add("DynamicProperty/OtherProperty/IntProperty", 1); 339 | 340 | var serialized = JsonConvert.SerializeObject(patchDoc); 341 | var deserialized = JsonConvert.DeserializeObject(serialized); 342 | 343 | var exception = Assert.Throws(() => 344 | { 345 | deserialized.ApplyTo(doc); 346 | }); 347 | //Assert.Equal( 348 | // "The property at path '/dynamicproperty/otherproperty/intproperty' could not be added.", 349 | // exception.Message); 350 | } 351 | 352 | [Fact] 353 | public void ShouldNotBeAbleToAddToNonExistingPropertyInNestedPropertyThatIsNotTheRoot() 354 | { 355 | //Adding to a Nonexistent Target 356 | // 357 | // An example target JSON document: 358 | // { "foo": "bar" } 359 | // A JSON Patch document: 360 | // [ 361 | // { "op": "add", "path": "/baz/bat", "value": "qux" } 362 | // ] 363 | // This JSON Patch document, applied to the target JSON document above, 364 | // would result in an error (therefore, it would not be applied), 365 | // because the "add" operation's target location that references neither 366 | // the root of the document, nor a member of an existing object, nor a 367 | // member of an existing array. 368 | 369 | var doc = new 370 | { 371 | Foo = "bar" 372 | }; 373 | 374 | // create patch 375 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 376 | patchDoc.Add("baz/bat", "qux"); 377 | 378 | var serialized = JsonConvert.SerializeObject(patchDoc); 379 | var deserialized = JsonConvert.DeserializeObject(serialized); 380 | 381 | var exception = Assert.Throws(() => 382 | { 383 | deserialized.ApplyTo(doc); 384 | }); 385 | //Assert.Equal( 386 | // "The property at path '/baz/bat' could not be added.", 387 | // exception.Message); 388 | } 389 | 390 | [Fact] 391 | public void ShouldReplacePropertyWithDifferentCase() 392 | { 393 | dynamic doc = new ExpandoObject(); 394 | doc.StringProperty = "A"; 395 | 396 | // create patch 397 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 398 | patchDoc.Add("stringproperty", "B"); 399 | 400 | var serialized = JsonConvert.SerializeObject(patchDoc); 401 | var deserialized = JsonConvert.DeserializeObject(serialized); 402 | 403 | deserialized.ApplyTo(doc); 404 | 405 | Assert.Equal("B", doc.StringProperty); 406 | } 407 | 408 | [Fact] 409 | public void AddToList() 410 | { 411 | var doc = new 412 | { 413 | IntegerList = new List() { 1, 2, 3 } 414 | }; 415 | 416 | // create patch 417 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 418 | patchDoc.Add("IntegerList/0", 4); 419 | 420 | var serialized = JsonConvert.SerializeObject(patchDoc); 421 | var deserialized = JsonConvert.DeserializeObject(serialized); 422 | 423 | deserialized.ApplyTo(doc); 424 | 425 | Assert.Equal(new List() { 4, 1, 2, 3 }, doc.IntegerList); 426 | } 427 | 428 | [Fact] 429 | public void AddToListNegativePosition() 430 | { 431 | dynamic doc = new ExpandoObject(); 432 | doc.IntegerList = new List() { 1, 2, 3 }; 433 | 434 | // create patch 435 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 436 | patchDoc.Add("IntegerList/-1", 4); 437 | 438 | var serialized = JsonConvert.SerializeObject(patchDoc); 439 | var deserialized = JsonConvert.DeserializeObject(serialized); 440 | 441 | var exception = Assert.Throws(() => 442 | { 443 | deserialized.ApplyTo(doc); 444 | }); 445 | //Assert.Equal( 446 | // "The property at path '/integerlist/-1' could not be added.", 447 | // exception.Message); 448 | } 449 | 450 | [Fact] 451 | public void ShouldAddToListWithDifferentCase() 452 | { 453 | var doc = new 454 | { 455 | IntegerList = new List() { 1, 2, 3 } 456 | }; 457 | 458 | // create patch 459 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 460 | patchDoc.Add("integerlist/0", 4); 461 | 462 | var serialized = JsonConvert.SerializeObject(patchDoc); 463 | var deserialized = JsonConvert.DeserializeObject(serialized); 464 | 465 | deserialized.ApplyTo(doc); 466 | 467 | Assert.Equal(new List() { 4, 1, 2, 3 }, doc.IntegerList); 468 | } 469 | 470 | [Fact] 471 | public void AddToListInvalidPositionTooLarge() 472 | { 473 | var doc = new 474 | { 475 | IntegerList = new List() { 1, 2, 3 } 476 | }; 477 | 478 | // create patch 479 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 480 | patchDoc.Add("IntegerList/4", 4); 481 | 482 | var serialized = JsonConvert.SerializeObject(patchDoc); 483 | var deserialized = JsonConvert.DeserializeObject(serialized); 484 | 485 | var exception = Assert.Throws(() => 486 | { 487 | deserialized.ApplyTo(doc); 488 | }); 489 | //Assert.Equal( 490 | // "The property at path '/integerlist/4' could not be added.", 491 | // exception.Message); 492 | } 493 | 494 | [Fact] 495 | public void AddToListAtEndWithSerialization() 496 | { 497 | var doc = new 498 | { 499 | IntegerList = new List() { 1, 2, 3 } 500 | }; 501 | 502 | // create patch 503 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 504 | patchDoc.Add("IntegerList/3", 4); 505 | 506 | var serialized = JsonConvert.SerializeObject(patchDoc); 507 | var deserialized = JsonConvert.DeserializeObject(serialized); 508 | 509 | deserialized.ApplyTo(doc); 510 | 511 | Assert.Equal(new List() { 1, 2, 3, 4 }, doc.IntegerList); 512 | } 513 | 514 | [Fact] 515 | public void AddToListAtBeginning() 516 | { 517 | var doc = new 518 | { 519 | IntegerList = new List() { 1, 2, 3 } 520 | }; 521 | 522 | // create patch 523 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 524 | patchDoc.Add("IntegerList/0", 4); 525 | 526 | var serialized = JsonConvert.SerializeObject(patchDoc); 527 | var deserialized = JsonConvert.DeserializeObject(serialized); 528 | 529 | deserialized.ApplyTo(doc); 530 | 531 | Assert.Equal(new List() { 4, 1, 2, 3 }, doc.IntegerList); 532 | } 533 | 534 | [Fact] 535 | public void AddToListInvalidPositionTooSmall() 536 | { 537 | var doc = new 538 | { 539 | IntegerList = new List() { 1, 2, 3 } 540 | }; 541 | 542 | // create patch 543 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 544 | patchDoc.Add("IntegerList/-1", 4); 545 | 546 | var serialized = JsonConvert.SerializeObject(patchDoc); 547 | var deserialized = JsonConvert.DeserializeObject(serialized); 548 | 549 | var exception = Assert.Throws(() => 550 | { 551 | deserialized.ApplyTo(doc); 552 | }); 553 | //Assert.Equal( 554 | // "The property at path '/integerlist/-1' could not be added.", 555 | // exception.Message); 556 | } 557 | 558 | [Fact] 559 | public void AddToListAppend() 560 | { 561 | var doc = new 562 | { 563 | IntegerList = new List() { 1, 2, 3 } 564 | }; 565 | 566 | // create patch 567 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 568 | patchDoc.Add("IntegerList/-", 4); 569 | 570 | var serialized = JsonConvert.SerializeObject(patchDoc); 571 | var deserialized = JsonConvert.DeserializeObject(serialized); 572 | 573 | deserialized.ApplyTo(doc); 574 | 575 | Assert.Equal(new List() { 1, 2, 3, 4 }, doc.IntegerList); 576 | } 577 | } 578 | } 579 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/AddTypedOperationTests.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Marvin.JsonPatch.Exceptions; 7 | using Newtonsoft.Json; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | using Xunit; 14 | 15 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 16 | { 17 | public class AddTypedOperationTests 18 | { 19 | 20 | [Fact] 21 | public void AddToListNegativePosition() 22 | { 23 | var doc = new SimpleDTO() 24 | { 25 | IntegerList = new List() { 1, 2, 3 } 26 | }; 27 | 28 | // create patch 29 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 30 | patchDoc.Add("IntegerList/-1", 4); 31 | 32 | var serialized = JsonConvert.SerializeObject(patchDoc); 33 | var deserialized = JsonConvert.DeserializeObject(serialized); 34 | 35 | Assert.Throws(() => { deserialized.ApplyTo(doc); }); 36 | } 37 | 38 | [Fact] 39 | public void AddToGenericList() 40 | { 41 | var doc = new SimpleDTO 42 | { 43 | IntegerGenericList = new List { 1, 2, 3 } 44 | }; 45 | 46 | // create patch 47 | var patchDoc = new JsonPatchDocument(); 48 | patchDoc.Add(o => o.IntegerGenericList, 4, 0); 49 | 50 | patchDoc.ApplyTo(doc); 51 | 52 | Assert.Equal(new List { 4, 1, 2, 3 }, doc.IntegerGenericList); 53 | } 54 | 55 | 56 | [Fact] 57 | public void AddToListInList() 58 | { 59 | var doc = new SimpleDTOWithNestedDTO() 60 | { 61 | ListOfSimpleDTO = new List() 62 | { 63 | new SimpleDTO() 64 | { 65 | IntegerList = new List() { 1, 2, 3 } 66 | } 67 | } 68 | }; 69 | 70 | // create patch 71 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 72 | patchDoc.Add("ListOfSimpleDTO/0/IntegerList/0", 4); 73 | 74 | var serialized = JsonConvert.SerializeObject(patchDoc); 75 | var deserialized = JsonConvert.DeserializeObject(serialized); 76 | 77 | deserialized.ApplyTo(doc); 78 | 79 | Assert.Equal(new List() { 4, 1, 2, 3 }, doc.ListOfSimpleDTO[0].IntegerList); 80 | } 81 | 82 | [Fact] 83 | public void AddToListInListInvalidPositionTooSmall() 84 | { 85 | var doc = new SimpleDTOWithNestedDTO() 86 | { 87 | ListOfSimpleDTO = new List() 88 | { 89 | new SimpleDTO() 90 | { 91 | IntegerList = new List() { 1, 2, 3 } 92 | } 93 | } 94 | }; 95 | 96 | // create patch 97 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 98 | patchDoc.Add("ListOfSimpleDTO/-1/IntegerList/0", 4); 99 | 100 | var serialized = JsonConvert.SerializeObject(patchDoc); 101 | var deserialized = JsonConvert.DeserializeObject(serialized); 102 | Assert.Throws(() => { deserialized.ApplyTo(doc); }); 103 | } 104 | 105 | [Fact] 106 | public void AddToListInListInvalidPositionTooLarge() 107 | { 108 | var doc = new SimpleDTOWithNestedDTO() 109 | { 110 | ListOfSimpleDTO = new List() 111 | { 112 | new SimpleDTO() 113 | { 114 | IntegerList = new List() { 1, 2, 3 } 115 | } 116 | } 117 | }; 118 | 119 | // create patch 120 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 121 | patchDoc.Add("ListOfSimpleDTO/20/IntegerList/0", 4); 122 | 123 | 124 | var serialized = JsonConvert.SerializeObject(patchDoc); 125 | var deserialized = JsonConvert.DeserializeObject(serialized); 126 | 127 | Assert.Throws(() => { deserialized.ApplyTo(doc); }); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/CaseTransformTypeTests.cs: -------------------------------------------------------------------------------- 1 | using Marvin.JsonPatch.Exceptions; 2 | using Marvin.JsonPatch.Helpers; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Dynamic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using Xunit; 11 | 12 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 13 | { 14 | public class CaseTransformTypeTests 15 | { 16 | [Fact] 17 | public void CaseTransformType_UpperCase_MustSerializeCorrectly() 18 | { 19 | dynamic doc = new ExpandoObject(); 20 | doc.StringProperty = "A"; 21 | 22 | JsonPatchDocument patchDoc = new JsonPatchDocument(CaseTransformType.UpperCase); 23 | patchDoc.Add("StringProperty", "B"); 24 | 25 | var result = JsonConvert.SerializeObject(patchDoc); 26 | 27 | Assert.Equal("[{\"value\":\"B\",\"path\":\"/STRINGPROPERTY\",\"op\":\"add\"}]", result); 28 | } 29 | 30 | [Fact] 31 | public void CaseTransformType_CamelCase_MustSerializeCorrectly() 32 | { 33 | dynamic doc = new ExpandoObject(); 34 | doc.StringProperty = "A"; 35 | 36 | JsonPatchDocument patchDoc = new JsonPatchDocument(CaseTransformType.CamelCase); 37 | patchDoc.Add("StringProperty", "B"); 38 | 39 | var result = JsonConvert.SerializeObject(patchDoc); 40 | 41 | Assert.Equal("[{\"value\":\"B\",\"path\":\"/stringProperty\",\"op\":\"add\"}]", result); 42 | } 43 | 44 | [Fact] 45 | public void CaseTransformType_OriginalCase_IsDefaultAndShouldSerializeCorrectly() 46 | { 47 | dynamic doc = new ExpandoObject(); 48 | doc.StringProperty = "A"; 49 | 50 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 51 | patchDoc.Add("StringProperty", "B"); 52 | 53 | var result = JsonConvert.SerializeObject(patchDoc); 54 | 55 | Assert.Equal("[{\"value\":\"B\",\"path\":\"/StringProperty\",\"op\":\"add\"}]", result); 56 | } 57 | 58 | [Fact] 59 | public void CaseTransformType_LowerCase_ShouldSerializeCorrectly() 60 | { 61 | dynamic doc = new ExpandoObject(); 62 | doc.StringProperty = "A"; 63 | 64 | JsonPatchDocument patchDoc = new JsonPatchDocument(CaseTransformType.LowerCase); 65 | patchDoc.Add("StringProperty", "B"); 66 | 67 | var result = JsonConvert.SerializeObject(patchDoc); 68 | 69 | Assert.Equal("[{\"value\":\"B\",\"path\":\"/stringproperty\",\"op\":\"add\"}]", result); 70 | } 71 | 72 | [Fact] 73 | public void CaseTransformType_EmptyPath_ShouldCamelCaseWithoutError() 74 | { 75 | dynamic doc = new ExpandoObject(); 76 | doc.StringProperty = "A"; 77 | 78 | JsonPatchDocument patchDoc = new JsonPatchDocument(CaseTransformType.CamelCase); 79 | patchDoc.Add("", "B"); 80 | 81 | var result = JsonConvert.SerializeObject(patchDoc); 82 | 83 | Assert.Equal("[{\"value\":\"B\",\"path\":\"/\",\"op\":\"add\"}]", result); 84 | } 85 | 86 | [Fact] 87 | public void CaseTransformType_MustIgnoreCaseOnApply() 88 | { 89 | dynamic doc = new ExpandoObject(); 90 | 91 | doc.StringProperty = "A"; 92 | doc.AnotherStringProperty = "B"; 93 | 94 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 95 | patchDoc.Copy("STRINGProperty", "anotherstringproperty"); 96 | 97 | var serialized = JsonConvert.SerializeObject(patchDoc); 98 | var deserialized = JsonConvert.DeserializeObject(serialized); 99 | deserialized.ApplyTo(doc); 100 | 101 | Assert.Equal("A", doc.AnotherStringProperty); 102 | } 103 | 104 | 105 | [Fact] 106 | public void CaseTransformType_UpperCase_MustIgnoreCaseOnApply() 107 | { 108 | dynamic doc = new ExpandoObject(); 109 | 110 | doc.StringProperty = "A"; 111 | doc.AnotherStringProperty = "B"; 112 | 113 | JsonPatchDocument patchDoc = new JsonPatchDocument(CaseTransformType.UpperCase); 114 | patchDoc.Copy("STRINGProperty", "anotherstringproperty"); 115 | 116 | var serialized = JsonConvert.SerializeObject(patchDoc); 117 | var deserialized = JsonConvert.DeserializeObject(serialized); 118 | deserialized.ApplyTo(doc); 119 | 120 | Assert.Equal("A", doc.AnotherStringProperty); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/CopyOperationTests.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Newtonsoft.Json; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Dynamic; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | using Xunit; 14 | 15 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 16 | { 17 | public class CopyOperationTests 18 | { 19 | 20 | [Fact] 21 | public void Copy() 22 | { 23 | dynamic doc = new ExpandoObject(); 24 | 25 | doc.StringProperty = "A"; 26 | doc.AnotherStringProperty = "B"; 27 | 28 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 29 | patchDoc.Copy("StringProperty", "AnotherStringProperty"); 30 | 31 | var serialized = JsonConvert.SerializeObject(patchDoc); 32 | var deserialized = JsonConvert.DeserializeObject(serialized); 33 | deserialized.ApplyTo(doc); 34 | 35 | Assert.Equal("A", doc.AnotherStringProperty); 36 | } 37 | 38 | [Fact] 39 | public void CopyInList() 40 | { 41 | dynamic doc = new ExpandoObject(); 42 | doc.IntegerList = new List() { 1, 2, 3 }; 43 | 44 | // create patch 45 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 46 | patchDoc.Copy("IntegerList/0", "IntegerList/1"); 47 | 48 | var serialized = JsonConvert.SerializeObject(patchDoc); 49 | var deserialized = JsonConvert.DeserializeObject(serialized); 50 | 51 | deserialized.ApplyTo(doc); 52 | 53 | Assert.Equal(new List() { 1, 1, 2, 3 }, doc.IntegerList); 54 | } 55 | 56 | [Fact] 57 | public void CopyFromListToEndOfList() 58 | { 59 | dynamic doc = new ExpandoObject(); 60 | doc.IntegerList = new List() { 1, 2, 3 }; 61 | 62 | // create patch 63 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 64 | patchDoc.Copy("IntegerList/0", "IntegerList/-"); 65 | 66 | var serialized = JsonConvert.SerializeObject(patchDoc); 67 | var deserialized = JsonConvert.DeserializeObject(serialized); 68 | deserialized.ApplyTo(doc); 69 | 70 | Assert.Equal(new List() { 1, 2, 3, 1 }, doc.IntegerList); 71 | } 72 | 73 | [Fact] 74 | public void CopyFromListToNonList() 75 | { 76 | dynamic doc = new ExpandoObject(); 77 | doc.IntegerList = new List() { 1, 2, 3 }; 78 | 79 | // create patch 80 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 81 | patchDoc.Copy("IntegerList/0", "IntegerValue"); 82 | 83 | var serialized = JsonConvert.SerializeObject(patchDoc); 84 | var deserialized = JsonConvert.DeserializeObject(serialized); 85 | 86 | deserialized.ApplyTo(doc); 87 | 88 | Assert.Equal(1, doc.IntegerValue); 89 | } 90 | 91 | [Fact] 92 | public void CopyFromNonListToList() 93 | { 94 | dynamic doc = new ExpandoObject(); 95 | doc.IntegerValue = 5; 96 | doc.IntegerList = new List() { 1, 2, 3 }; 97 | 98 | // create patch 99 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 100 | patchDoc.Copy("IntegerValue", "IntegerList/0"); 101 | 102 | var serialized = JsonConvert.SerializeObject(patchDoc); 103 | var deserialized = JsonConvert.DeserializeObject(serialized); 104 | deserialized.ApplyTo(doc); 105 | 106 | Assert.Equal(new List() { 5, 1, 2, 3 }, doc.IntegerList); 107 | } 108 | 109 | [Fact] 110 | public void CopyToEndOfList() 111 | { 112 | dynamic doc = new ExpandoObject(); 113 | doc.IntegerValue = 5; 114 | doc.IntegerList = new List() { 1, 2, 3 }; 115 | 116 | // create patch 117 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 118 | patchDoc.Copy("IntegerValue", "IntegerList/-"); 119 | 120 | var serialized = JsonConvert.SerializeObject(patchDoc); 121 | var deserialized = JsonConvert.DeserializeObject(serialized); 122 | 123 | deserialized.ApplyTo(doc); 124 | 125 | Assert.Equal(new List() { 1, 2, 3, 5 }, doc.IntegerList); 126 | } 127 | 128 | [Fact] 129 | public void NestedCopy() 130 | { 131 | dynamic doc = new ExpandoObject(); 132 | doc.SimpleDTO = new SimpleDTO() 133 | { 134 | StringProperty = "A", 135 | AnotherStringProperty = "B" 136 | }; 137 | 138 | // create patch 139 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 140 | patchDoc.Copy("SimpleDTO/StringProperty", "SimpleDTO/AnotherStringProperty"); 141 | 142 | var serialized = JsonConvert.SerializeObject(patchDoc); 143 | var deserialized = JsonConvert.DeserializeObject(serialized); 144 | 145 | deserialized.ApplyTo(doc); 146 | 147 | Assert.Equal("A", doc.SimpleDTO.AnotherStringProperty); 148 | } 149 | 150 | [Fact] 151 | public void NestedCopyInList() 152 | { 153 | dynamic doc = new ExpandoObject(); 154 | doc.SimpleDTO = new SimpleDTO() 155 | { 156 | IntegerList = new List() { 1, 2, 3 } 157 | }; 158 | 159 | // create patch 160 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 161 | patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/1"); 162 | 163 | var serialized = JsonConvert.SerializeObject(patchDoc); 164 | var deserialized = JsonConvert.DeserializeObject(serialized); 165 | deserialized.ApplyTo(doc); 166 | 167 | Assert.Equal(new List() { 1, 1, 2, 3 }, doc.SimpleDTO.IntegerList); 168 | } 169 | 170 | [Fact] 171 | public void NestedCopyFromListToEndOfList() 172 | { 173 | dynamic doc = new ExpandoObject(); 174 | doc.SimpleDTO = new SimpleDTO() 175 | { 176 | IntegerList = new List() { 1, 2, 3 } 177 | }; 178 | 179 | // create patch 180 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 181 | patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/-"); 182 | 183 | var serialized = JsonConvert.SerializeObject(patchDoc); 184 | var deserialized = JsonConvert.DeserializeObject(serialized); 185 | 186 | deserialized.ApplyTo(doc); 187 | 188 | Assert.Equal(new List() { 1, 2, 3, 1 }, doc.SimpleDTO.IntegerList); 189 | } 190 | 191 | [Fact] 192 | public void NestedCopyFromListToNonList() 193 | { 194 | dynamic doc = new ExpandoObject(); 195 | doc.SimpleDTO = new SimpleDTO() 196 | { 197 | IntegerList = new List() { 1, 2, 3 } 198 | }; 199 | 200 | // create patch 201 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 202 | patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerValue"); 203 | 204 | var serialized = JsonConvert.SerializeObject(patchDoc); 205 | var deserialized = JsonConvert.DeserializeObject(serialized); 206 | deserialized.ApplyTo(doc); 207 | 208 | Assert.Equal(1, doc.SimpleDTO.IntegerValue); 209 | } 210 | 211 | [Fact] 212 | public void NestedCopyFromNonListToList() 213 | { 214 | dynamic doc = new ExpandoObject(); 215 | doc.SimpleDTO = new SimpleDTO() 216 | { 217 | IntegerValue = 5, 218 | IntegerList = new List() { 1, 2, 3 } 219 | }; 220 | 221 | // create patch 222 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 223 | patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/0"); 224 | var serialized = JsonConvert.SerializeObject(patchDoc); 225 | var deserialized = JsonConvert.DeserializeObject(serialized); 226 | deserialized.ApplyTo(doc); 227 | 228 | Assert.Equal(new List() { 5, 1, 2, 3 }, doc.SimpleDTO.IntegerList); 229 | } 230 | 231 | [Fact] 232 | public void NestedCopyToEndOfList() 233 | { 234 | dynamic doc = new ExpandoObject(); 235 | doc.SimpleDTO = new SimpleDTO() 236 | { 237 | IntegerValue = 5, 238 | IntegerList = new List() { 1, 2, 3 } 239 | }; 240 | 241 | // create patch 242 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 243 | patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/-"); 244 | 245 | var serialized = JsonConvert.SerializeObject(patchDoc); 246 | var deserialized = JsonConvert.DeserializeObject(serialized); 247 | deserialized.ApplyTo(doc); 248 | 249 | Assert.Equal(new List() { 1, 2, 3, 5 }, doc.SimpleDTO.IntegerList); 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/CopyTypedOperationTests.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Newtonsoft.Json; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using Xunit; 13 | 14 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 15 | { 16 | public class CopyTypedOperationTests 17 | { 18 | [Fact] 19 | public void Copy() 20 | { 21 | var doc = new SimpleDTO() 22 | { 23 | StringProperty = "A", 24 | AnotherStringProperty = "B" 25 | }; 26 | 27 | // create patch 28 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 29 | patchDoc.Copy("StringProperty","AnotherStringProperty"); 30 | 31 | var serialized = JsonConvert.SerializeObject(patchDoc); 32 | var deserialized = JsonConvert.DeserializeObject(serialized); 33 | deserialized.ApplyTo(doc); 34 | 35 | Assert.Equal("A", doc.AnotherStringProperty); 36 | } 37 | 38 | [Fact] 39 | public void CopyInList() 40 | { 41 | var doc = new SimpleDTO() 42 | { 43 | IntegerList = new List() { 1, 2, 3 } 44 | }; 45 | 46 | // create patch 47 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 48 | patchDoc.Copy("IntegerList/0", "IntegerList/1"); 49 | 50 | var serialized = JsonConvert.SerializeObject(patchDoc); 51 | var deserialized = JsonConvert.DeserializeObject(serialized); 52 | 53 | deserialized.ApplyTo(doc); 54 | 55 | Assert.Equal(new List() { 1, 1, 2, 3 }, doc.IntegerList); 56 | } 57 | 58 | [Fact] 59 | public void CopyFromListToEndOfList() 60 | { 61 | var doc = new SimpleDTO() 62 | { 63 | IntegerList = new List() { 1, 2, 3 } 64 | }; 65 | 66 | // create patch 67 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 68 | patchDoc.Copy("IntegerList/0","IntegerList/-"); 69 | 70 | var serialized = JsonConvert.SerializeObject(patchDoc); 71 | var deserialized = JsonConvert.DeserializeObject(serialized); 72 | deserialized.ApplyTo(doc); 73 | 74 | Assert.Equal(new List() { 1, 2, 3, 1 }, doc.IntegerList); 75 | } 76 | 77 | [Fact] 78 | public void CopyFromListToNonList() 79 | { 80 | var doc = new SimpleDTO() 81 | { 82 | IntegerList = new List() { 1, 2, 3 } 83 | }; 84 | 85 | // create patch 86 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 87 | patchDoc.Copy("IntegerList/0", "IntegerValue"); 88 | 89 | var serialized = JsonConvert.SerializeObject(patchDoc); 90 | var deserialized = JsonConvert.DeserializeObject(serialized); 91 | 92 | deserialized.ApplyTo(doc); 93 | 94 | Assert.Equal(1, doc.IntegerValue); 95 | } 96 | 97 | [Fact] 98 | public void CopyFromNonListToList() 99 | { 100 | var doc = new SimpleDTO() 101 | { 102 | IntegerValue = 5, 103 | IntegerList = new List() { 1, 2, 3 } 104 | }; 105 | 106 | // create patch 107 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 108 | patchDoc.Copy("IntegerValue", "IntegerList/0"); 109 | 110 | var serialized = JsonConvert.SerializeObject(patchDoc); 111 | var deserialized = JsonConvert.DeserializeObject(serialized); 112 | deserialized.ApplyTo(doc); 113 | 114 | Assert.Equal(new List() { 5, 1, 2, 3 }, doc.IntegerList); 115 | } 116 | 117 | [Fact] 118 | public void CopyToEndOfList() 119 | { 120 | var doc = new SimpleDTO() 121 | { 122 | IntegerValue = 5, 123 | IntegerList = new List() { 1, 2, 3 } 124 | }; 125 | 126 | // create patch 127 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 128 | patchDoc.Copy("IntegerValue","IntegerList/-"); 129 | 130 | var serialized = JsonConvert.SerializeObject(patchDoc); 131 | var deserialized = JsonConvert.DeserializeObject(serialized); 132 | 133 | deserialized.ApplyTo(doc); 134 | 135 | Assert.Equal(new List() { 1, 2, 3, 5 }, doc.IntegerList); 136 | } 137 | 138 | [Fact] 139 | public void NestedCopy() 140 | { 141 | var doc = new SimpleDTOWithNestedDTO() 142 | { 143 | SimpleDTO = new SimpleDTO() 144 | { 145 | StringProperty = "A", 146 | AnotherStringProperty = "B" 147 | } 148 | }; 149 | 150 | // create patch 151 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 152 | patchDoc.Copy("SimpleDTO/StringProperty", "SimpleDTO/AnotherStringProperty"); 153 | 154 | var serialized = JsonConvert.SerializeObject(patchDoc); 155 | var deserialized = JsonConvert.DeserializeObject(serialized); 156 | 157 | deserialized.ApplyTo(doc); 158 | 159 | Assert.Equal("A", doc.SimpleDTO.AnotherStringProperty); 160 | } 161 | 162 | [Fact] 163 | public void NestedCopyInList() 164 | { 165 | var doc = new SimpleDTOWithNestedDTO() 166 | { 167 | SimpleDTO = new SimpleDTO() 168 | { 169 | IntegerList = new List() { 1, 2, 3 } 170 | } 171 | }; 172 | 173 | // create patch 174 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 175 | patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/1"); 176 | 177 | var serialized = JsonConvert.SerializeObject(patchDoc); 178 | var deserialized = JsonConvert.DeserializeObject(serialized); 179 | deserialized.ApplyTo(doc); 180 | 181 | Assert.Equal(new List() { 1, 1, 2, 3 }, doc.SimpleDTO.IntegerList); 182 | } 183 | 184 | [Fact] 185 | public void NestedCopyFromListToEndOfList() 186 | { 187 | var doc = new SimpleDTOWithNestedDTO() 188 | { 189 | SimpleDTO = new SimpleDTO() 190 | { 191 | IntegerList = new List() { 1, 2, 3 } 192 | } 193 | }; 194 | 195 | // create patch 196 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 197 | patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/-"); 198 | 199 | var serialized = JsonConvert.SerializeObject(patchDoc); 200 | var deserialized = JsonConvert.DeserializeObject(serialized); 201 | 202 | deserialized.ApplyTo(doc); 203 | 204 | Assert.Equal(new List() { 1, 2, 3, 1 }, doc.SimpleDTO.IntegerList); 205 | } 206 | 207 | [Fact] 208 | public void NestedCopyFromListToNonList() 209 | { 210 | var doc = new SimpleDTOWithNestedDTO() 211 | { 212 | SimpleDTO = new SimpleDTO() 213 | { 214 | IntegerList = new List() { 1, 2, 3 } 215 | } 216 | }; 217 | 218 | // create patch 219 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 220 | patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerValue"); 221 | 222 | var serialized = JsonConvert.SerializeObject(patchDoc); 223 | var deserialized = JsonConvert.DeserializeObject(serialized); 224 | deserialized.ApplyTo(doc); 225 | 226 | Assert.Equal(1, doc.SimpleDTO.IntegerValue); 227 | } 228 | 229 | [Fact] 230 | public void NestedCopyFromNonListToList() 231 | { 232 | var doc = new SimpleDTOWithNestedDTO() 233 | { 234 | SimpleDTO = new SimpleDTO() 235 | { 236 | IntegerValue = 5, 237 | IntegerList = new List() { 1, 2, 3 } 238 | } 239 | }; 240 | 241 | // create patch 242 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 243 | patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/0"); 244 | var serialized = JsonConvert.SerializeObject(patchDoc); 245 | var deserialized = JsonConvert.DeserializeObject(serialized); 246 | deserialized.ApplyTo(doc); 247 | 248 | Assert.Equal(new List() { 5, 1, 2, 3 }, doc.SimpleDTO.IntegerList); 249 | } 250 | 251 | [Fact] 252 | public void NestedCopyToEndOfList() 253 | { 254 | var doc = new SimpleDTOWithNestedDTO() 255 | { 256 | SimpleDTO = new SimpleDTO() 257 | { 258 | IntegerValue = 5, 259 | IntegerList = new List() { 1, 2, 3 } 260 | } 261 | }; 262 | 263 | // create patch 264 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 265 | patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/-"); 266 | 267 | var serialized = JsonConvert.SerializeObject(patchDoc); 268 | var deserialized = JsonConvert.DeserializeObject(serialized); 269 | deserialized.ApplyTo(doc); 270 | 271 | Assert.Equal(new List() { 1, 2, 3, 5 }, doc.SimpleDTO.IntegerList); 272 | } 273 | 274 | [Fact] 275 | public void CopyInGenericList() 276 | { 277 | var doc = new SimpleDTO 278 | { 279 | IntegerGenericList = new List { 1, 2, 3 } 280 | }; 281 | 282 | // create patch 283 | var patchDoc = new JsonPatchDocument(); 284 | patchDoc.Copy(o => o.IntegerGenericList, 0, o => o.IntegerGenericList, 1); 285 | 286 | patchDoc.ApplyTo(doc); 287 | 288 | Assert.Equal(new List { 1, 1, 2, 3 }, doc.IntegerGenericList); 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/JsonPropertyDTO.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 9 | { 10 | public class JsonPropertyDTO 11 | { 12 | [JsonProperty("AnotherName")] 13 | public string Name { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/JsonPropertyTests.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 10 | { 11 | public class JsonPropertyTests 12 | { 13 | [Fact] 14 | public void HonourJsonPropertyOnSerialization() 15 | { 16 | // create patch 17 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 18 | patchDoc.Add(p => p.Name, "Kevin"); 19 | 20 | var serialized = JsonConvert.SerializeObject(patchDoc); 21 | // serialized value should have "AnotherName" as path 22 | // deserialize to a JsonPatchDocument to check 23 | var deserialized = 24 | JsonConvert.DeserializeObject>(serialized); 25 | 26 | Assert.Equal(deserialized.Operations.First().path, "/anothername"); 27 | } 28 | 29 | [Fact] 30 | public void CanApplyToDifferentlyTypedClassWithPropertyMatchingJsonPropertyName() 31 | { 32 | // create patch 33 | JsonPatchDocument patchDocToSerialize = 34 | new JsonPatchDocument(); 35 | patchDocToSerialize.Add(p => p.Name, "Kevin"); 36 | 37 | // the patchdoc will deserialize to "anothername". We should thus be able to apply 38 | // it to a class that HAS that other property name. 39 | 40 | var doc = new JsonPropertyWithAnotherNameDTO() 41 | { 42 | AnotherName = "InitialValue" 43 | }; 44 | 45 | var serialized = JsonConvert.SerializeObject(patchDocToSerialize); 46 | var deserialized = 47 | JsonConvert.DeserializeObject> 48 | (serialized); 49 | 50 | deserialized.ApplyTo(doc); 51 | 52 | Assert.Equal(doc.AnotherName, "Kevin"); 53 | } 54 | 55 | [Fact] 56 | public void CanApplyToSameTypedClassWithMatchingJsonPropertyName() 57 | { 58 | // create patch 59 | JsonPatchDocument patchDocToSerialize = 60 | new JsonPatchDocument(); 61 | patchDocToSerialize.Add(p => p.Name, "Kevin"); 62 | 63 | // the patchdoc will deserialize to "anothername". As JsonPropertyDTO has 64 | // a JsonProperty signifying that "Name" should be deseriallized from "AnotherName", 65 | // we should be able to apply the patchDoc. 66 | 67 | var doc = new JsonPropertyDTO() 68 | { 69 | Name = "InitialValue" 70 | }; 71 | 72 | var serialized = JsonConvert.SerializeObject(patchDocToSerialize); 73 | var deserialized = 74 | JsonConvert.DeserializeObject> 75 | (serialized); 76 | 77 | deserialized.ApplyTo(doc); 78 | 79 | Assert.Equal(doc.Name, "Kevin"); 80 | } 81 | 82 | [Fact] 83 | public void HonourJsonPropertyOnApplyForAdd() 84 | { 85 | var doc = new JsonPropertyDTO() 86 | { 87 | Name = "InitialValue" 88 | }; 89 | 90 | // create patch 91 | //var patchDoc = new JsonPatchDocument(); 92 | //patchDoc.Add(p => p.Name, "Kevin"); 93 | 94 | // serialization should serialize to "AnotherName" 95 | var serialized = "[{\"value\":\"Kevin\",\"path\":\"/AnotherName\",\"op\":\"add\"}]"; 96 | // that means we can deserialize to JsonPatchDocument 97 | var deserialized = 98 | JsonConvert.DeserializeObject>(serialized); 99 | 100 | deserialized.ApplyTo(doc); 101 | 102 | Assert.Equal("Kevin", doc.Name); 103 | } 104 | 105 | 106 | [Fact] 107 | public void HonourJsonPropertyOnApplyForRemove() 108 | { 109 | var doc = new JsonPropertyDTO() 110 | { 111 | Name = "InitialValue" 112 | }; 113 | 114 | // create patch 115 | //var patchDoc = new JsonPatchDocument(); 116 | //patchDoc.Add(p => p.Name, "Kevin"); 117 | 118 | // serialization should serialize to "AnotherName" 119 | var serialized = "[{\"path\":\"/AnotherName\",\"op\":\"remove\"}]"; 120 | // that means we can deserialize to JsonPatchDocument 121 | var deserialized = 122 | JsonConvert.DeserializeObject>(serialized); 123 | 124 | deserialized.ApplyTo(doc); 125 | 126 | Assert.Equal(null, doc.Name); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/JsonPropertyWithAnotherNameDTO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 8 | { 9 | public class JsonPropertyWithAnotherNameDTO 10 | { 11 | public string AnotherName { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/Marvin.JsonPatch.Dynamic.XUnitTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {5E292FEB-38D6-40F4-B6BC-29F7F9AF255A} 9 | Library 10 | Properties 11 | Marvin.JsonPatch.Dynamic.XUnitTest 12 | Marvin.JsonPatch.Dynamic.XUnitTest 13 | v4.5.2 14 | 512 15 | 16 | 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\..\packages\Marvin.JsonPatch.1.1.0\lib\portable40-net40+win8\Marvin.JsonPatch.dll 38 | 39 | 40 | ..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll 41 | True 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll 53 | True 54 | 55 | 56 | ..\..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll 57 | True 58 | 59 | 60 | ..\..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll 61 | True 62 | 63 | 64 | ..\..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll 65 | True 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | {c8dc479f-2216-4b8f-bc26-23e88b6da6d5} 92 | Marvin.JsonPatch.Dynamic 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 105 | 106 | 107 | 108 | 115 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/MoveOperationTests.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Newtonsoft.Json; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Dynamic; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | using Xunit; 14 | 15 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 16 | { 17 | public class MoveOperationTests 18 | { 19 | [Fact] 20 | public void Move() 21 | { 22 | dynamic doc = new ExpandoObject(); 23 | doc.StringProperty = "A"; 24 | doc.AnotherStringProperty = "B"; 25 | 26 | // create patch 27 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 28 | patchDoc.Move("StringProperty", "AnotherStringProperty"); 29 | 30 | var serialized = JsonConvert.SerializeObject(patchDoc); 31 | var deserialized = JsonConvert.DeserializeObject(serialized); 32 | 33 | deserialized.ApplyTo(doc); 34 | 35 | Assert.Equal("A", doc.AnotherStringProperty); 36 | 37 | var cont = doc as IDictionary; 38 | object valueFromDictionary; 39 | cont.TryGetValue("StringProperty", out valueFromDictionary); 40 | Assert.Null(valueFromDictionary); 41 | } 42 | 43 | [Fact] 44 | public void MoveToNonExisting() 45 | { 46 | dynamic doc = new ExpandoObject(); 47 | doc.StringProperty = "A"; 48 | 49 | // create patch 50 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 51 | patchDoc.Move("StringProperty", "AnotherStringProperty"); 52 | 53 | var serialized = JsonConvert.SerializeObject(patchDoc); 54 | var deserialized = JsonConvert.DeserializeObject(serialized); 55 | 56 | deserialized.ApplyTo(doc); 57 | 58 | Assert.Equal("A", doc.AnotherStringProperty); 59 | 60 | var cont = doc as IDictionary; 61 | object valueFromDictionary; 62 | cont.TryGetValue("StringProperty", out valueFromDictionary); 63 | Assert.Null(valueFromDictionary); 64 | } 65 | 66 | [Fact] 67 | public void MoveDynamicToTyped() 68 | { 69 | dynamic doc = new ExpandoObject(); 70 | doc.StringProperty = "A"; 71 | doc.SimpleDTO = new SimpleDTO() { AnotherStringProperty = "B" }; 72 | 73 | // create patch 74 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 75 | patchDoc.Move("StringProperty", "SimpleDTO/AnotherStringProperty"); 76 | 77 | var serialized = JsonConvert.SerializeObject(patchDoc); 78 | var deserialized = JsonConvert.DeserializeObject(serialized); 79 | 80 | deserialized.ApplyTo(doc); 81 | 82 | Assert.Equal("A", doc.SimpleDTO.AnotherStringProperty); 83 | 84 | var cont = doc as IDictionary; 85 | object valueFromDictionary; 86 | cont.TryGetValue("StringProperty", out valueFromDictionary); 87 | Assert.Null(valueFromDictionary); 88 | } 89 | 90 | [Fact] 91 | public void MoveTypedToDynamic() 92 | { 93 | dynamic doc = new ExpandoObject(); 94 | doc.StringProperty = "A"; 95 | doc.SimpleDTO = new SimpleDTO() { AnotherStringProperty = "B" }; 96 | 97 | // create patch 98 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 99 | patchDoc.Move("SimpleDTO/AnotherStringProperty", "StringProperty"); 100 | 101 | var serialized = JsonConvert.SerializeObject(patchDoc); 102 | var deserialized = JsonConvert.DeserializeObject(serialized); 103 | 104 | deserialized.ApplyTo(doc); 105 | 106 | Assert.Equal("B", doc.StringProperty); 107 | Assert.Equal(null, doc.SimpleDTO.AnotherStringProperty); 108 | } 109 | 110 | [Fact] 111 | public void NestedMove() 112 | { 113 | dynamic doc = new ExpandoObject(); 114 | doc.Nested = new SimpleDTO() 115 | { 116 | StringProperty = "A", 117 | AnotherStringProperty = "B" 118 | }; 119 | 120 | // create patch 121 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 122 | patchDoc.Move("Nested/StringProperty", "Nested/AnotherStringProperty"); 123 | 124 | var serialized = JsonConvert.SerializeObject(patchDoc); 125 | var deserialized = JsonConvert.DeserializeObject(serialized); 126 | 127 | deserialized.ApplyTo(doc); 128 | 129 | Assert.Equal("A", doc.Nested.AnotherStringProperty); 130 | Assert.Equal(null, doc.Nested.StringProperty); 131 | } 132 | 133 | [Fact] 134 | public void MoveInList() 135 | { 136 | dynamic doc = new ExpandoObject(); 137 | doc.IntegerList = new List() { 1, 2, 3 }; 138 | 139 | // create patch 140 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 141 | patchDoc.Move("IntegerList/0", "IntegerList/1"); 142 | 143 | var serialized = JsonConvert.SerializeObject(patchDoc); 144 | var deserialized = JsonConvert.DeserializeObject(serialized); 145 | 146 | deserialized.ApplyTo(doc); 147 | 148 | Assert.Equal(new List() { 2, 1, 3 }, doc.IntegerList); 149 | } 150 | 151 | [Fact] 152 | public void NestedMoveInList() 153 | { 154 | dynamic doc = new ExpandoObject(); 155 | doc.Nested = new SimpleDTO() 156 | { 157 | IntegerList = new List() { 1, 2, 3 } 158 | }; 159 | 160 | // create patch 161 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 162 | patchDoc.Move("Nested/IntegerList/0", "Nested/IntegerList/1"); 163 | 164 | var serialized = JsonConvert.SerializeObject(patchDoc); 165 | var deserialized = JsonConvert.DeserializeObject(serialized); 166 | 167 | deserialized.ApplyTo(doc); 168 | 169 | Assert.Equal(new List() { 2, 1, 3 }, doc.Nested.IntegerList); 170 | } 171 | 172 | [Fact] 173 | public void MoveFromListToEndOfList() 174 | { 175 | dynamic doc = new ExpandoObject(); 176 | doc.IntegerList = new List() { 1, 2, 3 }; 177 | 178 | // create patch 179 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 180 | patchDoc.Move("IntegerList/0", "IntegerList/-"); 181 | 182 | var serialized = JsonConvert.SerializeObject(patchDoc); 183 | var deserialized = JsonConvert.DeserializeObject(serialized); 184 | deserialized.ApplyTo(doc); 185 | 186 | Assert.Equal(new List() { 2, 3, 1 }, doc.IntegerList); 187 | } 188 | 189 | [Fact] 190 | public void NestedMoveFromListToEndOfList() 191 | { 192 | dynamic doc = new ExpandoObject(); 193 | doc.Nested = new SimpleDTO() 194 | { 195 | IntegerList = new List() { 1, 2, 3 } 196 | }; 197 | 198 | // create patch 199 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 200 | patchDoc.Move("Nested/IntegerList/0", "Nested/IntegerList/-"); 201 | 202 | var serialized = JsonConvert.SerializeObject(patchDoc); 203 | var deserialized = JsonConvert.DeserializeObject(serialized); 204 | deserialized.ApplyTo(doc); 205 | 206 | Assert.Equal(new List() { 2, 3, 1 }, doc.Nested.IntegerList); 207 | } 208 | 209 | [Fact] 210 | public void MoveFomListToNonList() 211 | { 212 | dynamic doc = new ExpandoObject(); 213 | doc.IntegerList = new List() { 1, 2, 3 }; 214 | 215 | // create patch 216 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 217 | patchDoc.Move("IntegerList/0", "IntegerValue"); 218 | 219 | var serialized = JsonConvert.SerializeObject(patchDoc); 220 | var deserialized = JsonConvert.DeserializeObject(serialized); 221 | 222 | deserialized.ApplyTo(doc); 223 | 224 | Assert.Equal(new List() { 2, 3 }, doc.IntegerList); 225 | Assert.Equal(1, doc.IntegerValue); 226 | } 227 | 228 | [Fact] 229 | public void NestedMoveFomListToNonList() 230 | { 231 | dynamic doc = new ExpandoObject(); 232 | doc.Nested = new SimpleDTO() 233 | { 234 | IntegerList = new List() { 1, 2, 3 } 235 | }; 236 | 237 | // create patch 238 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 239 | patchDoc.Move("Nested/IntegerList/0", "Nested/IntegerValue"); 240 | 241 | var serialized = JsonConvert.SerializeObject(patchDoc); 242 | var deserialized = JsonConvert.DeserializeObject(serialized); 243 | 244 | deserialized.ApplyTo(doc); 245 | 246 | Assert.Equal(new List() { 2, 3 }, doc.Nested.IntegerList); 247 | Assert.Equal(1, doc.Nested.IntegerValue); 248 | } 249 | 250 | [Fact] 251 | public void MoveFromNonListToList() 252 | { 253 | dynamic doc = new ExpandoObject(); 254 | doc.IntegerValue = 5; 255 | doc.IntegerList = new List() { 1, 2, 3 }; 256 | 257 | // create patch 258 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 259 | patchDoc.Move("IntegerValue", "IntegerList/0"); 260 | 261 | var serialized = JsonConvert.SerializeObject(patchDoc); 262 | var deserialized = JsonConvert.DeserializeObject(serialized); 263 | 264 | deserialized.ApplyTo(doc); 265 | 266 | var cont = doc as IDictionary; 267 | object valueFromDictionary; 268 | cont.TryGetValue("IntegerValue", out valueFromDictionary); 269 | Assert.Null(valueFromDictionary); 270 | 271 | Assert.Equal(new List() { 5, 1, 2, 3 }, doc.IntegerList); 272 | } 273 | 274 | [Fact] 275 | public void NestedMoveFromNonListToList() 276 | { 277 | dynamic doc = new ExpandoObject(); 278 | doc.Nested = new SimpleDTO() 279 | { 280 | IntegerValue = 5, 281 | IntegerList = new List() { 1, 2, 3 } 282 | }; 283 | 284 | // create patch 285 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 286 | patchDoc.Move("Nested/IntegerValue", "Nested/IntegerList/0"); 287 | 288 | var serialized = JsonConvert.SerializeObject(patchDoc); 289 | var deserialized = JsonConvert.DeserializeObject(serialized); 290 | 291 | deserialized.ApplyTo(doc); 292 | 293 | Assert.Equal(0, doc.Nested.IntegerValue); 294 | Assert.Equal(new List() { 5, 1, 2, 3 }, doc.Nested.IntegerList); 295 | } 296 | 297 | [Fact] 298 | public void MoveToEndOfList() 299 | { 300 | dynamic doc = new ExpandoObject(); 301 | doc.IntegerValue = 5; 302 | doc.IntegerList = new List() { 1, 2, 3 }; 303 | 304 | // create patch 305 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 306 | patchDoc.Move("IntegerValue", "IntegerList/-"); 307 | 308 | var serialized = JsonConvert.SerializeObject(patchDoc); 309 | var deserialized = JsonConvert.DeserializeObject(serialized); 310 | 311 | deserialized.ApplyTo(doc); 312 | 313 | var cont = doc as IDictionary; 314 | object valueFromDictionary; 315 | cont.TryGetValue("IntegerValue", out valueFromDictionary); 316 | Assert.Null(valueFromDictionary); 317 | 318 | Assert.Equal(new List() { 1, 2, 3, 5 }, doc.IntegerList); 319 | } 320 | 321 | [Fact] 322 | public void NestedMoveToEndOfList() 323 | { 324 | dynamic doc = new ExpandoObject(); 325 | doc.Nested = new SimpleDTO() 326 | { 327 | IntegerValue = 5, 328 | IntegerList = new List() { 1, 2, 3 } 329 | }; 330 | 331 | // create patch 332 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 333 | patchDoc.Move("Nested/IntegerValue", "Nested/IntegerList/-"); 334 | 335 | var serialized = JsonConvert.SerializeObject(patchDoc); 336 | var deserialized = JsonConvert.DeserializeObject(serialized); 337 | 338 | deserialized.ApplyTo(doc); 339 | 340 | Assert.Equal(0, doc.Nested.IntegerValue); 341 | Assert.Equal(new List() { 1, 2, 3, 5 }, doc.Nested.IntegerList); 342 | } 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/MoveTypedOperationTests.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Newtonsoft.Json; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using Xunit; 13 | 14 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 15 | { 16 | public class MoveTypedOperationTests 17 | { 18 | [Fact] 19 | public void Move() 20 | { 21 | var doc = new SimpleDTO() 22 | { 23 | StringProperty = "A", 24 | AnotherStringProperty = "B" 25 | }; 26 | 27 | // create patch 28 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 29 | patchDoc.Move("StringProperty", "AnotherStringProperty"); 30 | 31 | var serialized = JsonConvert.SerializeObject(patchDoc); 32 | var deserialized = JsonConvert.DeserializeObject(serialized); 33 | 34 | deserialized.ApplyTo(doc); 35 | 36 | Assert.Equal("A", doc.AnotherStringProperty); 37 | Assert.Equal(null, doc.StringProperty); 38 | } 39 | 40 | [Fact] 41 | public void MoveInList() 42 | { 43 | var doc = new SimpleDTO() 44 | { 45 | IntegerList = new List() { 1, 2, 3 } 46 | }; 47 | 48 | // create patch 49 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 50 | patchDoc.Move("IntegerList/0","IntegerList/1"); 51 | 52 | var serialized = JsonConvert.SerializeObject(patchDoc); 53 | var deserialized = JsonConvert.DeserializeObject(serialized); 54 | 55 | deserialized.ApplyTo(doc); 56 | 57 | Assert.Equal(new List() { 2, 1, 3 }, doc.IntegerList); 58 | } 59 | 60 | [Fact] 61 | public void MoveFromListToEndOfList() 62 | { 63 | var doc = new SimpleDTO() 64 | { 65 | IntegerList = new List() { 1, 2, 3 } 66 | }; 67 | 68 | // create patch 69 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 70 | patchDoc.Move("IntegerList/0", "IntegerList/-"); 71 | 72 | var serialized = JsonConvert.SerializeObject(patchDoc); 73 | var deserialized = JsonConvert.DeserializeObject(serialized); 74 | deserialized.ApplyTo(doc); 75 | 76 | Assert.Equal(new List() { 2, 3, 1 }, doc.IntegerList); 77 | } 78 | 79 | [Fact] 80 | public void MoveFomListToNonList() 81 | { 82 | var doc = new SimpleDTO() 83 | { 84 | IntegerList = new List() { 1, 2, 3 } 85 | }; 86 | 87 | // create patch 88 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 89 | patchDoc.Move("IntegerList/0","IntegerValue"); 90 | 91 | var serialized = JsonConvert.SerializeObject(patchDoc); 92 | var deserialized = JsonConvert.DeserializeObject(serialized); 93 | 94 | deserialized.ApplyTo(doc); 95 | 96 | Assert.Equal(new List() { 2, 3 }, doc.IntegerList); 97 | Assert.Equal(1, doc.IntegerValue); 98 | } 99 | 100 | [Fact] 101 | public void MoveFromNonListToList() 102 | { 103 | var doc = new SimpleDTO() 104 | { 105 | IntegerValue = 5, 106 | IntegerList = new List() { 1, 2, 3 } 107 | }; 108 | 109 | // create patch 110 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 111 | patchDoc.Move("IntegerValue", "IntegerList/0"); 112 | 113 | var serialized = JsonConvert.SerializeObject(patchDoc); 114 | var deserialized = JsonConvert.DeserializeObject(serialized); 115 | 116 | deserialized.ApplyTo(doc); 117 | 118 | Assert.Equal(0, doc.IntegerValue); 119 | Assert.Equal(new List() { 5, 1, 2, 3 }, doc.IntegerList); 120 | } 121 | 122 | [Fact] 123 | public void MoveToEndOfList() 124 | { 125 | var doc = new SimpleDTO() 126 | { 127 | IntegerValue = 5, 128 | IntegerList = new List() { 1, 2, 3 } 129 | }; 130 | 131 | // create patch 132 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 133 | patchDoc.Move("IntegerValue","IntegerList/-"); 134 | 135 | var serialized = JsonConvert.SerializeObject(patchDoc); 136 | var deserialized = JsonConvert.DeserializeObject(serialized); 137 | 138 | deserialized.ApplyTo(doc); 139 | 140 | Assert.Equal(0, doc.IntegerValue); 141 | Assert.Equal(new List() { 1, 2, 3, 5 }, doc.IntegerList); 142 | } 143 | 144 | [Fact] 145 | public void MoveInGenericList() 146 | { 147 | var doc = new SimpleDTO 148 | { 149 | IntegerGenericList = new List { 1, 2, 3 } 150 | }; 151 | 152 | // create patch 153 | var patchDoc = new JsonPatchDocument(); 154 | patchDoc.Move(o => o.IntegerGenericList, 0, o => o.IntegerGenericList, 1); 155 | 156 | patchDoc.ApplyTo(doc); 157 | 158 | Assert.Equal(new List { 2, 1, 3 }, doc.IntegerGenericList); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/NestedDTO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 8 | { 9 | public class NestedDTO 10 | { 11 | public string StringProperty { get; set; } 12 | 13 | public dynamic DynamicProperty { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/PatchDocumentTests.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Marvin.JsonPatch.Exceptions; 7 | using Newtonsoft.Json; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Dynamic; 11 | using System.Linq; 12 | using System.Text; 13 | using System.Threading.Tasks; 14 | using Xunit; 15 | 16 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 17 | { 18 | public class PatchDocumentTests 19 | { 20 | 21 | 22 | [Fact] 23 | public void InvalidPathAtBeginningShouldThrowException() 24 | { 25 | 26 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 27 | Assert.Throws(() => { patchDoc.Add("//NewInt", 1); }); 28 | 29 | } 30 | 31 | 32 | [Fact] 33 | public void InvalidPathAtEndShouldThrowException() 34 | { 35 | 36 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 37 | Assert.Throws(() => { patchDoc.Add("NewInt//", 1); }); 38 | 39 | } 40 | 41 | 42 | [Fact] 43 | public void InvalidPathWithDotShouldThrowException() 44 | { 45 | 46 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 47 | Assert.Throws(() => { patchDoc.Add("NewInt.Test", 1); }); 48 | 49 | } 50 | 51 | 52 | 53 | [Fact] 54 | public void NonGenericPatchDocToGenericMustSerialize() 55 | { 56 | var doc = new SimpleDTO() 57 | { 58 | StringProperty = "A", 59 | AnotherStringProperty = "B" 60 | }; 61 | 62 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 63 | patchDoc.Copy("StringProperty", "AnotherStringProperty"); 64 | 65 | var serialized = JsonConvert.SerializeObject(patchDoc); 66 | var deserialized = JsonConvert.DeserializeObject>(serialized); 67 | 68 | deserialized.ApplyTo(doc); 69 | 70 | Assert.Equal("A", doc.AnotherStringProperty); 71 | 72 | } 73 | 74 | 75 | [Fact] 76 | public void GenericPatchDocToNonGenericMustSerialize() 77 | { 78 | var doc = new SimpleDTO() 79 | { 80 | StringProperty = "A", 81 | AnotherStringProperty = "B" 82 | }; 83 | 84 | JsonPatchDocument patchDocTyped = new JsonPatchDocument(); 85 | patchDocTyped.Copy(o => o.StringProperty, o => o.AnotherStringProperty); 86 | 87 | 88 | JsonPatchDocument patchDocUntyped = new JsonPatchDocument(); 89 | patchDocUntyped.Copy("StringProperty", "AnotherStringProperty"); 90 | 91 | 92 | var serializedTyped = JsonConvert.SerializeObject(patchDocTyped); 93 | var serializedUntyped = JsonConvert.SerializeObject(patchDocUntyped); 94 | var deserialized = JsonConvert.DeserializeObject(serializedTyped); 95 | 96 | deserialized.ApplyTo(doc); 97 | 98 | Assert.Equal("A", doc.AnotherStringProperty); 99 | 100 | } 101 | 102 | 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Marvin.JsonPatch.Dynamic.XUnitTest")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Marvin.JsonPatch.Dynamic.XUnitTest")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5e292feb-38d6-40f4-b6bc-29f7f9af255a")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/RemoveOperationTests.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Marvin.JsonPatch.Exceptions; 7 | using Newtonsoft.Json; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Dynamic; 11 | using System.Linq; 12 | using System.Text; 13 | using System.Threading.Tasks; 14 | using Xunit; 15 | 16 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 17 | { 18 | public class RemoveOperationTests 19 | { 20 | 21 | [Fact] 22 | public void RemovePropertyShouldFailIfRootIsAnonymous() 23 | { 24 | dynamic doc = new 25 | { 26 | Test = 1 27 | }; 28 | 29 | // create patch 30 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 31 | patchDoc.Remove("Test"); 32 | 33 | var serialized = JsonConvert.SerializeObject(patchDoc); 34 | var deserialized = JsonConvert.DeserializeObject(serialized); 35 | 36 | Assert.Throws(() => { deserialized.ApplyTo(doc); }); 37 | } 38 | 39 | [Fact] 40 | public void RemovePropertyShouldFailIfItDoesntExist() 41 | { 42 | dynamic doc = new ExpandoObject(); 43 | doc.Test = 1; 44 | 45 | // create patch 46 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 47 | patchDoc.Remove("NonExisting"); 48 | 49 | var serialized = JsonConvert.SerializeObject(patchDoc); 50 | var deserialized = JsonConvert.DeserializeObject(serialized); 51 | 52 | Assert.Throws(() => { deserialized.ApplyTo(doc); }); 53 | } 54 | 55 | [Fact] 56 | public void RemovePropertyFromExpandoObject() 57 | { 58 | dynamic obj = new ExpandoObject(); 59 | obj.Test = 1; 60 | 61 | // create patch 62 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 63 | patchDoc.Remove("Test"); 64 | 65 | var serialized = JsonConvert.SerializeObject(patchDoc); 66 | var deserialized = JsonConvert.DeserializeObject(serialized); 67 | 68 | deserialized.ApplyTo(obj); 69 | 70 | var cont = obj as IDictionary; 71 | 72 | object valueFromDictionary; 73 | 74 | cont.TryGetValue("Test", out valueFromDictionary); 75 | 76 | Assert.Null(valueFromDictionary); 77 | } 78 | 79 | [Fact] 80 | public void RemovePropertyFromExpandoObjectMixedCase() 81 | { 82 | dynamic obj = new ExpandoObject(); 83 | obj.Test = 1; 84 | 85 | // create patch 86 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 87 | patchDoc.Remove("test"); 88 | 89 | var serialized = JsonConvert.SerializeObject(patchDoc); 90 | var deserialized = JsonConvert.DeserializeObject(serialized); 91 | 92 | deserialized.ApplyTo(obj); 93 | 94 | var cont = obj as IDictionary; 95 | 96 | object valueFromDictionary; 97 | 98 | cont.TryGetValue("Test", out valueFromDictionary); 99 | 100 | Assert.Null(valueFromDictionary); 101 | } 102 | 103 | [Fact] 104 | public void RemoveNestedPropertyFromExpandoObject() 105 | { 106 | dynamic obj = new ExpandoObject(); 107 | obj.Test = new ExpandoObject(); 108 | obj.Test.AnotherTest = "A"; 109 | 110 | // create patch 111 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 112 | patchDoc.Remove("Test"); 113 | 114 | var serialized = JsonConvert.SerializeObject(patchDoc); 115 | var deserialized = JsonConvert.DeserializeObject(serialized); 116 | 117 | deserialized.ApplyTo(obj); 118 | 119 | var cont = obj as IDictionary; 120 | 121 | object valueFromDictionary; 122 | 123 | cont.TryGetValue("Test", out valueFromDictionary); 124 | 125 | Assert.Null(valueFromDictionary); 126 | } 127 | 128 | [Fact] 129 | public void RemoveNestedPropertyFromExpandoObjectMixedCase() 130 | { 131 | dynamic obj = new ExpandoObject(); 132 | obj.Test = new ExpandoObject(); 133 | obj.Test.AnotherTest = "A"; 134 | 135 | // create patch 136 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 137 | patchDoc.Remove("test"); 138 | 139 | var serialized = JsonConvert.SerializeObject(patchDoc); 140 | var deserialized = JsonConvert.DeserializeObject(serialized); 141 | 142 | deserialized.ApplyTo(obj); 143 | 144 | var cont = obj as IDictionary; 145 | 146 | object valueFromDictionary; 147 | 148 | cont.TryGetValue("Test", out valueFromDictionary); 149 | 150 | Assert.Null(valueFromDictionary); 151 | } 152 | 153 | [Fact] 154 | public void NestedRemove() 155 | { 156 | dynamic doc = new ExpandoObject(); 157 | doc.SimpleDTO = new SimpleDTO() 158 | { 159 | StringProperty = "A" 160 | }; 161 | 162 | // create patch 163 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 164 | patchDoc.Remove("SimpleDTO/StringProperty"); 165 | 166 | var serialized = JsonConvert.SerializeObject(patchDoc); 167 | var deserialized = JsonConvert.DeserializeObject(serialized); 168 | 169 | deserialized.ApplyTo(doc); 170 | 171 | Assert.Equal(null, doc.SimpleDTO.StringProperty); 172 | } 173 | 174 | [Fact] 175 | public void NestedRemoveMixedCase() 176 | { 177 | dynamic doc = new ExpandoObject(); 178 | doc.SimpleDTO = new SimpleDTO() 179 | { 180 | StringProperty = "A" 181 | }; 182 | 183 | // create patch 184 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 185 | patchDoc.Remove("Simpledto/stringProperty"); 186 | 187 | var serialized = JsonConvert.SerializeObject(patchDoc); 188 | var deserialized = JsonConvert.DeserializeObject(serialized); 189 | 190 | deserialized.ApplyTo(doc); 191 | 192 | Assert.Equal(null, doc.SimpleDTO.StringProperty); 193 | } 194 | 195 | [Fact] 196 | public void NestedRemoveFromList() 197 | { 198 | dynamic doc = new ExpandoObject(); 199 | doc.SimpleDTO = new SimpleDTO() 200 | { 201 | IntegerList = new List() { 1, 2, 3 } 202 | }; 203 | 204 | // create patch 205 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 206 | patchDoc.Remove("SimpleDTO/IntegerList/2"); 207 | 208 | var serialized = JsonConvert.SerializeObject(patchDoc); 209 | var deserialized = JsonConvert.DeserializeObject(serialized); 210 | 211 | deserialized.ApplyTo(doc); 212 | 213 | Assert.Equal(new List() { 1, 2 }, doc.SimpleDTO.IntegerList); 214 | } 215 | 216 | [Fact] 217 | public void NestedRemoveFromListMixedCase() 218 | { 219 | dynamic doc = new ExpandoObject(); 220 | doc.SimpleDTO = new SimpleDTO() 221 | { 222 | IntegerList = new List() { 1, 2, 3 } 223 | }; 224 | 225 | // create patch 226 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 227 | patchDoc.Remove("SimpleDTO/Integerlist/2"); 228 | 229 | var serialized = JsonConvert.SerializeObject(patchDoc); 230 | var deserialized = JsonConvert.DeserializeObject(serialized); 231 | 232 | deserialized.ApplyTo(doc); 233 | 234 | Assert.Equal(new List() { 1, 2 }, doc.SimpleDTO.IntegerList); 235 | } 236 | 237 | [Fact] 238 | public void NestedRemoveFromListInvalidPositionTooLarge() 239 | { 240 | dynamic doc = new ExpandoObject(); 241 | doc.SimpleDTO = new SimpleDTO() 242 | { 243 | IntegerList = new List() { 1, 2, 3 } 244 | }; 245 | 246 | // create patch 247 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 248 | patchDoc.Remove("SimpleDTO/IntegerList/3"); 249 | 250 | var serialized = JsonConvert.SerializeObject(patchDoc); 251 | var deserialized = JsonConvert.DeserializeObject(serialized); 252 | 253 | Assert.Throws(() => { deserialized.ApplyTo(doc); }); 254 | } 255 | 256 | [Fact] 257 | public void NestedRemoveFromListInvalidPositionTooSmall() 258 | { 259 | dynamic doc = new ExpandoObject(); 260 | doc.SimpleDTO = new SimpleDTO() 261 | { 262 | IntegerList = new List() { 1, 2, 3 } 263 | }; 264 | 265 | // create patch 266 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 267 | patchDoc.Remove("SimpleDTO/IntegerList/-1"); 268 | 269 | var serialized = JsonConvert.SerializeObject(patchDoc); 270 | var deserialized = JsonConvert.DeserializeObject(serialized); 271 | 272 | Assert.Throws(() => { deserialized.ApplyTo(doc); }); 273 | } 274 | 275 | [Fact] 276 | public void NestedRemoveFromEndOfList() 277 | { 278 | dynamic doc = new ExpandoObject(); 279 | doc.SimpleDTO = new SimpleDTO() 280 | { 281 | IntegerList = new List() { 1, 2, 3 } 282 | }; 283 | 284 | // create patch 285 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 286 | patchDoc.Remove("SimpleDTO/IntegerList/-"); 287 | 288 | var serialized = JsonConvert.SerializeObject(patchDoc); 289 | var deserialized = JsonConvert.DeserializeObject(serialized); 290 | 291 | deserialized.ApplyTo(doc); 292 | 293 | Assert.Equal(new List() { 1, 2 }, doc.SimpleDTO.IntegerList); 294 | } 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/RemoveTypedOperationTests.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Marvin.JsonPatch.Exceptions; 7 | using Newtonsoft.Json; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | using Xunit; 14 | 15 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 16 | { 17 | public class RemoveTypedOperationTests 18 | { 19 | [Fact] 20 | public void Remove() 21 | { 22 | var doc = new SimpleDTO() 23 | { 24 | StringProperty = "A" 25 | }; 26 | 27 | // create patch 28 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 29 | patchDoc.Remove("StringProperty"); 30 | 31 | var serialized = JsonConvert.SerializeObject(patchDoc); 32 | var deserialized = JsonConvert.DeserializeObject(serialized); 33 | 34 | deserialized.ApplyTo(doc); 35 | 36 | Assert.Equal(null, doc.StringProperty); 37 | } 38 | 39 | [Fact] 40 | public void RemoveFromList() 41 | { 42 | var doc = new SimpleDTO() 43 | { 44 | IntegerList = new List() { 1, 2, 3 } 45 | }; 46 | 47 | // create patch 48 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 49 | patchDoc.Remove("IntegerList/2"); 50 | 51 | var serialized = JsonConvert.SerializeObject(patchDoc); 52 | var deserialized = JsonConvert.DeserializeObject(serialized); 53 | 54 | deserialized.ApplyTo(doc); 55 | 56 | Assert.Equal(new List() { 1, 2 }, doc.IntegerList); 57 | } 58 | 59 | [Fact] 60 | public void RemoveFromListInvalidPositionTooLarge() 61 | { 62 | var doc = new SimpleDTO() 63 | { 64 | IntegerList = new List() { 1, 2, 3 } 65 | }; 66 | 67 | // create patch 68 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 69 | patchDoc.Remove("IntegerList/3"); 70 | 71 | var serialized = JsonConvert.SerializeObject(patchDoc); 72 | var deserialized = JsonConvert.DeserializeObject(serialized); 73 | 74 | Assert.Throws(() => { deserialized.ApplyTo(doc); }); 75 | } 76 | 77 | [Fact] 78 | public void RemoveFromListInvalidPositionTooSmall() 79 | { 80 | var doc = new SimpleDTO() 81 | { 82 | IntegerList = new List() { 1, 2, 3 } 83 | }; 84 | 85 | // create patch 86 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 87 | patchDoc.Remove("IntegerList/-1"); 88 | 89 | var serialized = JsonConvert.SerializeObject(patchDoc); 90 | var deserialized = JsonConvert.DeserializeObject(serialized); 91 | 92 | Assert.Throws(() => { deserialized.ApplyTo(doc); }); 93 | } 94 | 95 | [Fact] 96 | public void RemoveFromEndOfList() 97 | { 98 | var doc = new SimpleDTO() 99 | { 100 | IntegerList = new List() { 1, 2, 3 } 101 | }; 102 | 103 | // create patch 104 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 105 | patchDoc.Remove("IntegerList/-"); 106 | 107 | var serialized = JsonConvert.SerializeObject(patchDoc); 108 | var deserialized = JsonConvert.DeserializeObject(serialized); 109 | 110 | deserialized.ApplyTo(doc); 111 | 112 | Assert.Equal(new List() { 1, 2 }, doc.IntegerList); 113 | } 114 | 115 | [Fact] 116 | public void NestedRemove() 117 | { 118 | var doc = new SimpleDTOWithNestedDTO() 119 | { 120 | SimpleDTO = new SimpleDTO() 121 | { 122 | StringProperty = "A" 123 | } 124 | }; 125 | 126 | // create patch 127 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 128 | patchDoc.Remove("SimpleDTO/StringProperty"); 129 | 130 | var serialized = JsonConvert.SerializeObject(patchDoc); 131 | var deserialized = JsonConvert.DeserializeObject(serialized); 132 | 133 | deserialized.ApplyTo(doc); 134 | 135 | Assert.Equal(null, doc.SimpleDTO.StringProperty); 136 | } 137 | 138 | [Fact] 139 | public void NestedRemoveFromList() 140 | { 141 | var doc = new SimpleDTOWithNestedDTO() 142 | { 143 | SimpleDTO = new SimpleDTO() 144 | { 145 | IntegerList = new List() { 1, 2, 3 } 146 | } 147 | }; 148 | 149 | // create patch 150 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 151 | patchDoc.Remove("SimpleDTO/IntegerList/2"); 152 | 153 | var serialized = JsonConvert.SerializeObject(patchDoc); 154 | var deserialized = JsonConvert.DeserializeObject(serialized); 155 | 156 | deserialized.ApplyTo(doc); 157 | 158 | Assert.Equal(new List() { 1, 2 }, doc.SimpleDTO.IntegerList); 159 | } 160 | 161 | [Fact] 162 | public void NestedRemoveFromListInvalidPositionTooLarge() 163 | { 164 | var doc = new SimpleDTOWithNestedDTO() 165 | { 166 | SimpleDTO = new SimpleDTO() 167 | { 168 | IntegerList = new List() { 1, 2, 3 } 169 | } 170 | }; 171 | 172 | // create patch 173 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 174 | patchDoc.Remove("SimpleDTO/IntegerList/3"); 175 | 176 | var serialized = JsonConvert.SerializeObject(patchDoc); 177 | var deserialized = JsonConvert.DeserializeObject(serialized); 178 | 179 | Assert.Throws(() => { deserialized.ApplyTo(doc); }); 180 | } 181 | 182 | [Fact] 183 | public void NestedRemoveFromListInvalidPositionTooSmall() 184 | { 185 | var doc = new SimpleDTOWithNestedDTO() 186 | { 187 | SimpleDTO = new SimpleDTO() 188 | { 189 | IntegerList = new List() { 1, 2, 3 } 190 | } 191 | }; 192 | 193 | // create patch 194 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 195 | patchDoc.Remove("SimpleDTO/IntegerList/-1"); 196 | 197 | var serialized = JsonConvert.SerializeObject(patchDoc); 198 | var deserialized = JsonConvert.DeserializeObject(serialized); 199 | 200 | Assert.Throws(() => { deserialized.ApplyTo(doc); }); 201 | } 202 | 203 | [Fact] 204 | public void NestedRemoveFromEndOfList() 205 | { 206 | var doc = new SimpleDTOWithNestedDTO() 207 | { 208 | SimpleDTO = new SimpleDTO() 209 | { 210 | IntegerList = new List() { 1, 2, 3 } 211 | } 212 | }; 213 | 214 | // create patch 215 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 216 | patchDoc.Remove("SimpleDTO/IntegerList/-"); 217 | 218 | var serialized = JsonConvert.SerializeObject(patchDoc); 219 | var deserialized = JsonConvert.DeserializeObject(serialized); 220 | 221 | deserialized.ApplyTo(doc); 222 | 223 | Assert.Equal(new List() { 1, 2 }, doc.SimpleDTO.IntegerList); 224 | } 225 | 226 | [Fact] 227 | public void RemoveFromGenericList() 228 | { 229 | var doc = new SimpleDTO 230 | { 231 | IntegerGenericList = new List { 1, 2, 3 } 232 | }; 233 | 234 | // create patch 235 | var patchDoc = new JsonPatchDocument(); 236 | patchDoc.Remove(o => o.IntegerGenericList, 2); 237 | 238 | patchDoc.ApplyTo(doc); 239 | 240 | Assert.Equal(new List { 1, 2 }, doc.IntegerGenericList); 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/ReplaceOperationTests.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Marvin.JsonPatch.Exceptions; 7 | using Newtonsoft.Json; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Collections.ObjectModel; 11 | using System.Dynamic; 12 | using System.Linq; 13 | using System.Text; 14 | using System.Threading.Tasks; 15 | using Xunit; 16 | 17 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 18 | { 19 | public class ReplaceOperationTests 20 | { 21 | [Fact] 22 | public void ReplaceGuidTest() 23 | { 24 | dynamic doc = new SimpleDTO() 25 | { 26 | GuidValue = Guid.NewGuid() 27 | }; 28 | 29 | var newGuid = Guid.NewGuid(); 30 | // create patch 31 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 32 | patchDoc.Replace("GuidValue", newGuid); 33 | 34 | // serialize & deserialize 35 | var serialized = JsonConvert.SerializeObject(patchDoc); 36 | var deserizalized = JsonConvert.DeserializeObject(serialized); 37 | 38 | deserizalized.ApplyTo(doc); 39 | 40 | Assert.Equal(newGuid, doc.GuidValue); 41 | } 42 | 43 | [Fact] 44 | public void ReplaceGuidTestExpandoObject() 45 | { 46 | dynamic doc = new ExpandoObject(); 47 | doc.GuidValue = Guid.NewGuid(); 48 | 49 | var newGuid = Guid.NewGuid(); 50 | // create patch 51 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 52 | patchDoc.Replace("GuidValue", newGuid); 53 | 54 | // serialize & deserialize 55 | var serialized = JsonConvert.SerializeObject(patchDoc); 56 | var deserizalized = JsonConvert.DeserializeObject(serialized); 57 | 58 | deserizalized.ApplyTo(doc); 59 | 60 | Assert.Equal(newGuid, doc.GuidValue); 61 | } 62 | 63 | [Fact] 64 | public void ReplaceGuidTestExpandoObjectInAnonymous() 65 | { 66 | dynamic nestedObject = new ExpandoObject(); 67 | nestedObject.GuidValue = Guid.NewGuid(); 68 | 69 | dynamic doc = new 70 | { 71 | NestedObject = nestedObject 72 | }; 73 | 74 | var newGuid = Guid.NewGuid(); 75 | // create patch 76 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 77 | patchDoc.Replace("nestedobject/GuidValue", newGuid); 78 | 79 | // serialize & deserialize 80 | var serialized = JsonConvert.SerializeObject(patchDoc); 81 | var deserizalized = JsonConvert.DeserializeObject(serialized); 82 | 83 | deserizalized.ApplyTo(doc); 84 | 85 | Assert.Equal(newGuid, doc.NestedObject.GuidValue); 86 | } 87 | 88 | [Fact] 89 | public void ReplaceNestedObjectTest() 90 | { 91 | dynamic doc = new ExpandoObject(); 92 | doc.SimpleDTO = new SimpleDTO() 93 | { 94 | IntegerValue = 5, 95 | IntegerList = new List() { 1, 2, 3 } 96 | }; 97 | 98 | var newDTO = new SimpleDTO() 99 | { 100 | DoubleValue = 1 101 | }; 102 | 103 | // create patch 104 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 105 | patchDoc.Replace("SimpleDTO", newDTO); 106 | 107 | // serialize & deserialize 108 | var serialized = JsonConvert.SerializeObject(patchDoc); 109 | var deserialized = JsonConvert.DeserializeObject(serialized); 110 | 111 | deserialized.ApplyTo(doc); 112 | 113 | Assert.Equal(1, doc.SimpleDTO.DoubleValue); 114 | Assert.Equal(0, doc.SimpleDTO.IntegerValue); 115 | Assert.Equal(null, doc.SimpleDTO.IntegerList); 116 | } 117 | 118 | [Fact] 119 | public void ReplaceInList() 120 | { 121 | dynamic doc = new ExpandoObject(); 122 | doc.IntegerList = new List() { 1, 2, 3 }; 123 | 124 | // create patch 125 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 126 | patchDoc.Replace("IntegerList/0", 5); 127 | 128 | var serialized = JsonConvert.SerializeObject(patchDoc); 129 | var deserialized = JsonConvert.DeserializeObject(serialized); 130 | 131 | deserialized.ApplyTo(doc); 132 | 133 | Assert.Equal(new List() { 5, 2, 3 }, doc.IntegerList); 134 | } 135 | 136 | [Fact] 137 | public void ReplaceFullList() 138 | { 139 | dynamic doc = new ExpandoObject(); 140 | doc.IntegerList = new List() { 1, 2, 3 }; 141 | 142 | // create patch 143 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 144 | patchDoc.Replace("IntegerList", new List() { 4, 5, 6 }); 145 | 146 | var serialized = JsonConvert.SerializeObject(patchDoc); 147 | var deserialized = JsonConvert.DeserializeObject(serialized); 148 | 149 | deserialized.ApplyTo(doc); 150 | 151 | Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList); 152 | } 153 | 154 | [Fact] 155 | public void ReplaceInListInList() 156 | { 157 | dynamic doc = new ExpandoObject(); 158 | doc.SimpleDTOList = new List() { 159 | new SimpleDTO() { 160 | IntegerList = new List(){1,2,3} 161 | }}; 162 | 163 | // create patch 164 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 165 | patchDoc.Replace("SimpleDTOList/0/IntegerList/0", 4); 166 | 167 | var serialized = JsonConvert.SerializeObject(patchDoc); 168 | var deserialized = JsonConvert.DeserializeObject(serialized); 169 | 170 | deserialized.ApplyTo(doc); 171 | 172 | Assert.Equal(4, doc.SimpleDTOList[0].IntegerList[0]); 173 | } 174 | 175 | [Fact] 176 | public void ReplaceInListInListAtEnd() 177 | { 178 | dynamic doc = new ExpandoObject(); 179 | doc.SimpleDTOList = new List() { 180 | new SimpleDTO() { 181 | IntegerList = new List(){1,2,3} 182 | }}; 183 | 184 | // create patch 185 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 186 | patchDoc.Replace("SimpleDTOList/0/IntegerList/-", 4); 187 | 188 | var serialized = JsonConvert.SerializeObject(patchDoc); 189 | var deserialized = JsonConvert.DeserializeObject(serialized); 190 | 191 | deserialized.ApplyTo(doc); 192 | 193 | Assert.Equal(4, doc.SimpleDTOList[0].IntegerList[2]); 194 | } 195 | 196 | [Fact] 197 | public void ReplaceInListInListInvalidPositionTooLarge() 198 | { 199 | dynamic doc = new ExpandoObject(); 200 | doc.SimpleDTOList = new List() { 201 | new SimpleDTO() { 202 | IntegerList = new List(){1,2,3} 203 | }}; 204 | 205 | // create patch 206 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 207 | patchDoc.Replace("SimpleDTOList/10/IntegerList/0", 4); 208 | 209 | var serialized = JsonConvert.SerializeObject(patchDoc); 210 | var deserialized = JsonConvert.DeserializeObject(serialized); 211 | 212 | Assert.Throws(() => { deserialized.ApplyTo(doc); }); 213 | } 214 | 215 | [Fact] 216 | public void ReplaceInListInListInvalidPositionTooSmall() 217 | { 218 | dynamic doc = new ExpandoObject(); 219 | doc.SimpleDTOList = new List() { 220 | new SimpleDTO() { 221 | IntegerList = new List(){1,2,3} 222 | }}; 223 | 224 | // create patch 225 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 226 | patchDoc.Replace("SimpleDTOList/-20/IntegerList/0", 4); 227 | 228 | var serialized = JsonConvert.SerializeObject(patchDoc); 229 | var deserialized = JsonConvert.DeserializeObject(serialized); 230 | 231 | Assert.Throws(() => { deserialized.ApplyTo(doc); }); 232 | } 233 | 234 | [Fact] 235 | public void ReplaceFullListFromEnumerable() 236 | { 237 | dynamic doc = new ExpandoObject(); 238 | doc.IntegerList = new List() { 1, 2, 3 }; 239 | 240 | // create patch 241 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 242 | patchDoc.Replace("IntegerList", new List() { 4, 5, 6 }); 243 | 244 | var serialized = JsonConvert.SerializeObject(patchDoc); 245 | var deserialized = JsonConvert.DeserializeObject(serialized); 246 | 247 | deserialized.ApplyTo(doc); 248 | Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList); 249 | } 250 | 251 | [Fact] 252 | public void ReplaceFullListWithCollection() 253 | { 254 | dynamic doc = new ExpandoObject(); 255 | doc.IntegerList = new List() { 1, 2, 3 }; 256 | 257 | // create patch 258 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 259 | patchDoc.Replace("IntegerList", new Collection() { 4, 5, 6 }); 260 | 261 | var serialized = JsonConvert.SerializeObject(patchDoc); 262 | var deserialized = JsonConvert.DeserializeObject(serialized); 263 | 264 | deserialized.ApplyTo(doc); 265 | 266 | Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList); 267 | } 268 | 269 | [Fact] 270 | public void ReplaceAtEndOfList() 271 | { 272 | dynamic doc = new ExpandoObject(); 273 | doc.IntegerList = new List() { 1, 2, 3 }; 274 | 275 | // create patch 276 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 277 | patchDoc.Replace("IntegerList/-", 5); 278 | 279 | var serialized = JsonConvert.SerializeObject(patchDoc); 280 | var deserialized = JsonConvert.DeserializeObject(serialized); 281 | deserialized.ApplyTo(doc); 282 | 283 | Assert.Equal(new List() { 1, 2, 5 }, doc.IntegerList); 284 | } 285 | 286 | [Fact] 287 | public void ReplaceInListInvalidInvalidPositionTooLarge() 288 | { 289 | dynamic doc = new ExpandoObject(); 290 | doc.IntegerList = new List() { 1, 2, 3 }; 291 | 292 | // create patch 293 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 294 | patchDoc.Replace("IntegerList/3", 5); 295 | 296 | var serialized = JsonConvert.SerializeObject(patchDoc); 297 | var deserialized = JsonConvert.DeserializeObject(serialized); 298 | 299 | Assert.Throws(() => 300 | { 301 | deserialized.ApplyTo(doc); 302 | }); 303 | } 304 | 305 | [Fact] 306 | public void ReplaceInListInvalidPositionTooSmall() 307 | { 308 | dynamic doc = new ExpandoObject(); 309 | doc.IntegerList = new List() { 1, 2, 3 }; 310 | 311 | // create patch 312 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 313 | patchDoc.Replace("IntegerList/-1", 5); 314 | 315 | var serialized = JsonConvert.SerializeObject(patchDoc); 316 | var deserialized = JsonConvert.DeserializeObject(serialized); 317 | 318 | Assert.Throws(() => 319 | { 320 | deserialized.ApplyTo(doc); 321 | }); 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/ReplaceTypedOperationTests.cs: -------------------------------------------------------------------------------- 1 | // Any comments, input: @KevinDockx 2 | // Any issues, requests: https://github.com/KevinDockx/JsonPatch.Dynamic 3 | // 4 | // Enjoy :-) 5 | 6 | using Marvin.JsonPatch.Exceptions; 7 | using Newtonsoft.Json; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Collections.ObjectModel; 11 | using System.Linq; 12 | using System.Text; 13 | using System.Threading.Tasks; 14 | using Xunit; 15 | 16 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 17 | { 18 | public class ReplaceTypedOperationTests 19 | { 20 | [Fact] 21 | public void ReplaceGuidTest() 22 | { 23 | var doc = new SimpleDTO() 24 | { 25 | GuidValue = Guid.NewGuid() 26 | }; 27 | 28 | var newGuid = Guid.NewGuid(); 29 | // create patch 30 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 31 | patchDoc.Replace("GuidValue", newGuid); 32 | 33 | // serialize & deserialize 34 | var serialized = JsonConvert.SerializeObject(patchDoc); 35 | var deserizalized = JsonConvert.DeserializeObject(serialized); 36 | 37 | deserizalized.ApplyTo(doc); 38 | 39 | Assert.Equal(newGuid, doc.GuidValue); 40 | } 41 | 42 | [Fact] 43 | public void SerializeAndReplaceNestedObjectTest() 44 | { 45 | var doc = new SimpleDTOWithNestedDTO() 46 | { 47 | SimpleDTO = new SimpleDTO() 48 | { 49 | IntegerValue = 5, 50 | IntegerList = new List() { 1, 2, 3 } 51 | } 52 | }; 53 | 54 | var newDTO = new SimpleDTO() 55 | { 56 | DoubleValue = 1 57 | }; 58 | 59 | // create patch 60 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 61 | patchDoc.Replace("SimpleDTO", newDTO); 62 | 63 | // serialize & deserialize 64 | var serialized = JsonConvert.SerializeObject(patchDoc); 65 | var deserialized = JsonConvert.DeserializeObject(serialized); 66 | 67 | deserialized.ApplyTo(doc); 68 | 69 | Assert.Equal(1, doc.SimpleDTO.DoubleValue); 70 | Assert.Equal(0, doc.SimpleDTO.IntegerValue); 71 | Assert.Equal(null, doc.SimpleDTO.IntegerList); 72 | } 73 | 74 | [Fact] 75 | public void ReplaceInList() 76 | { 77 | var doc = new SimpleDTO() 78 | { 79 | IntegerList = new List() { 1, 2, 3 } 80 | }; 81 | 82 | // create patch 83 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 84 | patchDoc.Replace("IntegerList/0", 5); 85 | 86 | var serialized = JsonConvert.SerializeObject(patchDoc); 87 | var deserialized = JsonConvert.DeserializeObject(serialized); 88 | 89 | deserialized.ApplyTo(doc); 90 | 91 | Assert.Equal(new List() { 5, 2, 3 }, doc.IntegerList); 92 | } 93 | 94 | 95 | [Fact] 96 | public void ReplaceInGenericList() 97 | { 98 | var doc = new SimpleDTO 99 | { 100 | IntegerGenericList = new List { 1, 2, 3 } 101 | }; 102 | 103 | // create patch 104 | var patchDoc = new JsonPatchDocument(); 105 | patchDoc.Replace(o => o.IntegerGenericList, 5, 0); 106 | 107 | patchDoc.ApplyTo(doc); 108 | 109 | Assert.Equal(new List { 5, 2, 3 }, doc.IntegerGenericList); 110 | } 111 | 112 | [Fact] 113 | public void ReplaceFullList() 114 | { 115 | var doc = new SimpleDTO() 116 | { 117 | IntegerList = new List() { 1, 2, 3 } 118 | }; 119 | 120 | // create patch 121 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 122 | patchDoc.Replace("IntegerList", new List() { 4, 5, 6 }); 123 | 124 | var serialized = JsonConvert.SerializeObject(patchDoc); 125 | var deserialized = JsonConvert.DeserializeObject(serialized); 126 | 127 | deserialized.ApplyTo(doc); 128 | 129 | Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList); 130 | } 131 | 132 | [Fact] 133 | public void ReplaceInListInList() 134 | { 135 | var doc = new SimpleDTO() 136 | { 137 | SimpleDTOList = new List() { 138 | new SimpleDTO() { 139 | IntegerList = new List(){1,2,3} 140 | }} 141 | }; 142 | 143 | // create patch 144 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 145 | patchDoc.Replace("SimpleDTOList/0/IntegerList/0",4); 146 | 147 | var serialized = JsonConvert.SerializeObject(patchDoc); 148 | var deserialized = JsonConvert.DeserializeObject(serialized); 149 | 150 | deserialized.ApplyTo(doc); 151 | 152 | Assert.Equal(4, doc.SimpleDTOList.First().IntegerList.First()); 153 | } 154 | 155 | public void ReplaceInListInListInvalidPosition() 156 | { 157 | var doc = new SimpleDTO() 158 | { 159 | SimpleDTOList = new List() { 160 | new SimpleDTO() { 161 | IntegerList = new List(){1,2,3} 162 | }} 163 | }; 164 | 165 | // create patch 166 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 167 | patchDoc.Replace("SimpleDTOList/0/IntegerList/10", 4); 168 | 169 | var serialized = JsonConvert.SerializeObject(patchDoc); 170 | var deserialized = JsonConvert.DeserializeObject(serialized); 171 | 172 | Assert.Throws(() => { deserialized.ApplyTo(doc); }); 173 | } 174 | 175 | [Fact] 176 | public void ReplaceFullListFromEnumerable() 177 | { 178 | var doc = new SimpleDTO() 179 | { 180 | IntegerList = new List() { 1, 2, 3 } 181 | }; 182 | 183 | // create patch 184 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 185 | patchDoc.Replace("IntegerList", new List() { 4, 5, 6 }); 186 | 187 | var serialized = JsonConvert.SerializeObject(patchDoc); 188 | var deserialized = JsonConvert.DeserializeObject(serialized); 189 | 190 | deserialized.ApplyTo(doc); 191 | 192 | Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList); 193 | } 194 | 195 | [Fact] 196 | public void ReplaceFullListWithCollection() 197 | { 198 | var doc = new SimpleDTO() 199 | { 200 | IntegerList = new List() { 1, 2, 3 } 201 | }; 202 | 203 | // create patch 204 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 205 | patchDoc.Replace("IntegerList", new Collection() { 4, 5, 6 }); 206 | 207 | var serialized = JsonConvert.SerializeObject(patchDoc); 208 | var deserialized = JsonConvert.DeserializeObject(serialized); 209 | 210 | deserialized.ApplyTo(doc); 211 | 212 | Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList); 213 | } 214 | 215 | [Fact] 216 | public void ReplaceAtEndOfList() 217 | { 218 | var doc = new SimpleDTO() 219 | { 220 | IntegerList = new List() { 1, 2, 3 } 221 | }; 222 | 223 | // create patch 224 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 225 | patchDoc.Replace("IntegerList/-", 5); 226 | 227 | var serialized = JsonConvert.SerializeObject(patchDoc); 228 | var deserialized = JsonConvert.DeserializeObject(serialized); 229 | deserialized.ApplyTo(doc); 230 | 231 | Assert.Equal(new List() { 1, 2, 5 }, doc.IntegerList); 232 | } 233 | 234 | [Fact] 235 | public void ReplaceInListInvalidInvalidPositionTooLarge() 236 | { 237 | var doc = new SimpleDTO() 238 | { 239 | IntegerList = new List() { 1, 2, 3 } 240 | }; 241 | 242 | // create patch 243 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 244 | patchDoc.Replace("IntegerList/3", 5); 245 | 246 | var serialized = JsonConvert.SerializeObject(patchDoc); 247 | var deserialized = JsonConvert.DeserializeObject(serialized); 248 | 249 | Assert.Throws(() => 250 | { 251 | deserialized.ApplyTo(doc); 252 | }); 253 | } 254 | 255 | [Fact] 256 | public void ReplaceInListInvalidPositionTooSmall() 257 | { 258 | var doc = new SimpleDTO() 259 | { 260 | IntegerList = new List() { 1, 2, 3 } 261 | }; 262 | 263 | // create patch 264 | JsonPatchDocument patchDoc = new JsonPatchDocument(); 265 | patchDoc.Replace("IntegerList/-1", 5); 266 | 267 | var serialized = JsonConvert.SerializeObject(patchDoc); 268 | var deserialized = JsonConvert.DeserializeObject(serialized); 269 | 270 | Assert.Throws(() => 271 | { 272 | deserialized.ApplyTo(doc); 273 | }); 274 | } 275 | 276 | [Fact] 277 | public void Replace() 278 | { 279 | var doc = new SimpleDTOWithNestedDTO() 280 | { 281 | SimpleDTO = new SimpleDTO() 282 | { 283 | StringProperty = "A", 284 | DecimalValue = 10 285 | } 286 | }; 287 | 288 | // create patch 289 | JsonPatchDocument patchDoc = 290 | new JsonPatchDocument(); 291 | patchDoc.Replace(o => o.SimpleDTO.StringProperty, "B"); 292 | patchDoc.Replace(o => o.SimpleDTO.DecimalValue, 12); 293 | 294 | patchDoc.ApplyTo(doc); 295 | 296 | Assert.Equal("B", doc.SimpleDTO.StringProperty); 297 | Assert.Equal(12, doc.SimpleDTO.DecimalValue); 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/SimpleDTO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 8 | { 9 | public class SimpleDTO 10 | { 11 | public List SimpleDTOList { get; set; } 12 | public List IntegerList { get; set; } 13 | public IList IntegerGenericList { get; set; } 14 | public int IntegerValue { get; set; } 15 | public string StringProperty { get; set; } 16 | public string AnotherStringProperty { get; set; } 17 | public decimal DecimalValue { get; set; } 18 | 19 | public double DoubleValue { get; set; } 20 | 21 | public float FloatValue { get; set; } 22 | 23 | public Guid GuidValue { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/SimpleDTOWithNestedDTO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Marvin.JsonPatch.Dynamic.XUnitTest 8 | { 9 | public class SimpleDTOWithNestedDTO 10 | { 11 | public int IntegerValue { get; set; } 12 | 13 | public NestedDTO NestedDTO { get; set; } 14 | 15 | public SimpleDTO SimpleDTO { get; set; } 16 | 17 | public List ListOfSimpleDTO { get; set; } 18 | 19 | 20 | public SimpleDTOWithNestedDTO() 21 | { 22 | NestedDTO = new NestedDTO(); 23 | SimpleDTO = new SimpleDTO(); 24 | ListOfSimpleDTO = new List(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/Marvin.JsonPatch.Dynamic.XUnitTest/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | --------------------------------------------------------------------------------