diff --git a/.azure/pipelines/fast-pr-validation.yml b/.azure/pipelines/fast-pr-validation.yml
index c88e0a520a..5449521778 100644
--- a/.azure/pipelines/fast-pr-validation.yml
+++ b/.azure/pipelines/fast-pr-validation.yml
@@ -3,28 +3,20 @@ trigger:
- release/*
jobs:
-- template: project-ci.yml
+- template: jobs/default-build.yml
parameters:
+ jobName: PR_FastCheck
+ jobDisplayName: Fast Check
+ agentOs: Windows
buildArgs: "/t:FastCheck"
-- job: RepoBuilds
- pool:
- vmImage: vs2017-win2016
- strategy:
- maxParallel: 3
- matrix:
- DataProtection:
- _FolderName: DataProtection
- WebSockets:
- _FolderName: WebSockets
- steps:
- - script: src/$(_FolderName)/build.cmd -ci
- displayName: Run src/$(_FolderName)/build.cmd
- - task: PublishTestResults@2
- displayName: Publish test results
- condition: always()
- inputs:
- testRunner: vstest
- testResultsFiles: 'src/$(_FolderName)/artifacts/logs/**/*.trx'
+- template: jobs/default-build.yml
+ parameters:
+ jobName: Windows_Build
+ jobDisplayName: "Build: Windows"
+ agentOs: Windows
+ beforeBuild:
+ - powershell: "& ./src/IISIntegration/tools/UpdateIISExpressCertificate.ps1"
+ displayName: Setup IISExpress test certificates"
- template: jobs/iisintegration-job.yml
parameters:
variables:
diff --git a/.azure/pipelines/jobs/default-build.yml b/.azure/pipelines/jobs/default-build.yml
index d39a7e1e22..065e4d1770 100644
--- a/.azure/pipelines/jobs/default-build.yml
+++ b/.azure/pipelines/jobs/default-build.yml
@@ -63,6 +63,7 @@ jobs:
- job: ${{ coalesce(parameters.jobName, parameters.agentOs) }}
displayName: ${{ coalesce(parameters.jobDisplayName, parameters.agentOs) }}
dependsOn: ${{ parameters.dependsOn }}
+ timeoutInMinutes: 90
workspace:
clean: all
strategy:
diff --git a/.gitmodules b/.gitmodules
index 76458daf89..5eaf7683cc 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -66,10 +66,6 @@
path = modules/JavaScriptServices
url = https://github.com/aspnet/JavaScriptServices.git
branch = release/2.1
-[submodule "modules/JsonPatch"]
- path = modules/JsonPatch
- url = https://github.com/aspnet/JsonPatch.git
- branch = release/2.1
[submodule "modules/KestrelHttpServer"]
path = modules/KestrelHttpServer
url = https://github.com/aspnet/KestrelHttpServer.git
diff --git a/Directory.Build.props b/Directory.Build.props
index b281c92a76..99d625f1ad 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -34,7 +34,15 @@
true
+
+ netcoreapp2.1;net461
+
+
+
+
+
+
diff --git a/Directory.Build.targets b/Directory.Build.targets
index b834d88ec0..a382d031d9 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -1,9 +1,39 @@
+
+ $(AssemblyName)
+ false
+
+
+
+
+
+
+ true
+ $(PackagesInPatch.Contains(' $(PackageId);'))
+
+
+
+
+ true
+
+ false
+
+
+
+
+ $(BaselinePackageVersion).0
+ $(BaselinePackageVersion).0
+ $(BaselinePackageVersion)
+
+
true
+
+ false
+
$(IsImplementationProject)
true
false
@@ -14,5 +44,6 @@
+
diff --git a/build/SharedFx.targets b/build/SharedFx.targets
index 84d2aee45c..2ab7352634 100644
--- a/build/SharedFx.targets
+++ b/build/SharedFx.targets
@@ -33,7 +33,7 @@
-
+
$(_MetapackageSrcRoot)$(MetapackageName)\
$(_WorkRoot)pkg\$(MetapackageName)\
diff --git a/build/buildorder.props b/build/buildorder.props
index 1cb0f6cec8..55aceb0800 100644
--- a/build/buildorder.props
+++ b/build/buildorder.props
@@ -7,7 +7,6 @@
-
@@ -30,7 +29,6 @@
-
diff --git a/build/dependencies.props b/build/dependencies.props
index dda2ffa595..e0fdd5fcc2 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -15,6 +15,7 @@
$(KoreBuildVersion)
$(KoreBuildVersion)
+ 2.1.3-rtm-15842
diff --git a/build/repo.props b/build/repo.props
index ec2fe7ab7f..a44dd76881 100644
--- a/build/repo.props
+++ b/build/repo.props
@@ -2,9 +2,8 @@
true
- false
-
- public
+ true
+ false
false
true
@@ -42,6 +41,16 @@
+
+
+
+
+
+
diff --git a/build/repo.targets b/build/repo.targets
index cde696935c..9e7e303327 100644
--- a/build/repo.targets
+++ b/build/repo.targets
@@ -16,18 +16,52 @@
SetTeamCityBuildNumberToVersion;$(PrepareDependsOn);VerifyPackageArtifactConfig;VerifyExternalDependencyConfig;PrepareOutputPaths
$(CleanDependsOn);CleanArtifacts;CleanRepoArtifacts
- $(RestoreDependsOn);InstallDotNet
- $(CompileDependsOn);BuildRepositories
+ $(RestoreDependsOn);InstallDotNet;RestoreProjects
+ $(CompileDependsOn);BuildProjects;PackProjects;BuildRepositories
$(PackageDependsOn);BuildMetapackages;CheckExpectedPackagesExist
- $(TestDependsOn);_TestRepositories
- $(GetArtifactInfoDependsOn);ResolveRepoInfo
+ $(TestDependsOn);TestProjects;_TestRepositories
+ $(GetArtifactInfoDependsOn);GetProjectArtifactInfo;ResolveRepoInfo
-
+
+
+
+
+
+
+
+ $(MSBuildThisFileDirectory)..\eng\ProjectReferences.props
+
+
+
+
+ @(_ProjectReferenceProvider->'', '%0A ')
+
+
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
MicrosoftNETCoreAppPackageVersion=$(MicrosoftNETCoreAppPackageVersion);
@@ -216,7 +250,7 @@
-
+
+
+
+ $(BuildProperties);DesignTimeBuild=true
+
+
+
+ DependsOnTargets="_SetDesignTimeBuild;ComputeGraph;VerifyPackageArtifactConfig;VerifyAllReposHaveNuGetPackageVerifier" />
diff --git a/build/submodules.props b/build/submodules.props
index 8b30d5efcb..8030fb219f 100644
--- a/build/submodules.props
+++ b/build/submodules.props
@@ -77,6 +77,5 @@
-
diff --git a/build/tasks/RepoTasks.csproj b/build/tasks/RepoTasks.csproj
index 6be1f375d2..20cbf6cce8 100644
--- a/build/tasks/RepoTasks.csproj
+++ b/build/tasks/RepoTasks.csproj
@@ -6,6 +6,7 @@
+
diff --git a/docs/preparing-patch-updates.md b/docs/PreparingPatchUpdates.md
similarity index 74%
rename from docs/preparing-patch-updates.md
rename to docs/PreparingPatchUpdates.md
index 87821935de..34813e8162 100644
--- a/docs/preparing-patch-updates.md
+++ b/docs/PreparingPatchUpdates.md
@@ -10,11 +10,15 @@ In order to prepare this repo to build a new servicing update, the following cha
+ 8
```
-* Update the package archive baselines. This is used to make sure each build
- of the package archives we give to Azure only contains new files and does
+* Update the package archive baselines. This is used to make sure each build of the package archives we give to Azure only contains new files and does
not require overwriting existing files. See [src/PackageArchive/ZipManifestGenerator/](/src/PackageArchive/ZipManifestGenerator/README.md) for instructions on how to run this tool.
-* Update the list of repositories which will contain changes in [build/submodules.props](/build/submodules.props).
+* Update the package baselines. This is used to ensure packages keep a consistent set of dependencies between releases.
+ See [eng/tools/BaselineGenerator/](/eng/tools/BaselineGenerator/README.md) for instructions on how to run this tool.
+
+* **For packages with source code in this repo (not a submodule):** Update the list of packages in [eng/PatchConfig.props](/eng/PatchConfig.props) to list which packages should be patching in this release.
+
+* **For packages still building from submodules:** Update the list of repositories which will contain changes in [build/submodules.props](/build/submodules.props).
* `` items represent repos which were released in a previous patch, and will not contain servicing updates in the next patch.
* `` items represent repos which will produce new packages in this patch.
diff --git a/eng/Baseline.props b/eng/Baseline.props
new file mode 100644
index 0000000000..acae8786c0
--- /dev/null
+++ b/eng/Baseline.props
@@ -0,0 +1,100 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ 2.1.6
+
+
+
+ 2.1.1
+
+
+
+
+ 2.1.1
+
+
+
+
+
+
+
+
+
+ 2.1.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2.1.1
+
+
+
+
+ 2.1.1
+
+
+
+
+
+
+
+
+ 2.1.1
+
+
+
+
+
+
+
+ 2.1.1
+
+
+
+
+
+
+
+ 0.4.1
+
+
+
+
+
+
+
+ 2.1.1
+
+
+
+
+
+
+
+ 2.1.1
+
+
+
+
+
+
+
+ 2.1.1
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eng/Dependencies.props b/eng/Dependencies.props
new file mode 100644
index 0000000000..917b401b0a
--- /dev/null
+++ b/eng/Dependencies.props
@@ -0,0 +1,46 @@
+
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eng/PatchConfig.props b/eng/PatchConfig.props
new file mode 100644
index 0000000000..91e93a17fe
--- /dev/null
+++ b/eng/PatchConfig.props
@@ -0,0 +1,13 @@
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+
+ Microsoft.AspNetCore;
+ Microsoft.AspNetCore.Server.IISIntegration;
+
+
+
+
diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props
new file mode 100644
index 0000000000..7381583fb8
--- /dev/null
+++ b/eng/ProjectReferences.props
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/eng/targets/CSharp.Common.props b/eng/targets/CSharp.Common.props
new file mode 100644
index 0000000000..b3fc97e2d9
--- /dev/null
+++ b/eng/targets/CSharp.Common.props
@@ -0,0 +1,14 @@
+
+
+
+ 7.2
+
+
+ SHA256
+
+
+
+
+
+
+
diff --git a/eng/targets/CSharp.Common.targets b/eng/targets/CSharp.Common.targets
new file mode 100644
index 0000000000..a7f7b610b6
--- /dev/null
+++ b/eng/targets/CSharp.Common.targets
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/eng/targets/Packaging.targets b/eng/targets/Packaging.targets
new file mode 100644
index 0000000000..69ad343564
--- /dev/null
+++ b/eng/targets/Packaging.targets
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+ $(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg
+
+
+
+
+ NuGetPackage
+ $(PackageId)
+ $(PackageVersion)
+ $(RepositoryRoot)
+ true
+
+
+
+
diff --git a/eng/targets/ResolveReferences.targets b/eng/targets/ResolveReferences.targets
new file mode 100644
index 0000000000..caf44207ee
--- /dev/null
+++ b/eng/targets/ResolveReferences.targets
@@ -0,0 +1,135 @@
+
+
+
+
+ ResolveCustomReferences;
+ $(ResolveReferencesDependsOn);
+
+
+
+
+
+ true
+ true
+ true
+ false
+
+
+ true
+ true
+ false
+
+
+
+ <_ImplicitPackageReference Include="@(PackageReference->WithMetadataValue('IsImplicitlyDefined', 'true'))" />
+ <_ExplicitPackageReference Include="@(PackageReference)" Exclude="@(_ImplicitPackageReference)" />
+ <_ExplicitPackageReference Remove="Internal.AspNetCore.Sdk" />
+
+
+
+
+ <_ProjectReferenceByAssemblyName Condition="'$(UseProjectReferences)' == 'true'"
+ Include="@(ProjectReferenceProvider)"
+ Exclude="@(UnusedProjectReferenceProvider)" />
+
+
+
+
+
+
+
+
+
+
+
+ <_LatestPackageReferenceWithVersion Include="@(Reference)" Condition=" '$(UseLatestPackageReferences)' == 'true' ">
+ %(LatestPackageReference.Identity)
+ %(LatestPackageReference.Version)
+
+ <_LatestPackageReferenceWithVersion Remove="@(_LatestPackageReferenceWithVersion)" Condition="'%(Id)' != '%(Identity)' " />
+
+
+
+
+
+
+ <_BaselinePackageReferenceWithVersion Include="@(Reference)" Condition=" '$(IsServicingBuild)' == 'true' OR '$(UseLatestPackageReferences)' != 'true' ">
+ %(BaselinePackageReference.Identity)
+ %(BaselinePackageReference.Version)
+
+
+ <_BaselinePackageReferenceWithVersion Remove="@(_BaselinePackageReferenceWithVersion)" Condition="'%(Id)' != '%(Identity)' " />
+
+
+
+
+
+
+ <_PrivatePackageReferenceWithVersion Include="@(Reference->WithMetadataValue('PrivateAssets', 'All'))">
+ %(LatestPackageReference.Identity)
+ %(LatestPackageReference.Version)
+
+
+ <_PrivatePackageReferenceWithVersion Remove="@(_PrivatePackageReferenceWithVersion)" Condition="'%(Id)' != '%(Identity)' " />
+
+
+
+
+
+
+ <_LatestPackageReferenceWithVersion Remove="@(_LatestPackageReferenceWithVersion)" />
+ <_BaselinePackageReferenceWithVersion Remove="@(_BaselinePackageReferenceWithVersion)" />
+ <_PrivatePackageReferenceWithVersion Remove="@(_PrivatePackageReferenceWithVersion)" />
+ <_ImplicitPackageReference Remove="@(_ImplicitPackageReference)" />
+
+
+
+
+ <_ExplicitPackageReference Remove="@(_ExplicitPackageReference)" />
+
+
+
+
+
+
+
+
+
+ <_TargetFramework Remove="@(_TargetFramework)" />
+ <_TargetFramework Include="$(TargetFramework)" Condition="'$(TargetFramework)' != '' "/>
+ <_TargetFramework Include="$(TargetFrameworks)" Condition="'$(TargetFramework)' == '' "/>
+
+
+
+
+
+
+
+
+
+
+ $([MSBuild]::MakeRelative($(RepositoryRoot), $(MSBuildProjectFullPath)))
+
+
+
+
diff --git a/eng/tools/BaselineGenerator/BaselineGenerator.csproj b/eng/tools/BaselineGenerator/BaselineGenerator.csproj
new file mode 100644
index 0000000000..625777dbd2
--- /dev/null
+++ b/eng/tools/BaselineGenerator/BaselineGenerator.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ netcoreapp2.1
+ -o "$(MSBuildThisFileDirectory)../../Baseline.props"
+ $(MSBuildProjectDirectory)
+
+
+
+
+
+
+
+
diff --git a/eng/tools/BaselineGenerator/Program.cs b/eng/tools/BaselineGenerator/Program.cs
new file mode 100644
index 0000000000..d3d753a5ba
--- /dev/null
+++ b/eng/tools/BaselineGenerator/Program.cs
@@ -0,0 +1,132 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using Microsoft.Extensions.CommandLineUtils;
+using NuGet.Packaging;
+using NuGet.Packaging.Core;
+
+namespace PackageBaselineGenerator
+{
+ ///
+ /// This generates Baseline.props with information about the last RTM release.
+ ///
+ class Program : CommandLineApplication
+ {
+ static void Main(string[] args)
+ {
+ new Program().Execute(args);
+ }
+
+ private readonly CommandOption _source;
+ private readonly CommandOption _output;
+
+ public Program()
+ {
+ _source = Option("-s|--source ", "The NuGet v2 source of the package to fetch", CommandOptionType.SingleValue);
+ _output = Option("-o|--output ", "The generated file output path", CommandOptionType.SingleValue);
+
+ Invoke = () => Run().GetAwaiter().GetResult();
+ }
+
+ private async Task Run()
+ {
+ var source = _source.HasValue()
+ ? _source.Value()
+ : "https://www.nuget.org/api/v2/package";
+
+ var packageCache = Environment.GetEnvironmentVariable("NUGET_PACKAGES") != null
+ ? Environment.GetEnvironmentVariable("NUGET_PACKAGES")
+ : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
+
+ var tempDir = Path.Combine(Directory.GetCurrentDirectory(), "obj", "tmp");
+ Directory.CreateDirectory(tempDir);
+
+ var input = XDocument.Load(Path.Combine(Directory.GetCurrentDirectory(), "baseline.xml"));
+
+ var output = _output.HasValue()
+ ? _output.Value()
+ : Path.Combine(Directory.GetCurrentDirectory(), "Baseline.props");
+
+ var baselineVersion = input.Root.Attribute("Version").Value;
+
+ var doc = new XDocument(
+ new XComment(" Auto generated. Do not edit manually, use eng/tools/BaselineGenerator/ to recreate. "),
+ new XElement("Project",
+ new XElement("PropertyGroup",
+ new XElement("MSBuildAllProjects", "$(MSBuildAllProjects);$(MSBuildThisFileFullPath)"),
+ new XElement("AspNetCoreBaselineVersion", baselineVersion))));
+
+ var client = new HttpClient();
+
+ foreach (var pkg in input.Root.Descendants("Package"))
+ {
+ var id = pkg.Attribute("Id").Value;
+ var version = pkg.Attribute("Version").Value;
+ var packageFileName = $"{id}.{version}.nupkg";
+ var nupkgPath = Path.Combine(packageCache, id.ToLowerInvariant(), version, packageFileName);
+ if (!File.Exists(nupkgPath))
+ {
+ nupkgPath = Path.Combine(tempDir, packageFileName);
+ }
+
+ if (!File.Exists(nupkgPath))
+ {
+ var url = $"{source}/{id}/{version}";
+ using (var file = File.Create(nupkgPath))
+ {
+ Console.WriteLine($"Downloading {url}");
+ var response = await client.GetStreamAsync(url);
+ await response.CopyToAsync(file);
+ }
+ }
+
+
+ using (var reader = new PackageArchiveReader(nupkgPath))
+ {
+ var first = true;
+ foreach (var group in reader.NuspecReader.GetDependencyGroups())
+ {
+ if (first)
+ {
+ first = false;
+ doc.Root.Add(new XComment($" Package: {id}"));
+
+ var propertyGroup = new XElement("PropertyGroup",
+ new XAttribute("Condition", $" '$(PackageId)' == '{id}' "),
+ new XElement("BaselinePackageVersion", version));
+ doc.Root.Add(propertyGroup);
+ }
+
+ var itemGroup = new XElement("ItemGroup", new XAttribute("Condition", $" '$(PackageId)' == '{id}' AND '$(TargetFramework)' == '{group.TargetFramework.GetShortFolderName()}' "));
+ doc.Root.Add(itemGroup);
+
+ foreach (var dependency in group.Packages)
+ {
+ itemGroup.Add(new XElement("BaselinePackageReference", new XAttribute("Include", dependency.Id), new XAttribute("Version", dependency.VersionRange.ToString())));
+ }
+ }
+ }
+ }
+
+ var settings = new XmlWriterSettings
+ {
+ OmitXmlDeclaration = true,
+ Encoding = Encoding.UTF8,
+ Indent = true,
+ };
+ using (var writer = XmlWriter.Create(output, settings))
+ {
+ doc.Save(writer);
+ }
+
+ return 0;
+ }
+ }
+}
diff --git a/eng/tools/BaselineGenerator/README.md b/eng/tools/BaselineGenerator/README.md
new file mode 100644
index 0000000000..1afd97d1b5
--- /dev/null
+++ b/eng/tools/BaselineGenerator/README.md
@@ -0,0 +1,10 @@
+BaselineGenerator
+=================
+
+This tool is used to generate an MSBuild file which sets the "baseline" against which servicing updates are built.
+
+## Usage
+
+1. Add to the [baseline.xml](./baseline.xml) a list of package ID's and their latest released versions. The source of this information can typically
+ be found in the build.xml file generated during ProdCon builds. See https://github.com/dotnet/versions/blob/master/build-info/dotnet/product/cli/release/2.1.6/build.xml for example.
+2. Run `dotnet run` on this project.
diff --git a/eng/tools/BaselineGenerator/baseline.xml b/eng/tools/BaselineGenerator/baseline.xml
new file mode 100644
index 0000000000..3687f918d6
--- /dev/null
+++ b/eng/tools/BaselineGenerator/baseline.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eng/tools/Directory.Build.props b/eng/tools/Directory.Build.props
new file mode 100644
index 0000000000..fda3ea0cbe
--- /dev/null
+++ b/eng/tools/Directory.Build.props
@@ -0,0 +1,3 @@
+
+
+
diff --git a/eng/tools/Directory.Build.targets b/eng/tools/Directory.Build.targets
new file mode 100644
index 0000000000..f75adf7e4d
--- /dev/null
+++ b/eng/tools/Directory.Build.targets
@@ -0,0 +1,2 @@
+
+
diff --git a/eng/tools/tools.sln b/eng/tools/tools.sln
new file mode 100644
index 0000000000..527e47cbc3
--- /dev/null
+++ b/eng/tools/tools.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaselineGenerator", "BaselineGenerator\BaselineGenerator.csproj", "{CF76A947-3A72-4824-87E6-BF029D84218B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|x64.Build.0 = Debug|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|x86.Build.0 = Debug|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Release|x64.ActiveCfg = Release|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Release|x64.Build.0 = Release|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Release|x86.ActiveCfg = Release|Any CPU
+ {CF76A947-3A72-4824-87E6-BF029D84218B}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/modules/JsonPatch b/modules/JsonPatch
deleted file mode 160000
index 218064c300..0000000000
--- a/modules/JsonPatch
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 218064c300a7cf5a76669e133340a98a0c5517a5
diff --git a/src/DataProtection/Directory.Build.props b/src/DataProtection/Directory.Build.props
index b18bd3713f..deb7bb4ee6 100644
--- a/src/DataProtection/Directory.Build.props
+++ b/src/DataProtection/Directory.Build.props
@@ -5,20 +5,4 @@
-
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Features/JsonPatch/src/Adapters/IObjectAdapter.cs b/src/Features/JsonPatch/src/Adapters/IObjectAdapter.cs
new file mode 100644
index 0000000000..e5206bfa0d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Adapters/IObjectAdapter.cs
@@ -0,0 +1,112 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Operations;
+
+namespace Microsoft.AspNetCore.JsonPatch.Adapters
+{
+ ///
+ /// Defines the operations that can be performed on a JSON patch document.
+ ///
+ public interface IObjectAdapter
+ {
+ ///
+ /// Using the "add" operation a new value is inserted into the root of the target
+ /// document, into the target array at the specified valid index, or to a target object at
+ /// the specified location.
+ ///
+ /// When adding to arrays, the specified index MUST NOT be greater than the number of elements in the array.
+ /// To append the value to the array, the index of "-" character is used (see [RFC6901]).
+ ///
+ /// When adding to an object, if an object member does not already exist, a new member is added to the object at the
+ /// specified location or if an object member does exist, that member's value is replaced.
+ ///
+ /// The operation object MUST contain a "value" member whose content
+ /// specifies the value to be added.
+ ///
+ /// For example:
+ ///
+ /// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-4
+ ///
+ /// The add operation.
+ /// Object to apply the operation to.
+ void Add(Operation operation, object objectToApplyTo);
+
+ ///
+ /// Using the "copy" operation, a value is copied from a specified location to the
+ /// target location.
+ ///
+ /// The operation object MUST contain a "from" member, which references the location in the
+ /// target document to copy the value from.
+ ///
+ /// The "from" location MUST exist for the operation to be successful.
+ ///
+ /// For example:
+ ///
+ /// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-7
+ ///
+ /// The copy operation.
+ /// Object to apply the operation to.
+ void Copy(Operation operation, object objectToApplyTo);
+
+ ///
+ /// Using the "move" operation the value at a specified location is removed and
+ /// added to the target location.
+ ///
+ /// The operation object MUST contain a "from" member, which references the location in the
+ /// target document to move the value from.
+ ///
+ /// The "from" location MUST exist for the operation to be successful.
+ ///
+ /// For example:
+ ///
+ /// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
+ ///
+ /// A location cannot be moved into one of its children.
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
+ ///
+ /// The move operation.
+ /// Object to apply the operation to.
+ void Move(Operation operation, object objectToApplyTo);
+
+ ///
+ /// Using the "remove" operation the value at the target location is removed.
+ ///
+ /// The target location MUST exist for the operation to be successful.
+ ///
+ /// For example:
+ ///
+ /// { "op": "remove", "path": "/a/b/c" }
+ ///
+ /// If removing an element from an array, any elements above the
+ /// specified index are shifted one position to the left.
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
+ ///
+ /// The remove operation.
+ /// Object to apply the operation to.
+ void Remove(Operation operation, object objectToApplyTo);
+
+ ///
+ /// Using the "replace" operation he value at the target location is replaced
+ /// with a new value. The operation object MUST contain a "value" member
+ /// which specifies the replacement value.
+ ///
+ /// The target location MUST exist for the operation to be successful.
+ ///
+ /// For example:
+ ///
+ /// { "op": "replace", "path": "/a/b/c", "value": 42 }
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
+ ///
+ /// The replace operation.
+ /// Object to apply the operation to.
+ void Replace(Operation operation, object objectToApplyTo);
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Adapters/IObjectAdapterWithTest.cs b/src/Features/JsonPatch/src/Adapters/IObjectAdapterWithTest.cs
new file mode 100644
index 0000000000..e1b4ce7950
--- /dev/null
+++ b/src/Features/JsonPatch/src/Adapters/IObjectAdapterWithTest.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Operations;
+
+namespace Microsoft.AspNetCore.JsonPatch.Adapters
+{
+ ///
+ /// Defines the operations that can be performed on a JSON patch document, including "test".
+ ///
+ public interface IObjectAdapterWithTest : IObjectAdapter
+ {
+ ///
+ /// Using the "test" operation a value at the target location is compared for
+ /// equality to a specified value.
+ ///
+ /// The operation object MUST contain a "value" member that specifies
+ /// value to be compared to the target location's value.
+ ///
+ /// The target location MUST be equal to the "value" value for the
+ /// operation to be considered successful.
+ ///
+ /// For example:
+ /// { "op": "test", "path": "/a/b/c", "value": "foo" }
+ ///
+ /// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-7
+ ///
+ /// The test operation.
+ /// Object to apply the operation to.
+ void Test(Operation operation, object objectToApplyTo);
+ }
+}
diff --git a/src/Features/JsonPatch/src/Adapters/ObjectAdapter.cs b/src/Features/JsonPatch/src/Adapters/ObjectAdapter.cs
new file mode 100644
index 0000000000..73095b52c2
--- /dev/null
+++ b/src/Features/JsonPatch/src/Adapters/ObjectAdapter.cs
@@ -0,0 +1,328 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Internal;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Adapters
+{
+ ///
+ public class ObjectAdapter : IObjectAdapterWithTest
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The .
+ /// The for logging .
+ public ObjectAdapter(
+ IContractResolver contractResolver,
+ Action logErrorAction)
+ {
+ ContractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
+ LogErrorAction = logErrorAction;
+ }
+
+ ///
+ /// Gets or sets the .
+ ///
+ public IContractResolver ContractResolver { get; }
+
+ ///
+ /// Action for logging .
+ ///
+ public Action LogErrorAction { get; }
+
+ public void Add(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ Add(operation.path, operation.value, objectToApplyTo, operation);
+ }
+
+ ///
+ /// Add is used by various operations (eg: add, copy, ...), yet through different operations;
+ /// This method allows code reuse yet reporting the correct operation on error
+ ///
+ private void Add(
+ string path,
+ object value,
+ object objectToApplyTo,
+ Operation operation)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ var parsedPath = new ParsedPath(path);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver);
+
+ var target = objectToApplyTo;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToApplyTo, path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+
+ if (!adapter.TryAdd(target, parsedPath.LastSegment, ContractResolver, value, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+ }
+
+ public void Move(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ // Get value at 'from' location and add that value to the 'path' location
+ if (TryGetValue(operation.from, objectToApplyTo, operation, out var propertyValue))
+ {
+ // remove that value
+ Remove(operation.from, objectToApplyTo, operation);
+
+ // add that value to the path location
+ Add(operation.path,
+ propertyValue,
+ objectToApplyTo,
+ operation);
+ }
+ }
+
+ public void Remove(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ Remove(operation.path, objectToApplyTo, operation);
+ }
+
+ ///
+ /// Remove is used by various operations (eg: remove, move, ...), yet through different operations;
+ /// This method allows code reuse yet reporting the correct operation on error. The return value
+ /// contains the type of the item that has been removed (and a bool possibly signifying an error)
+ /// This can be used by other methods, like replace, to ensure that we can pass in the correctly
+ /// typed value to whatever method follows.
+ ///
+ private void Remove(string path, object objectToApplyTo, Operation operationToReport)
+ {
+ var parsedPath = new ParsedPath(path);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver);
+
+ var target = objectToApplyTo;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToApplyTo, path, operationToReport, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+
+ if (!adapter.TryRemove(target, parsedPath.LastSegment, ContractResolver, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, path, operationToReport, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+ }
+
+ public void Replace(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ var parsedPath = new ParsedPath(operation.path);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver);
+
+ var target = objectToApplyTo;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToApplyTo, operation.path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+
+ if (!adapter.TryReplace(target, parsedPath.LastSegment, ContractResolver, operation.value, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+ }
+
+ public void Copy(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ // Get value at 'from' location and add that value to the 'path' location
+ if (TryGetValue(operation.from, objectToApplyTo, operation, out var propertyValue))
+ {
+ // Create deep copy
+ var copyResult = ConversionResultProvider.CopyTo(propertyValue, propertyValue.GetType());
+ if (copyResult.CanBeConverted)
+ {
+ Add(operation.path,
+ copyResult.ConvertedInstance,
+ objectToApplyTo,
+ operation);
+ }
+ else
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, Resources.FormatCannotCopyProperty(operation.from));
+ ErrorReporter(error);
+ return;
+ }
+ }
+ }
+
+ public void Test(Operation operation, object objectToApplyTo)
+ {
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ var parsedPath = new ParsedPath(operation.path);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver);
+
+ var target = objectToApplyTo;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToApplyTo, operation.path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+
+ if (!adapter.TryTest(target, parsedPath.LastSegment, ContractResolver, operation.value, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, errorMessage);
+ ErrorReporter(error);
+ return;
+ }
+ }
+
+ private bool TryGetValue(
+ string fromLocation,
+ object objectToGetValueFrom,
+ Operation operation,
+ out object propertyValue)
+ {
+ if (fromLocation == null)
+ {
+ throw new ArgumentNullException(nameof(fromLocation));
+ }
+
+ if (objectToGetValueFrom == null)
+ {
+ throw new ArgumentNullException(nameof(objectToGetValueFrom));
+ }
+
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(operation));
+ }
+
+ propertyValue = null;
+
+ var parsedPath = new ParsedPath(fromLocation);
+ var visitor = new ObjectVisitor(parsedPath, ContractResolver);
+
+ var target = objectToGetValueFrom;
+ if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
+ {
+ var error = CreatePathNotFoundError(objectToGetValueFrom, fromLocation, operation, errorMessage);
+ ErrorReporter(error);
+ return false;
+ }
+
+ if (!adapter.TryGet(target, parsedPath.LastSegment, ContractResolver, out propertyValue, out errorMessage))
+ {
+ var error = CreateOperationFailedError(objectToGetValueFrom, fromLocation, operation, errorMessage);
+ ErrorReporter(error);
+ return false;
+ }
+
+ return true;
+ }
+
+ private Action ErrorReporter
+ {
+ get
+ {
+ return LogErrorAction ?? Internal.ErrorReporter.Default;
+ }
+ }
+
+ private JsonPatchError CreateOperationFailedError(object target, string path, Operation operation, string errorMessage)
+ {
+ return new JsonPatchError(
+ target,
+ operation,
+ errorMessage ?? Resources.FormatCannotPerformOperation(operation.op, path));
+ }
+
+ private JsonPatchError CreatePathNotFoundError(object target, string path, Operation operation, string errorMessage)
+ {
+ return new JsonPatchError(
+ target,
+ operation,
+ errorMessage ?? Resources.FormatTargetLocationNotFound(operation.op, path));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Converters/JsonPatchDocumentConverter.cs b/src/Features/JsonPatch/src/Converters/JsonPatchDocumentConverter.cs
new file mode 100644
index 0000000000..aed9c48474
--- /dev/null
+++ b/src/Features/JsonPatch/src/Converters/JsonPatchDocumentConverter.cs
@@ -0,0 +1,76 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Converters
+{
+ public class JsonPatchDocumentConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ return true;
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
+ JsonSerializer serializer)
+ {
+ if (objectType != typeof(JsonPatchDocument))
+ {
+ throw new ArgumentException(Resources.FormatParameterMustMatchType("objectType", "JsonPatchDocument"), "objectType");
+ }
+
+ try
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return null;
+ }
+
+ // load jObject
+ var jObject = JArray.Load(reader);
+
+ // Create target object for Json => list of operations
+ var targetOperations = new List();
+
+ // Create a new reader for this jObject, and set all properties
+ // to match the original reader.
+ var jObjectReader = jObject.CreateReader();
+ jObjectReader.Culture = reader.Culture;
+ jObjectReader.DateParseHandling = reader.DateParseHandling;
+ jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
+ jObjectReader.FloatParseHandling = reader.FloatParseHandling;
+
+ // Populate the object properties
+ serializer.Populate(jObjectReader, targetOperations);
+
+ // container target: the JsonPatchDocument.
+ var container = new JsonPatchDocument(targetOperations, new DefaultContractResolver());
+
+ return container;
+ }
+ catch (Exception ex)
+ {
+ throw new JsonSerializationException(Resources.InvalidJsonPatchDocument, ex);
+ }
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ if (value is IJsonPatchDocument)
+ {
+ var jsonPatchDoc = (IJsonPatchDocument)value;
+ var lst = jsonPatchDoc.GetOperations();
+
+ // write out the operations, no envelope
+ serializer.Serialize(writer, lst);
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Converters/TypedJsonPatchDocumentConverter.cs b/src/Features/JsonPatch/src/Converters/TypedJsonPatchDocumentConverter.cs
new file mode 100644
index 0000000000..fd779ba4ee
--- /dev/null
+++ b/src/Features/JsonPatch/src/Converters/TypedJsonPatchDocumentConverter.cs
@@ -0,0 +1,64 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Converters
+{
+ public class TypedJsonPatchDocumentConverter : JsonPatchDocumentConverter
+ {
+ public override object ReadJson(
+ JsonReader reader,
+ Type objectType,
+ object existingValue,
+ JsonSerializer serializer)
+ {
+ try
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return null;
+ }
+
+ var genericType = objectType.GetTypeInfo().GenericTypeArguments[0];
+
+ // load jObject
+ var jObject = JArray.Load(reader);
+
+ // Create target object for Json => list of operations, typed to genericType
+ var genericOperation = typeof(Operation<>);
+ var concreteOperationType = genericOperation.MakeGenericType(genericType);
+
+ var genericList = typeof(List<>);
+ var concreteList = genericList.MakeGenericType(concreteOperationType);
+
+ var targetOperations = Activator.CreateInstance(concreteList);
+
+ //Create a new reader for this jObject, and set all properties to match the original reader.
+ var jObjectReader = jObject.CreateReader();
+ jObjectReader.Culture = reader.Culture;
+ jObjectReader.DateParseHandling = reader.DateParseHandling;
+ jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
+ jObjectReader.FloatParseHandling = reader.FloatParseHandling;
+
+ // Populate the object properties
+ serializer.Populate(jObjectReader, targetOperations);
+
+ // container target: the typed JsonPatchDocument.
+ var container = Activator.CreateInstance(objectType, targetOperations, new DefaultContractResolver());
+
+ return container;
+ }
+ catch (Exception ex)
+ {
+ throw new JsonSerializationException(Resources.InvalidJsonPatchDocument, ex);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Exceptions/JsonPatchException.cs b/src/Features/JsonPatch/src/Exceptions/JsonPatchException.cs
new file mode 100644
index 0000000000..90e080575a
--- /dev/null
+++ b/src/Features/JsonPatch/src/Exceptions/JsonPatchException.cs
@@ -0,0 +1,38 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+
+namespace Microsoft.AspNetCore.JsonPatch.Exceptions
+{
+ public class JsonPatchException : Exception
+ {
+ public Operation FailedOperation { get; private set; }
+ public object AffectedObject { get; private set; }
+
+
+ public JsonPatchException()
+ {
+
+ }
+
+ public JsonPatchException(JsonPatchError jsonPatchError, Exception innerException)
+ : base(jsonPatchError.ErrorMessage, innerException)
+ {
+ FailedOperation = jsonPatchError.Operation;
+ AffectedObject = jsonPatchError.AffectedObject;
+ }
+
+ public JsonPatchException(JsonPatchError jsonPatchError)
+ : this(jsonPatchError, null)
+ {
+ }
+
+ public JsonPatchException(string message, Exception innerException)
+ : base (message, innerException)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Helpers/GetValueResult.cs b/src/Features/JsonPatch/src/Helpers/GetValueResult.cs
new file mode 100644
index 0000000000..e2e739a027
--- /dev/null
+++ b/src/Features/JsonPatch/src/Helpers/GetValueResult.cs
@@ -0,0 +1,30 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.JsonPatch.Helpers
+{
+ ///
+ /// Return value for the helper method used by Copy/Move. Needed to ensure we can make a different
+ /// decision in the calling method when the value is null because it cannot be fetched (HasError = true)
+ /// versus when it actually is null (much like why RemovedPropertyTypeResult is used for returning
+ /// type in the Remove operation).
+ ///
+ public class GetValueResult
+ {
+ public GetValueResult(object propertyValue, bool hasError)
+ {
+ PropertyValue = propertyValue;
+ HasError = hasError;
+ }
+
+ ///
+ /// The value of the property we're trying to get
+ ///
+ public object PropertyValue { get; private set; }
+
+ ///
+ /// HasError: true when an error occurred, the operation didn't complete succesfully
+ ///
+ public bool HasError { get; private set; }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Helpers/JsonPatchProperty.cs b/src/Features/JsonPatch/src/Helpers/JsonPatchProperty.cs
new file mode 100644
index 0000000000..041b0104ac
--- /dev/null
+++ b/src/Features/JsonPatch/src/Helpers/JsonPatchProperty.cs
@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ ///
+ /// Metadata for JsonProperty.
+ ///
+ public class JsonPatchProperty
+ {
+ ///
+ /// Initializes a new instance.
+ ///
+ public JsonPatchProperty(JsonProperty property, object parent)
+ {
+ if (property == null)
+ {
+ throw new ArgumentNullException(nameof(property));
+ }
+
+ if (parent == null)
+ {
+ throw new ArgumentNullException(nameof(parent));
+ }
+
+ Property = property;
+ Parent = parent;
+ }
+
+ ///
+ /// Gets or sets JsonProperty.
+ ///
+ public JsonProperty Property { get; set; }
+
+ ///
+ /// Gets or sets Parent.
+ ///
+ public object Parent { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/IJsonPatchDocument.cs b/src/Features/JsonPatch/src/IJsonPatchDocument.cs
new file mode 100644
index 0000000000..fc5f5bd4d1
--- /dev/null
+++ b/src/Features/JsonPatch/src/IJsonPatchDocument.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using System.Collections.Generic;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ public interface IJsonPatchDocument
+ {
+ IContractResolver ContractResolver { get; set; }
+
+ IList GetOperations();
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Internal/ConversionResult.cs b/src/Features/JsonPatch/src/Internal/ConversionResult.cs
new file mode 100644
index 0000000000..77181eb18d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ConversionResult.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class ConversionResult
+ {
+ public ConversionResult(bool canBeConverted, object convertedInstance)
+ {
+ CanBeConverted = canBeConverted;
+ ConvertedInstance = convertedInstance;
+ }
+
+ public bool CanBeConverted { get; }
+ public object ConvertedInstance { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Internal/ConversionResultProvider.cs b/src/Features/JsonPatch/src/Internal/ConversionResultProvider.cs
new file mode 100644
index 0000000000..71af0d27fc
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ConversionResultProvider.cs
@@ -0,0 +1,75 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Reflection;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public static class ConversionResultProvider
+ {
+ public static ConversionResult ConvertTo(object value, Type typeToConvertTo)
+ {
+ if (value == null)
+ {
+ return new ConversionResult(IsNullableType(typeToConvertTo), null);
+ }
+ else if (typeToConvertTo.IsAssignableFrom(value.GetType()))
+ {
+ // No need to convert
+ return new ConversionResult(true, value);
+ }
+ else
+ {
+ try
+ {
+ var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), typeToConvertTo);
+ return new ConversionResult(true, deserialized);
+ }
+ catch
+ {
+ return new ConversionResult(canBeConverted: false, convertedInstance: null);
+ }
+ }
+ }
+
+ public static ConversionResult CopyTo(object value, Type typeToConvertTo)
+ {
+ var targetType = typeToConvertTo;
+ if (value == null)
+ {
+ return new ConversionResult(IsNullableType(typeToConvertTo), null);
+ }
+ else if (typeToConvertTo.IsAssignableFrom(value.GetType()))
+ {
+ // Keep original type
+ targetType = value.GetType();
+ }
+ try
+ {
+ var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), targetType);
+ return new ConversionResult(true, deserialized);
+ }
+ catch
+ {
+ return new ConversionResult(canBeConverted: false, convertedInstance: null);
+ }
+ }
+
+ private static bool IsNullableType(Type type)
+ {
+ var typeInfo = type.GetTypeInfo();
+ if (typeInfo.IsValueType)
+ {
+ // value types are only nullable if they are Nullable
+ return typeInfo.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
+ }
+ else
+ {
+ // reference types are always nullable
+ return true;
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/DictionaryAdapterOfTU.cs b/src/Features/JsonPatch/src/Internal/DictionaryAdapterOfTU.cs
new file mode 100644
index 0000000000..8a344e24ee
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/DictionaryAdapterOfTU.cs
@@ -0,0 +1,245 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class DictionaryAdapter : IAdapter
+ {
+ public bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ // As per JsonPatch spec, if a key already exists, adding should replace the existing value
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ dictionary[convertedKey] = convertedValue;
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ value = null;
+ return false;
+ }
+
+ if (!dictionary.ContainsKey(convertedKey))
+ {
+ value = null;
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ value = dictionary[convertedKey];
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ return false;
+ }
+
+ // As per JsonPatch spec, the target location must exist for remove to be successful
+ if (!dictionary.ContainsKey(convertedKey))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ dictionary.Remove(convertedKey);
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ return false;
+ }
+
+ // As per JsonPatch spec, the target location must exist for remove to be successful
+ if (!dictionary.ContainsKey(convertedKey))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ dictionary[convertedKey] = convertedValue;
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryTest(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ return false;
+ }
+
+ // As per JsonPatch spec, the target location must exist for test to be successful
+ if (!dictionary.ContainsKey(convertedKey))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ var currentValue = dictionary[convertedKey];
+
+ // The target segment does not have an assigned value to compare the test value with
+ if (currentValue == null || string.IsNullOrEmpty(currentValue.ToString()))
+ {
+ errorMessage = Resources.FormatValueForTargetSegmentCannotBeNullOrEmpty(segment);
+ return false;
+ }
+
+ if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
+ {
+ errorMessage = Resources.FormatValueNotEqualToTestValue(currentValue, value, segment);
+ return false;
+ }
+ else
+ {
+ errorMessage = null;
+ return true;
+ }
+ }
+
+ public bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object nextTarget,
+ out string errorMessage)
+ {
+ var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
+ var key = contract.DictionaryKeyResolver(segment);
+ var dictionary = (IDictionary)target;
+
+ if (!TryConvertKey(key, out var convertedKey, out errorMessage))
+ {
+ nextTarget = null;
+ return false;
+ }
+
+ if (dictionary.ContainsKey(convertedKey))
+ {
+ nextTarget = dictionary[convertedKey];
+ errorMessage = null;
+ return true;
+ }
+ else
+ {
+ nextTarget = null;
+ errorMessage = null;
+ return false;
+ }
+ }
+
+ private bool TryConvertKey(string key, out TKey convertedKey, out string errorMessage)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(key, typeof(TKey));
+ if (conversionResult.CanBeConverted)
+ {
+ errorMessage = null;
+ convertedKey = (TKey)conversionResult.ConvertedInstance;
+ return true;
+ }
+ else
+ {
+ errorMessage = Resources.FormatInvalidPathSegment(key);
+ convertedKey = default(TKey);
+ return false;
+ }
+ }
+
+ private bool TryConvertValue(object value, out TValue convertedValue, out string errorMessage)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(value, typeof(TValue));
+ if (conversionResult.CanBeConverted)
+ {
+ errorMessage = null;
+ convertedValue = (TValue)conversionResult.ConvertedInstance;
+ return true;
+ }
+ else
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ convertedValue = default(TValue);
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/DynamicObjectAdapter.cs b/src/Features/JsonPatch/src/Internal/DynamicObjectAdapter.cs
new file mode 100644
index 0000000000..fb4adeb1f2
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/DynamicObjectAdapter.cs
@@ -0,0 +1,243 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using Microsoft.CSharp.RuntimeBinder;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+using CSharpBinder = Microsoft.CSharp.RuntimeBinder;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class DynamicObjectAdapter : IAdapter
+ {
+ public bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TrySetDynamicObjectProperty(target, contractResolver, segment, value, out errorMessage))
+ {
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out value, out errorMessage))
+ {
+ value = null;
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
+ {
+ return false;
+ }
+
+ // Setting the value to "null" will use the default value in case of value types, and
+ // null in case of reference types
+ object value = null;
+ if (property.GetType().GetTypeInfo().IsValueType
+ && Nullable.GetUnderlyingType(property.GetType()) == null)
+ {
+ value = Activator.CreateInstance(property.GetType());
+ }
+
+ if (!TrySetDynamicObjectProperty(target, contractResolver, segment, value, out errorMessage))
+ {
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+
+ }
+
+ public bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, property.GetType(), out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ if (!TrySetDynamicObjectProperty(target, contractResolver, segment, convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryTest(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, property.GetType(), out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ if (!JToken.DeepEquals(JsonConvert.SerializeObject(property), JsonConvert.SerializeObject(convertedValue)))
+ {
+ errorMessage = Resources.FormatValueNotEqualToTestValue(property, value, segment);
+ return false;
+ }
+ else
+ {
+ errorMessage = null;
+ return true;
+ }
+ }
+
+ public bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object nextTarget,
+ out string errorMessage)
+ {
+ if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
+ {
+ nextTarget = null;
+ return false;
+ }
+ else
+ {
+ nextTarget = property;
+ errorMessage = null;
+ return true;
+ }
+ }
+
+ private bool TryGetDynamicObjectProperty(
+ object target,
+ IContractResolver contractResolver,
+ string segment,
+ out object value,
+ out string errorMessage)
+ {
+ var jsonDynamicContract = (JsonDynamicContract)contractResolver.ResolveContract(target.GetType());
+
+ var propertyName = jsonDynamicContract.PropertyNameResolver(segment);
+
+ var binder = CSharpBinder.Binder.GetMember(
+ CSharpBinderFlags.None,
+ propertyName,
+ target.GetType(),
+ new List
+ {
+ CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
+ });
+
+ var callsite = CallSite>.Create(binder);
+
+ try
+ {
+ value = callsite.Target(callsite, target);
+ errorMessage = null;
+ return true;
+ }
+ catch (RuntimeBinderException)
+ {
+ value = null;
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+ }
+
+ private bool TrySetDynamicObjectProperty(
+ object target,
+ IContractResolver contractResolver,
+ string segment,
+ object value,
+ out string errorMessage)
+ {
+ var jsonDynamicContract = (JsonDynamicContract)contractResolver.ResolveContract(target.GetType());
+
+ var propertyName = jsonDynamicContract.PropertyNameResolver(segment);
+
+ var binder = CSharpBinder.Binder.SetMember(
+ CSharpBinderFlags.None,
+ propertyName,
+ target.GetType(),
+ new List
+ {
+ CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
+ CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
+ });
+
+ var callsite = CallSite>.Create(binder);
+
+ try
+ {
+ callsite.Target(callsite, target, value);
+ errorMessage = null;
+ return true;
+ }
+ catch (RuntimeBinderException)
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+ }
+
+ private bool TryConvertValue(object value, Type propertyType, out object convertedValue)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(value, propertyType);
+ if (!conversionResult.CanBeConverted)
+ {
+ convertedValue = null;
+ return false;
+ }
+
+ convertedValue = conversionResult.ConvertedInstance;
+ return true;
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/ErrorReporter.cs b/src/Features/JsonPatch/src/Internal/ErrorReporter.cs
new file mode 100644
index 0000000000..76b55a6144
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ErrorReporter.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ internal static class ErrorReporter
+ {
+ public static readonly Action Default = (error) =>
+ {
+ throw new JsonPatchException(error);
+ };
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/IAdapter.cs b/src/Features/JsonPatch/src/Internal/IAdapter.cs
new file mode 100644
index 0000000000..ec28131f7d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/IAdapter.cs
@@ -0,0 +1,51 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public interface IAdapter
+ {
+ bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object nextTarget,
+ out string errorMessage);
+
+ bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage);
+
+ bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage);
+
+ bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage);
+
+ bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage);
+
+ bool TryTest(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage);
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/ListAdapter.cs b/src/Features/JsonPatch/src/Internal/ListAdapter.cs
new file mode 100644
index 0000000000..d1348fd5c6
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ListAdapter.cs
@@ -0,0 +1,349 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Microsoft.Extensions.Internal;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class ListAdapter : IAdapter
+ {
+ public bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Add, out var positionInfo, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ if (positionInfo.Type == PositionType.EndOfList)
+ {
+ list.Add(convertedValue);
+ }
+ else
+ {
+ list.Insert(positionInfo.Index, convertedValue);
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ value = null;
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Get, out var positionInfo, out errorMessage))
+ {
+ value = null;
+ return false;
+ }
+
+ if (positionInfo.Type == PositionType.EndOfList)
+ {
+ value = list[list.Count - 1];
+ }
+ else
+ {
+ value = list[positionInfo.Index];
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Remove, out var positionInfo, out errorMessage))
+ {
+ return false;
+ }
+
+ if (positionInfo.Type == PositionType.EndOfList)
+ {
+ list.RemoveAt(list.Count - 1);
+ }
+ else
+ {
+ list.RemoveAt(positionInfo.Index);
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Replace, out var positionInfo, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ if (positionInfo.Type == PositionType.EndOfList)
+ {
+ list[list.Count - 1] = convertedValue;
+ }
+ else
+ {
+ list[positionInfo.Index] = convertedValue;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryTest(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ var list = (IList)target;
+
+ if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryGetPositionInfo(list, segment, OperationType.Replace, out var positionInfo, out errorMessage))
+ {
+ return false;
+ }
+
+ if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
+ {
+ return false;
+ }
+
+ var currentValue = list[positionInfo.Index];
+ if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
+ {
+ errorMessage = Resources.FormatValueAtListPositionNotEqualToTestValue(currentValue, value, positionInfo.Index);
+ return false;
+ }
+ else
+ {
+ errorMessage = null;
+ return true;
+ }
+ }
+
+ public bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ var list = target as IList;
+ if (list == null)
+ {
+ value = null;
+ errorMessage = null;
+ return false;
+ }
+
+ var index = -1;
+ if (!int.TryParse(segment, out index))
+ {
+ value = null;
+ errorMessage = Resources.FormatInvalidIndexValue(segment);
+ return false;
+ }
+
+ if (index < 0 || index >= list.Count)
+ {
+ value = null;
+ errorMessage = Resources.FormatIndexOutOfBounds(segment);
+ return false;
+ }
+
+ value = list[index];
+ errorMessage = null;
+ return true;
+ }
+
+ private bool TryConvertValue(
+ object originalValue,
+ Type listTypeArgument,
+ string segment,
+ out object convertedValue,
+ out string errorMessage)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(originalValue, listTypeArgument);
+ if (!conversionResult.CanBeConverted)
+ {
+ convertedValue = null;
+ errorMessage = Resources.FormatInvalidValueForProperty(originalValue);
+ return false;
+ }
+
+ convertedValue = conversionResult.ConvertedInstance;
+ errorMessage = null;
+ return true;
+ }
+
+ private bool TryGetListTypeArgument(IList list, out Type listTypeArgument, out string errorMessage)
+ {
+ // Arrays are not supported as they have fixed size and operations like Add, Insert do not make sense
+ var listType = list.GetType();
+ if (listType.IsArray)
+ {
+ errorMessage = Resources.FormatPatchNotSupportedForArrays(listType.FullName);
+ listTypeArgument = null;
+ return false;
+ }
+ else
+ {
+ var genericList = ClosedGenericMatcher.ExtractGenericInterface(listType, typeof(IList<>));
+ if (genericList == null)
+ {
+ errorMessage = Resources.FormatPatchNotSupportedForNonGenericLists(listType.FullName);
+ listTypeArgument = null;
+ return false;
+ }
+ else
+ {
+ listTypeArgument = genericList.GenericTypeArguments[0];
+ errorMessage = null;
+ return true;
+ }
+ }
+ }
+
+ private bool TryGetPositionInfo(
+ IList list,
+ string segment,
+ OperationType operationType,
+ out PositionInfo positionInfo,
+ out string errorMessage)
+ {
+ if (segment == "-")
+ {
+ positionInfo = new PositionInfo(PositionType.EndOfList, -1);
+ errorMessage = null;
+ return true;
+ }
+
+ var position = -1;
+ if (int.TryParse(segment, out position))
+ {
+ if (position >= 0 && position < list.Count)
+ {
+ positionInfo = new PositionInfo(PositionType.Index, position);
+ errorMessage = null;
+ return true;
+ }
+ // As per JSON Patch spec, for Add operation the index value representing the number of elements is valid,
+ // where as for other operations like Remove, Replace, Move and Copy the target index MUST exist.
+ else if (position == list.Count && operationType == OperationType.Add)
+ {
+ positionInfo = new PositionInfo(PositionType.EndOfList, -1);
+ errorMessage = null;
+ return true;
+ }
+ else
+ {
+ positionInfo = new PositionInfo(PositionType.OutOfBounds, position);
+ errorMessage = Resources.FormatIndexOutOfBounds(segment);
+ return false;
+ }
+ }
+ else
+ {
+ positionInfo = new PositionInfo(PositionType.Invalid, -1);
+ errorMessage = Resources.FormatInvalidIndexValue(segment);
+ return false;
+ }
+ }
+
+ private struct PositionInfo
+ {
+ public PositionInfo(PositionType type, int index)
+ {
+ Type = type;
+ Index = index;
+ }
+
+ public PositionType Type { get; }
+ public int Index { get; }
+ }
+
+ private enum PositionType
+ {
+ Index, // valid index
+ EndOfList, // '-'
+ Invalid, // Ex: not an integer
+ OutOfBounds
+ }
+
+ private enum OperationType
+ {
+ Add,
+ Remove,
+ Get,
+ Replace
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/ObjectVisitor.cs b/src/Features/JsonPatch/src/Internal/ObjectVisitor.cs
new file mode 100644
index 0000000000..8994f0aa52
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ObjectVisitor.cs
@@ -0,0 +1,72 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class ObjectVisitor
+ {
+ private readonly IContractResolver _contractResolver;
+ private readonly ParsedPath _path;
+
+ public ObjectVisitor(ParsedPath path, IContractResolver contractResolver)
+ {
+ _path = path;
+ _contractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
+ }
+
+ public bool TryVisit(ref object target, out IAdapter adapter, out string errorMessage)
+ {
+ if (target == null)
+ {
+ adapter = null;
+ errorMessage = null;
+ return false;
+ }
+
+ adapter = SelectAdapter(target);
+
+ // Traverse until the penultimate segment to get the target object and adapter
+ for (var i = 0; i < _path.Segments.Count - 1; i++)
+ {
+ if (!adapter.TryTraverse(target, _path.Segments[i], _contractResolver, out var next, out errorMessage))
+ {
+ adapter = null;
+ return false;
+ }
+
+ target = next;
+ adapter = SelectAdapter(target);
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ private IAdapter SelectAdapter(object targetObject)
+ {
+ var jsonContract = _contractResolver.ResolveContract(targetObject.GetType());
+
+ if (targetObject is IList)
+ {
+ return new ListAdapter();
+ }
+ else if (jsonContract is JsonDictionaryContract jsonDictionaryContract)
+ {
+ var type = typeof(DictionaryAdapter<,>).MakeGenericType(jsonDictionaryContract.DictionaryKeyType, jsonDictionaryContract.DictionaryValueType);
+ return (IAdapter)Activator.CreateInstance(type);
+ }
+ else if (jsonContract is JsonDynamicContract)
+ {
+ return new DynamicObjectAdapter();
+ }
+ else
+ {
+ return new PocoAdapter();
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/ParsedPath.cs b/src/Features/JsonPatch/src/Internal/ParsedPath.cs
new file mode 100644
index 0000000000..8d0e69aa4d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/ParsedPath.cs
@@ -0,0 +1,92 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public struct ParsedPath
+ {
+ private static readonly string[] Empty = null;
+
+ private readonly string[] _segments;
+
+ public ParsedPath(string path)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ _segments = ParsePath(path);
+ }
+
+ public string LastSegment
+ {
+ get
+ {
+ if (_segments == null || _segments.Length == 0)
+ {
+ return null;
+ }
+
+ return _segments[_segments.Length - 1];
+ }
+ }
+
+ public IReadOnlyList Segments => _segments ?? Empty;
+
+ private static string[] ParsePath(string path)
+ {
+ var strings = new List();
+ var sb = new StringBuilder(path.Length);
+
+ for (var i = 0; i < path.Length; i++)
+ {
+ if (path[i] == '/')
+ {
+ if (sb.Length > 0)
+ {
+ strings.Add(sb.ToString());
+ sb.Length = 0;
+ }
+ }
+ else if (path[i] == '~')
+ {
+ ++i;
+ if (i >= path.Length)
+ {
+ throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
+ }
+
+ if (path[i] == '0')
+ {
+ sb.Append('~');
+ }
+ else if (path[i] == '1')
+ {
+ sb.Append('/');
+ }
+ else
+ {
+ throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
+ }
+ }
+ else
+ {
+ sb.Append(path[i]);
+ }
+ }
+
+ if (sb.Length > 0)
+ {
+ strings.Add(sb.ToString());
+ }
+
+ return strings.ToArray();
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/PathHelpers.cs b/src/Features/JsonPatch/src/Internal/PathHelpers.cs
new file mode 100644
index 0000000000..f0afedb60e
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/PathHelpers.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using System;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ internal static class PathHelpers
+ {
+ internal static string ValidateAndNormalizePath(string path)
+ {
+ // check for most common path errors on create. This is not
+ // absolutely necessary, but it allows us to already catch mistakes
+ // on creation of the patch document rather than on execute.
+
+ if (path.Contains("//"))
+ {
+ throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
+ }
+
+ if (!path.StartsWith("/", StringComparison.Ordinal))
+ {
+ return "/" + path;
+ }
+ else
+ {
+ return path;
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Internal/PocoAdapter.cs b/src/Features/JsonPatch/src/Internal/PocoAdapter.cs
new file mode 100644
index 0000000000..0eee0fc889
--- /dev/null
+++ b/src/Features/JsonPatch/src/Internal/PocoAdapter.cs
@@ -0,0 +1,236 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Reflection;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch.Internal
+{
+ public class PocoAdapter : IAdapter
+ {
+ public bool TryAdd(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!jsonProperty.Writable)
+ {
+ errorMessage = Resources.FormatCannotUpdateProperty(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, jsonProperty.PropertyType, out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ jsonProperty.ValueProvider.SetValue(target, convertedValue);
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryGet(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ value = null;
+ return false;
+ }
+
+ if (!jsonProperty.Readable)
+ {
+ errorMessage = Resources.FormatCannotReadProperty(segment);
+ value = null;
+ return false;
+ }
+
+ value = jsonProperty.ValueProvider.GetValue(target);
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryRemove(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!jsonProperty.Writable)
+ {
+ errorMessage = Resources.FormatCannotUpdateProperty(segment);
+ return false;
+ }
+
+ // Setting the value to "null" will use the default value in case of value types, and
+ // null in case of reference types
+ object value = null;
+ if (jsonProperty.PropertyType.GetTypeInfo().IsValueType
+ && Nullable.GetUnderlyingType(jsonProperty.PropertyType) == null)
+ {
+ value = Activator.CreateInstance(jsonProperty.PropertyType);
+ }
+
+ jsonProperty.ValueProvider.SetValue(target, value);
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryReplace(
+ object target,
+ string segment,
+ IContractResolver
+ contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!jsonProperty.Writable)
+ {
+ errorMessage = Resources.FormatCannotUpdateProperty(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, jsonProperty.PropertyType, out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ jsonProperty.ValueProvider.SetValue(target, convertedValue);
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryTest(
+ object target,
+ string segment,
+ IContractResolver
+ contractResolver,
+ object value,
+ out string errorMessage)
+ {
+ if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ if (!jsonProperty.Readable)
+ {
+ errorMessage = Resources.FormatCannotReadProperty(segment);
+ return false;
+ }
+
+ if (!TryConvertValue(value, jsonProperty.PropertyType, out var convertedValue))
+ {
+ errorMessage = Resources.FormatInvalidValueForProperty(value);
+ return false;
+ }
+
+ var currentValue = jsonProperty.ValueProvider.GetValue(target);
+ if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
+ {
+ errorMessage = Resources.FormatValueNotEqualToTestValue(currentValue, value, segment);
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ public bool TryTraverse(
+ object target,
+ string segment,
+ IContractResolver contractResolver,
+ out object value,
+ out string errorMessage)
+ {
+ if (target == null)
+ {
+ value = null;
+ errorMessage = null;
+ return false;
+ }
+
+ if (TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
+ {
+ value = jsonProperty.ValueProvider.GetValue(target);
+ errorMessage = null;
+ return true;
+ }
+
+ value = null;
+ errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
+ return false;
+ }
+
+ private bool TryGetJsonProperty(
+ object target,
+ IContractResolver contractResolver,
+ string segment,
+ out JsonProperty jsonProperty)
+ {
+ if (contractResolver.ResolveContract(target.GetType()) is JsonObjectContract jsonObjectContract)
+ {
+ var pocoProperty = jsonObjectContract
+ .Properties
+ .FirstOrDefault(p => string.Equals(p.PropertyName, segment, StringComparison.OrdinalIgnoreCase));
+
+ if (pocoProperty != null)
+ {
+ jsonProperty = pocoProperty;
+ return true;
+ }
+ }
+
+ jsonProperty = null;
+ return false;
+ }
+
+ private bool TryConvertValue(object value, Type propertyType, out object convertedValue)
+ {
+ var conversionResult = ConversionResultProvider.ConvertTo(value, propertyType);
+ if (!conversionResult.CanBeConverted)
+ {
+ convertedValue = null;
+ return false;
+ }
+
+ convertedValue = conversionResult.ConvertedInstance;
+ return true;
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/JsonPatchDocument.cs b/src/Features/JsonPatch/src/JsonPatchDocument.cs
new file mode 100644
index 0000000000..b6539caae8
--- /dev/null
+++ b/src/Features/JsonPatch/src/JsonPatchDocument.cs
@@ -0,0 +1,256 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.JsonPatch.Adapters;
+using Microsoft.AspNetCore.JsonPatch.Converters;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using Microsoft.AspNetCore.JsonPatch.Internal;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ // Implementation details: the purpose of this type of patch document is to allow creation of such
+ // documents for cases where there's no class/DTO to work on. Typical use case: backend not built in
+ // .NET or architecture doesn't contain a shared DTO layer.
+ [JsonConverter(typeof(JsonPatchDocumentConverter))]
+ public class JsonPatchDocument : IJsonPatchDocument
+ {
+ public List Operations { get; private set; }
+
+ [JsonIgnore]
+ public IContractResolver ContractResolver { get; set; }
+
+ public JsonPatchDocument()
+ {
+ Operations = new List();
+ ContractResolver = new DefaultContractResolver();
+ }
+
+ public JsonPatchDocument(List operations, IContractResolver contractResolver)
+ {
+ if (operations == null)
+ {
+ throw new ArgumentNullException(nameof(operations));
+ }
+
+ if (contractResolver == null)
+ {
+ throw new ArgumentNullException(nameof(contractResolver));
+ }
+
+ Operations = operations;
+ ContractResolver = contractResolver;
+ }
+
+ ///
+ /// Add operation. Will result in, for example,
+ /// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Add(string path, object value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("add", PathHelpers.ValidateAndNormalizePath(path), null, value));
+ return this;
+ }
+
+ ///
+ /// Remove value at target location. Will result in, for example,
+ /// { "op": "remove", "path": "/a/b/c" }
+ ///
+ /// target location
+ ///
+ public JsonPatchDocument Remove(string path)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("remove", PathHelpers.ValidateAndNormalizePath(path), null, null));
+ return this;
+ }
+
+ ///
+ /// Replace value. Will result in, for example,
+ /// { "op": "replace", "path": "/a/b/c", "value": 42 }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Replace(string path, object value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("replace", PathHelpers.ValidateAndNormalizePath(path), null, value));
+ return this;
+ }
+
+ ///
+ /// Test value. Will result in, for example,
+ /// { "op": "test", "path": "/a/b/c", "value": 42 }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Test(string path, object value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("test", PathHelpers.ValidateAndNormalizePath(path), null, value));
+ return this;
+ }
+
+ ///
+ /// Removes value at specified location and add it to the target location. Will result in, for example:
+ /// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Move(string from, string path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("move", PathHelpers.ValidateAndNormalizePath(path), PathHelpers.ValidateAndNormalizePath(from)));
+ return this;
+ }
+
+ ///
+ /// Copy the value at specified location to the target location. Will result in, for example:
+ /// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Copy(string from, string path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("copy", PathHelpers.ValidateAndNormalizePath(path), PathHelpers.ValidateAndNormalizePath(from)));
+ return this;
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ public void ApplyTo(object objectToApplyTo)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction: null));
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ /// Action to log errors
+ public void ApplyTo(object objectToApplyTo, Action logErrorAction)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ var adapter = new ObjectAdapter(ContractResolver, logErrorAction);
+ foreach (var op in Operations)
+ {
+ try
+ {
+ op.Apply(objectToApplyTo, adapter);
+ }
+ catch (JsonPatchException jsonPatchException)
+ {
+ var errorReporter = logErrorAction ?? ErrorReporter.Default;
+ errorReporter(new JsonPatchError(objectToApplyTo, op, jsonPatchException.Message));
+
+ // As per JSON Patch spec if an operation results in error, further operations should not be executed.
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ /// IObjectAdapter instance to use when applying
+ public void ApplyTo(object objectToApplyTo, IObjectAdapter adapter)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (adapter == null)
+ {
+ throw new ArgumentNullException(nameof(adapter));
+ }
+
+ // apply each operation in order
+ foreach (var op in Operations)
+ {
+ op.Apply(objectToApplyTo, adapter);
+ }
+ }
+
+ IList IJsonPatchDocument.GetOperations()
+ {
+ var allOps = new List();
+
+ if (Operations != null)
+ {
+ foreach (var op in Operations)
+ {
+ var untypedOp = new Operation();
+
+ untypedOp.op = op.op;
+ untypedOp.value = op.value;
+ untypedOp.path = op.path;
+ untypedOp.from = op.from;
+
+ allOps.Add(untypedOp);
+ }
+ }
+
+ return allOps;
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs b/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs
new file mode 100644
index 0000000000..8ae1430185
--- /dev/null
+++ b/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs
@@ -0,0 +1,869 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq.Expressions;
+using Microsoft.AspNetCore.JsonPatch.Adapters;
+using Microsoft.AspNetCore.JsonPatch.Converters;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using Microsoft.AspNetCore.JsonPatch.Internal;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ // Implementation details: the purpose of this type of patch document is to ensure we can do type-checking
+ // when producing a JsonPatchDocument. However, we cannot send this "typed" over the wire, as that would require
+ // including type data in the JsonPatchDocument serialized as JSON (to allow for correct deserialization) - that's
+ // not according to RFC 6902, and would thus break cross-platform compatibility.
+ [JsonConverter(typeof(TypedJsonPatchDocumentConverter))]
+ public class JsonPatchDocument : IJsonPatchDocument where TModel : class
+ {
+ public List> Operations { get; private set; }
+
+ [JsonIgnore]
+ public IContractResolver ContractResolver { get; set; }
+
+ public JsonPatchDocument()
+ {
+ Operations = new List>();
+ ContractResolver = new DefaultContractResolver();
+ }
+
+ // Create from list of operations
+ public JsonPatchDocument(List> operations, IContractResolver contractResolver)
+ {
+ Operations = operations ?? throw new ArgumentNullException(nameof(operations));
+ ContractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
+ }
+
+ ///
+ /// Add operation. Will result in, for example,
+ /// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
+ ///
+ /// value type
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Add(Expression> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "add",
+ GetPath(path, null),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Add value to list at given position
+ ///
+ /// value type
+ /// target location
+ /// value
+ /// position
+ ///
+ public JsonPatchDocument Add(
+ Expression>> path,
+ TProp value,
+ int position)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "add",
+ GetPath(path, position.ToString()),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Add value to the end of the list
+ ///
+ /// value type
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Add(Expression>> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "add",
+ GetPath(path, "-"),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Remove value at target location. Will result in, for example,
+ /// { "op": "remove", "path": "/a/b/c" }
+ ///
+ /// target location
+ ///
+ public JsonPatchDocument Remove(Expression> path)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation("remove", GetPath(path, null), from: null));
+
+ return this;
+ }
+
+ ///
+ /// Remove value from list at given position
+ ///
+ /// value type
+ /// target location
+ /// position
+ ///
+ public JsonPatchDocument Remove(Expression>> path, int position)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "remove",
+ GetPath(path, position.ToString()),
+ from: null));
+
+ return this;
+ }
+
+ ///
+ /// Remove value from end of list
+ ///
+ /// value type
+ /// target location
+ ///
+ public JsonPatchDocument Remove(Expression>> path)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "remove",
+ GetPath(path, "-"),
+ from: null));
+
+ return this;
+ }
+
+ ///
+ /// Replace value. Will result in, for example,
+ /// { "op": "replace", "path": "/a/b/c", "value": 42 }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Replace(Expression> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "replace",
+ GetPath(path, null),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Replace value in a list at given position
+ ///
+ /// value type
+ /// target location
+ /// value
+ /// position
+ ///
+ public JsonPatchDocument Replace(Expression>> path,
+ TProp value, int position)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "replace",
+ GetPath(path, position.ToString()),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Replace value at end of a list
+ ///
+ /// value type
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Replace(Expression>> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "replace",
+ GetPath(path, "-"),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Test value. Will result in, for example,
+ /// { "op": "test", "path": "/a/b/c", "value": 42 }
+ ///
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Test(Expression> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "test",
+ GetPath(path, null),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Test value in a list at given position
+ ///
+ /// value type
+ /// target location
+ /// value
+ /// position
+ ///
+ public JsonPatchDocument Test(Expression>> path,
+ TProp value, int position)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "test",
+ GetPath(path, position.ToString()),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Test value at end of a list
+ ///
+ /// value type
+ /// target location
+ /// value
+ ///
+ public JsonPatchDocument Test(Expression>> path, TProp value)
+ {
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "test",
+ GetPath(path, "-"),
+ from: null,
+ value: value));
+
+ return this;
+ }
+
+ ///
+ /// Removes value at specified location and add it to the target location. Will result in, for example:
+ /// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Move(
+ Expression> from,
+ Expression> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, null),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Move from a position in a list to a new location
+ ///
+ ///
+ /// source location
+ /// position
+ /// target location
+ ///
+ public JsonPatchDocument Move(
+ Expression>> from,
+ int positionFrom,
+ Expression> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, null),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Move from a property to a location in a list
+ ///
+ ///
+ /// source location
+ /// target location
+ /// position
+ ///
+ public JsonPatchDocument Move(
+ Expression> from,
+ Expression>> path,
+ int positionTo)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, positionTo.ToString()),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Move from a position in a list to another location in a list
+ ///
+ ///
+ /// source location
+ /// position (source)
+ /// target location
+ /// position (target)
+ ///
+ public JsonPatchDocument Move(
+ Expression>> from,
+ int positionFrom,
+ Expression>> path,
+ int positionTo)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, positionTo.ToString()),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Move from a position in a list to the end of another list
+ ///
+ ///
+ /// source location
+ /// position
+ /// target location
+ ///
+ public JsonPatchDocument Move(
+ Expression>> from,
+ int positionFrom,
+ Expression>> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, "-"),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Move to the end of a list
+ ///
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Move(
+ Expression> from,
+ Expression>> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "move",
+ GetPath(path, "-"),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Copy the value at specified location to the target location. Willr esult in, for example:
+ /// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Copy(
+ Expression> from,
+ Expression> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, null),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Copy from a position in a list to a new location
+ ///
+ ///
+ /// source location
+ /// position
+ /// target location
+ ///
+ public JsonPatchDocument Copy(
+ Expression>> from,
+ int positionFrom,
+ Expression> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, null),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Copy from a property to a location in a list
+ ///
+ ///
+ /// source location
+ /// target location
+ /// position
+ ///
+ public JsonPatchDocument Copy(
+ Expression> from,
+ Expression>> path,
+ int positionTo)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, positionTo.ToString()),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Copy from a position in a list to a new location in a list
+ ///
+ ///
+ /// source location
+ /// position (source)
+ /// target location
+ /// position (target)
+ ///
+ public JsonPatchDocument Copy(
+ Expression>> from,
+ int positionFrom,
+ Expression>> path,
+ int positionTo)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, positionTo.ToString()),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Copy from a position in a list to the end of another list
+ ///
+ ///
+ /// source location
+ /// position
+ /// target location
+ ///
+ public JsonPatchDocument Copy(
+ Expression>> from,
+ int positionFrom,
+ Expression>> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, "-"),
+ GetPath(from, positionFrom.ToString())));
+
+ return this;
+ }
+
+ ///
+ /// Copy to the end of a list
+ ///
+ ///
+ /// source location
+ /// target location
+ ///
+ public JsonPatchDocument Copy(
+ Expression> from,
+ Expression>> path)
+ {
+ if (from == null)
+ {
+ throw new ArgumentNullException(nameof(from));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ Operations.Add(new Operation(
+ "copy",
+ GetPath(path, "-"),
+ GetPath(from, null)));
+
+ return this;
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ public void ApplyTo(TModel objectToApplyTo)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction: null));
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ /// Action to log errors
+ public void ApplyTo(TModel objectToApplyTo, Action logErrorAction)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ var adapter = new ObjectAdapter(ContractResolver, logErrorAction);
+ foreach (var op in Operations)
+ {
+ try
+ {
+ op.Apply(objectToApplyTo, adapter);
+ }
+ catch (JsonPatchException jsonPatchException)
+ {
+ var errorReporter = logErrorAction ?? ErrorReporter.Default;
+ errorReporter(new JsonPatchError(objectToApplyTo, op, jsonPatchException.Message));
+
+ // As per JSON Patch spec if an operation results in error, further operations should not be executed.
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Apply this JsonPatchDocument
+ ///
+ /// Object to apply the JsonPatchDocument to
+ /// IObjectAdapter instance to use when applying
+ public void ApplyTo(TModel objectToApplyTo, IObjectAdapter adapter)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (adapter == null)
+ {
+ throw new ArgumentNullException(nameof(adapter));
+ }
+
+ // apply each operation in order
+ foreach (var op in Operations)
+ {
+ op.Apply(objectToApplyTo, adapter);
+ }
+ }
+
+ IList IJsonPatchDocument.GetOperations()
+ {
+ var allOps = new List();
+
+ if (Operations != null)
+ {
+ foreach (var op in Operations)
+ {
+ var untypedOp = new Operation
+ {
+ op = op.op,
+ value = op.value,
+ path = op.path,
+ from = op.from
+ };
+
+ allOps.Add(untypedOp);
+ }
+ }
+
+ return allOps;
+ }
+
+ // Internal for testing
+ internal string GetPath(Expression> expr, string position)
+ {
+ var segments = GetPathSegments(expr.Body);
+ var path = String.Join("/", segments);
+ if (position != null)
+ {
+ path += "/" + position;
+ if (segments.Count == 0)
+ {
+ return path;
+ }
+ }
+
+ return "/" + path;
+ }
+
+ private List GetPathSegments(Expression expr)
+ {
+ var listOfSegments = new List();
+ switch (expr.NodeType)
+ {
+ case ExpressionType.ArrayIndex:
+ var binaryExpression = (BinaryExpression)expr;
+ listOfSegments.AddRange(GetPathSegments(binaryExpression.Left));
+ listOfSegments.Add(binaryExpression.Right.ToString());
+ return listOfSegments;
+
+ case ExpressionType.Call:
+ var methodCallExpression = (MethodCallExpression)expr;
+ listOfSegments.AddRange(GetPathSegments(methodCallExpression.Object));
+ listOfSegments.Add(EvaluateExpression(methodCallExpression.Arguments[0]));
+ return listOfSegments;
+
+ case ExpressionType.Convert:
+ listOfSegments.AddRange(GetPathSegments(((UnaryExpression)expr).Operand));
+ return listOfSegments;
+
+ case ExpressionType.MemberAccess:
+ var memberExpression = expr as MemberExpression;
+ listOfSegments.AddRange(GetPathSegments(memberExpression.Expression));
+ // Get property name, respecting JsonProperty attribute
+ listOfSegments.Add(GetPropertyNameFromMemberExpression(memberExpression));
+ return listOfSegments;
+
+ case ExpressionType.Parameter:
+ // Fits "x => x" (the whole document which is "" as JSON pointer)
+ return listOfSegments;
+
+ default:
+ throw new InvalidOperationException(Resources.FormatExpressionTypeNotSupported(expr));
+ }
+ }
+
+ private string GetPropertyNameFromMemberExpression(MemberExpression memberExpression)
+ {
+ var jsonObjectContract = ContractResolver.ResolveContract(memberExpression.Expression.Type) as JsonObjectContract;
+ if (jsonObjectContract != null)
+ {
+ return jsonObjectContract.Properties
+ .First(jsonProperty => jsonProperty.UnderlyingName == memberExpression.Member.Name)
+ .PropertyName;
+ }
+
+ return null;
+ }
+
+ private static bool ContinueWithSubPath(ExpressionType expressionType)
+ {
+ return (expressionType == ExpressionType.ArrayIndex
+ || expressionType == ExpressionType.Call
+ || expressionType == ExpressionType.Convert
+ || expressionType == ExpressionType.MemberAccess);
+
+ }
+
+ // Evaluates the value of the key or index which may be an int or a string,
+ // or some other expression type.
+ // The expression is converted to a delegate and the result of executing the delegate is returned as a string.
+ private static string EvaluateExpression(Expression expression)
+ {
+ var converted = Expression.Convert(expression, typeof(object));
+ var fakeParameter = Expression.Parameter(typeof(object), null);
+ var lambda = Expression.Lambda>(converted, fakeParameter);
+ var func = lambda.Compile();
+
+ return Convert.ToString(func(null), CultureInfo.InvariantCulture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/JsonPatchError.cs b/src/Features/JsonPatch/src/JsonPatchError.cs
new file mode 100644
index 0000000000..a49af7a4e2
--- /dev/null
+++ b/src/Features/JsonPatch/src/JsonPatchError.cs
@@ -0,0 +1,50 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Operations;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ ///
+ /// Captures error message and the related entity and the operation that caused it.
+ ///
+ public class JsonPatchError
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The object that is affected by the error.
+ /// The that caused the error.
+ /// The error message.
+ public JsonPatchError(
+ object affectedObject,
+ Operation operation,
+ string errorMessage)
+ {
+ if (errorMessage == null)
+ {
+ throw new ArgumentNullException(nameof(errorMessage));
+ }
+
+ AffectedObject = affectedObject;
+ Operation = operation;
+ ErrorMessage = errorMessage;
+ }
+
+ ///
+ /// Gets the object that is affected by the error.
+ ///
+ public object AffectedObject { get; }
+
+ ///
+ /// Gets the that caused the error.
+ ///
+ public Operation Operation { get; }
+
+ ///
+ /// Gets the error message.
+ ///
+ public string ErrorMessage { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj
new file mode 100644
index 0000000000..3708e92927
--- /dev/null
+++ b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj
@@ -0,0 +1,17 @@
+
+
+
+ ASP.NET Core support for JSON PATCH.
+ netstandard2.0
+ $(NoWarn);CS1591
+ true
+ aspnetcore;json;jsonpatch
+
+
+
+
+
+
+
+
+
diff --git a/src/Features/JsonPatch/src/Operations/Operation.cs b/src/Features/JsonPatch/src/Operations/Operation.cs
new file mode 100644
index 0000000000..690ade4776
--- /dev/null
+++ b/src/Features/JsonPatch/src/Operations/Operation.cs
@@ -0,0 +1,82 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Adapters;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNetCore.JsonPatch.Operations
+{
+ public class Operation : OperationBase
+ {
+ [JsonProperty("value")]
+ public object value { get; set; }
+
+ public Operation()
+ {
+
+ }
+
+ public Operation(string op, string path, string from, object value)
+ : base(op, path, from)
+ {
+ this.value = value;
+ }
+
+ public Operation(string op, string path, string from)
+ : base(op, path, from)
+ {
+ }
+
+ public void Apply(object objectToApplyTo, IObjectAdapter adapter)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (adapter == null)
+ {
+ throw new ArgumentNullException(nameof(adapter));
+ }
+
+ switch (OperationType)
+ {
+ case OperationType.Add:
+ adapter.Add(this, objectToApplyTo);
+ break;
+ case OperationType.Remove:
+ adapter.Remove(this, objectToApplyTo);
+ break;
+ case OperationType.Replace:
+ adapter.Replace(this, objectToApplyTo);
+ break;
+ case OperationType.Move:
+ adapter.Move(this, objectToApplyTo);
+ break;
+ case OperationType.Copy:
+ adapter.Copy(this, objectToApplyTo);
+ break;
+ case OperationType.Test:
+ if (adapter is IObjectAdapterWithTest adapterWithTest)
+ {
+ adapterWithTest.Test(this, objectToApplyTo);
+ break;
+ }
+ else
+ {
+ throw new NotSupportedException(Resources.TestOperationNotSupported);
+ }
+ default:
+ break;
+ }
+ }
+
+ public bool ShouldSerializevalue()
+ {
+ return (OperationType == OperationType.Add
+ || OperationType == OperationType.Replace
+ || OperationType == OperationType.Test);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Operations/OperationBase.cs b/src/Features/JsonPatch/src/Operations/OperationBase.cs
new file mode 100644
index 0000000000..e629e2308d
--- /dev/null
+++ b/src/Features/JsonPatch/src/Operations/OperationBase.cs
@@ -0,0 +1,76 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNetCore.JsonPatch.Operations
+{
+ public class OperationBase
+ {
+ private string _op;
+ private OperationType _operationType;
+
+ [JsonIgnore]
+ public OperationType OperationType
+ {
+ get
+ {
+ return _operationType;
+ }
+ }
+
+ [JsonProperty("path")]
+ public string path { get; set; }
+
+ [JsonProperty("op")]
+ public string op
+ {
+ get
+ {
+ return _op;
+ }
+ set
+ {
+ OperationType result;
+ if (!Enum.TryParse(value, ignoreCase: true, result: out result))
+ {
+ result = OperationType.Invalid;
+ }
+ _operationType = result;
+ _op = value;
+ }
+ }
+
+ [JsonProperty("from")]
+ public string from { get; set; }
+
+ public OperationBase()
+ {
+
+ }
+
+ public OperationBase(string op, string path, string from)
+ {
+ if (op == null)
+ {
+ throw new ArgumentNullException(nameof(op));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ this.op = op;
+ this.path = path;
+ this.from = from;
+ }
+
+ public bool ShouldSerializefrom()
+ {
+ return (OperationType == OperationType.Move
+ || OperationType == OperationType.Copy);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Operations/OperationOfT.cs b/src/Features/JsonPatch/src/Operations/OperationOfT.cs
new file mode 100644
index 0000000000..bd13528775
--- /dev/null
+++ b/src/Features/JsonPatch/src/Operations/OperationOfT.cs
@@ -0,0 +1,94 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.JsonPatch.Adapters;
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+
+namespace Microsoft.AspNetCore.JsonPatch.Operations
+{
+ public class Operation : Operation where TModel : class
+ {
+ public Operation()
+ {
+
+ }
+
+ public Operation(string op, string path, string from, object value)
+ : base(op, path, from)
+ {
+ if (op == null)
+ {
+ throw new ArgumentNullException(nameof(op));
+ }
+
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ this.value = value;
+ }
+
+ public Operation(string op, string path, string from)
+ : base(op, path, from)
+ {
+ if (op == null)
+ {
+ throw new ArgumentNullException(nameof(op));
+ }
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ }
+
+ public void Apply(TModel objectToApplyTo, IObjectAdapter adapter)
+ {
+ if (objectToApplyTo == null)
+ {
+ throw new ArgumentNullException(nameof(objectToApplyTo));
+ }
+
+ if (adapter == null)
+ {
+ throw new ArgumentNullException(nameof(adapter));
+ }
+
+ switch (OperationType)
+ {
+ case OperationType.Add:
+ adapter.Add(this, objectToApplyTo);
+ break;
+ case OperationType.Remove:
+ adapter.Remove(this, objectToApplyTo);
+ break;
+ case OperationType.Replace:
+ adapter.Replace(this, objectToApplyTo);
+ break;
+ case OperationType.Move:
+ adapter.Move(this, objectToApplyTo);
+ break;
+ case OperationType.Copy:
+ adapter.Copy(this, objectToApplyTo);
+ break;
+ case OperationType.Test:
+ if (adapter is IObjectAdapterWithTest adapterWithTest)
+ {
+ adapterWithTest.Test(this, objectToApplyTo);
+ break;
+ }
+ else
+ {
+ throw new JsonPatchException(new JsonPatchError(objectToApplyTo, this, Resources.TestOperationNotSupported));
+ }
+ case OperationType.Invalid:
+ throw new JsonPatchException(
+ Resources.FormatInvalidJsonPatchOperation(op), innerException: null);
+ default:
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Operations/OperationType.cs b/src/Features/JsonPatch/src/Operations/OperationType.cs
new file mode 100644
index 0000000000..725646df3a
--- /dev/null
+++ b/src/Features/JsonPatch/src/Operations/OperationType.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.JsonPatch.Operations
+{
+ public enum OperationType
+ {
+ Add,
+ Remove,
+ Replace,
+ Move,
+ Copy,
+ Test,
+ Invalid
+ }
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/Properties/AssemblyInfo.cs b/src/Features/JsonPatch/src/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..11fa956b64
--- /dev/null
+++ b/src/Features/JsonPatch/src/Properties/AssemblyInfo.cs
@@ -0,0 +1,7 @@
+
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.JsonPatch.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Features/JsonPatch/src/Properties/Resources.Designer.cs b/src/Features/JsonPatch/src/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..c314465238
--- /dev/null
+++ b/src/Features/JsonPatch/src/Properties/Resources.Designer.cs
@@ -0,0 +1,338 @@
+//
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.JsonPatch.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// The property at '{0}' could not be copied.
+ ///
+ internal static string CannotCopyProperty
+ {
+ get => GetString("CannotCopyProperty");
+ }
+
+ ///
+ /// The property at '{0}' could not be copied.
+ ///
+ internal static string FormatCannotCopyProperty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotCopyProperty"), p0);
+
+ ///
+ /// The type of the property at path '{0}' could not be determined.
+ ///
+ internal static string CannotDeterminePropertyType
+ {
+ get => GetString("CannotDeterminePropertyType");
+ }
+
+ ///
+ /// The type of the property at path '{0}' could not be determined.
+ ///
+ internal static string FormatCannotDeterminePropertyType(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotDeterminePropertyType"), p0);
+
+ ///
+ /// The '{0}' operation at path '{1}' could not be performed.
+ ///
+ internal static string CannotPerformOperation
+ {
+ get => GetString("CannotPerformOperation");
+ }
+
+ ///
+ /// The '{0}' operation at path '{1}' could not be performed.
+ ///
+ internal static string FormatCannotPerformOperation(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotPerformOperation"), p0, p1);
+
+ ///
+ /// The property at '{0}' could not be read.
+ ///
+ internal static string CannotReadProperty
+ {
+ get => GetString("CannotReadProperty");
+ }
+
+ ///
+ /// The property at '{0}' could not be read.
+ ///
+ internal static string FormatCannotReadProperty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotReadProperty"), p0);
+
+ ///
+ /// The property at path '{0}' could not be updated.
+ ///
+ internal static string CannotUpdateProperty
+ {
+ get => GetString("CannotUpdateProperty");
+ }
+
+ ///
+ /// The property at path '{0}' could not be updated.
+ ///
+ internal static string FormatCannotUpdateProperty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("CannotUpdateProperty"), p0);
+
+ ///
+ /// The expression '{0}' is not supported. Supported expressions include member access and indexer expressions.
+ ///
+ internal static string ExpressionTypeNotSupported
+ {
+ get => GetString("ExpressionTypeNotSupported");
+ }
+
+ ///
+ /// The expression '{0}' is not supported. Supported expressions include member access and indexer expressions.
+ ///
+ internal static string FormatExpressionTypeNotSupported(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ExpressionTypeNotSupported"), p0);
+
+ ///
+ /// The index value provided by path segment '{0}' is out of bounds of the array size.
+ ///
+ internal static string IndexOutOfBounds
+ {
+ get => GetString("IndexOutOfBounds");
+ }
+
+ ///
+ /// The index value provided by path segment '{0}' is out of bounds of the array size.
+ ///
+ internal static string FormatIndexOutOfBounds(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("IndexOutOfBounds"), p0);
+
+ ///
+ /// The path segment '{0}' is invalid for an array index.
+ ///
+ internal static string InvalidIndexValue
+ {
+ get => GetString("InvalidIndexValue");
+ }
+
+ ///
+ /// The path segment '{0}' is invalid for an array index.
+ ///
+ internal static string FormatInvalidIndexValue(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidIndexValue"), p0);
+
+ ///
+ /// The JSON patch document was malformed and could not be parsed.
+ ///
+ internal static string InvalidJsonPatchDocument
+ {
+ get => GetString("InvalidJsonPatchDocument");
+ }
+
+ ///
+ /// The JSON patch document was malformed and could not be parsed.
+ ///
+ internal static string FormatInvalidJsonPatchDocument()
+ => GetString("InvalidJsonPatchDocument");
+
+ ///
+ /// Invalid JsonPatch operation '{0}'.
+ ///
+ internal static string InvalidJsonPatchOperation
+ {
+ get => GetString("InvalidJsonPatchOperation");
+ }
+
+ ///
+ /// Invalid JsonPatch operation '{0}'.
+ ///
+ internal static string FormatInvalidJsonPatchOperation(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidJsonPatchOperation"), p0);
+
+ ///
+ /// The provided path segment '{0}' cannot be converted to the target type.
+ ///
+ internal static string InvalidPathSegment
+ {
+ get => GetString("InvalidPathSegment");
+ }
+
+ ///
+ /// The provided path segment '{0}' cannot be converted to the target type.
+ ///
+ internal static string FormatInvalidPathSegment(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidPathSegment"), p0);
+
+ ///
+ /// The provided string '{0}' is an invalid path.
+ ///
+ internal static string InvalidValueForPath
+ {
+ get => GetString("InvalidValueForPath");
+ }
+
+ ///
+ /// The provided string '{0}' is an invalid path.
+ ///
+ internal static string FormatInvalidValueForPath(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidValueForPath"), p0);
+
+ ///
+ /// The value '{0}' is invalid for target location.
+ ///
+ internal static string InvalidValueForProperty
+ {
+ get => GetString("InvalidValueForProperty");
+ }
+
+ ///
+ /// The value '{0}' is invalid for target location.
+ ///
+ internal static string FormatInvalidValueForProperty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidValueForProperty"), p0);
+
+ ///
+ /// '{0}' must be of type '{1}'.
+ ///
+ internal static string ParameterMustMatchType
+ {
+ get => GetString("ParameterMustMatchType");
+ }
+
+ ///
+ /// '{0}' must be of type '{1}'.
+ ///
+ internal static string FormatParameterMustMatchType(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ParameterMustMatchType"), p0, p1);
+
+ ///
+ /// The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.
+ ///
+ internal static string PatchNotSupportedForArrays
+ {
+ get => GetString("PatchNotSupportedForArrays");
+ }
+
+ ///
+ /// The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.
+ ///
+ internal static string FormatPatchNotSupportedForArrays(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("PatchNotSupportedForArrays"), p0);
+
+ ///
+ /// The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.
+ ///
+ internal static string PatchNotSupportedForNonGenericLists
+ {
+ get => GetString("PatchNotSupportedForNonGenericLists");
+ }
+
+ ///
+ /// The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.
+ ///
+ internal static string FormatPatchNotSupportedForNonGenericLists(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("PatchNotSupportedForNonGenericLists"), p0);
+
+ ///
+ /// The target location specified by path segment '{0}' was not found.
+ ///
+ internal static string TargetLocationAtPathSegmentNotFound
+ {
+ get => GetString("TargetLocationAtPathSegmentNotFound");
+ }
+
+ ///
+ /// The target location specified by path segment '{0}' was not found.
+ ///
+ internal static string FormatTargetLocationAtPathSegmentNotFound(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("TargetLocationAtPathSegmentNotFound"), p0);
+
+ ///
+ /// For operation '{0}', the target location specified by path '{1}' was not found.
+ ///
+ internal static string TargetLocationNotFound
+ {
+ get => GetString("TargetLocationNotFound");
+ }
+
+ ///
+ /// For operation '{0}', the target location specified by path '{1}' was not found.
+ ///
+ internal static string FormatTargetLocationNotFound(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("TargetLocationNotFound"), p0, p1);
+
+ ///
+ /// The test operation is not supported.
+ ///
+ internal static string TestOperationNotSupported
+ {
+ get => GetString("TestOperationNotSupported");
+ }
+
+ ///
+ /// The test operation is not supported.
+ ///
+ internal static string FormatTestOperationNotSupported()
+ => GetString("TestOperationNotSupported");
+
+ ///
+ /// The current value '{0}' at position '{2}' is not equal to the test value '{1}'.
+ ///
+ internal static string ValueAtListPositionNotEqualToTestValue
+ {
+ get => GetString("ValueAtListPositionNotEqualToTestValue");
+ }
+
+ ///
+ /// The current value '{0}' at position '{2}' is not equal to the test value '{1}'.
+ ///
+ internal static string FormatValueAtListPositionNotEqualToTestValue(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ValueAtListPositionNotEqualToTestValue"), p0, p1, p2);
+
+ ///
+ /// The value at '{0}' cannot be null or empty to perform the test operation.
+ ///
+ internal static string ValueForTargetSegmentCannotBeNullOrEmpty
+ {
+ get => GetString("ValueForTargetSegmentCannotBeNullOrEmpty");
+ }
+
+ ///
+ /// The value at '{0}' cannot be null or empty to perform the test operation.
+ ///
+ internal static string FormatValueForTargetSegmentCannotBeNullOrEmpty(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ValueForTargetSegmentCannotBeNullOrEmpty"), p0);
+
+ ///
+ /// The current value '{0}' at path '{2}' is not equal to the test value '{1}'.
+ ///
+ internal static string ValueNotEqualToTestValue
+ {
+ get => GetString("ValueNotEqualToTestValue");
+ }
+
+ ///
+ /// The current value '{0}' at path '{2}' is not equal to the test value '{1}'.
+ ///
+ internal static string FormatValueNotEqualToTestValue(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ValueNotEqualToTestValue"), p0, p1, p2);
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/src/Resources.resx b/src/Features/JsonPatch/src/Resources.resx
new file mode 100644
index 0000000000..87cc399c62
--- /dev/null
+++ b/src/Features/JsonPatch/src/Resources.resx
@@ -0,0 +1,186 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The property at '{0}' could not be copied.
+
+
+ The type of the property at path '{0}' could not be determined.
+
+
+ The '{0}' operation at path '{1}' could not be performed.
+
+
+ The property at '{0}' could not be read.
+
+
+ The property at path '{0}' could not be updated.
+
+
+ The expression '{0}' is not supported. Supported expressions include member access and indexer expressions.
+
+
+ The index value provided by path segment '{0}' is out of bounds of the array size.
+
+
+ The path segment '{0}' is invalid for an array index.
+
+
+ The JSON patch document was malformed and could not be parsed.
+
+
+ Invalid JsonPatch operation '{0}'.
+
+
+ The provided path segment '{0}' cannot be converted to the target type.
+
+
+ The provided string '{0}' is an invalid path.
+
+
+ The value '{0}' is invalid for target location.
+
+
+ '{0}' must be of type '{1}'.
+
+
+ The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.
+
+
+ The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.
+
+
+ The target location specified by path segment '{0}' was not found.
+
+
+ For operation '{0}', the target location specified by path '{1}' was not found.
+
+
+ The test operation is not supported.
+
+
+ The current value '{0}' at position '{2}' is not equal to the test value '{1}'.
+
+
+ The value at '{0}' cannot be null or empty to perform the test operation.
+
+
+ The current value '{0}' at path '{2}' is not equal to the test value '{1}'.
+
+
\ No newline at end of file
diff --git a/src/Features/JsonPatch/src/baseline.netcore.json b/src/Features/JsonPatch/src/baseline.netcore.json
new file mode 100644
index 0000000000..3d90a8f017
--- /dev/null
+++ b/src/Features/JsonPatch/src/baseline.netcore.json
@@ -0,0 +1,1985 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.JsonPatch, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.JsonPatchProperty",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Property",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.JsonProperty",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Property",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Newtonsoft.Json.Serialization.JsonProperty"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Parent",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Parent",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "property",
+ "Type": "Newtonsoft.Json.Serialization.JsonProperty"
+ },
+ {
+ "Name": "parent",
+ "Type": "System.Object"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_ContractResolver",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.IContractResolver",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_ContractResolver",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetOperations",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IList",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Operations",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.List",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ContractResolver",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.IContractResolver",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_ContractResolver",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "logErrorAction",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "adapter",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "operations",
+ "Type": "System.Collections.Generic.List"
+ },
+ {
+ "Name": "contractResolver",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Operations",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.List>",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ContractResolver",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.IContractResolver",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_ContractResolver",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.IJsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ },
+ {
+ "Name": "position",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "position",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ },
+ {
+ "Name": "position",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ },
+ {
+ "Name": "position",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "value",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionTo",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionTo",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionTo",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionTo",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>>"
+ },
+ {
+ "Name": "positionFrom",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "from",
+ "Type": "System.Linq.Expressions.Expression>"
+ },
+ {
+ "Name": "path",
+ "Type": "System.Linq.Expressions.Expression>>"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TProp",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "T0"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "T0"
+ },
+ {
+ "Name": "logErrorAction",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ApplyTo",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "T0"
+ },
+ {
+ "Name": "adapter",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "operations",
+ "Type": "System.Collections.Generic.List>"
+ },
+ {
+ "Name": "contractResolver",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": [
+ {
+ "ParameterName": "TModel",
+ "ParameterPosition": 0,
+ "Class": true,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.JsonPatchError",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_AffectedObject",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Operation",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ErrorMessage",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "affectedObject",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "errorMessage",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.JsonPatch.Operations.OperationBase",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_value",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_value",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Apply",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "adapter",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ShouldSerializevalue",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Operations.OperationBase",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_OperationType",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.Operations.OperationType",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_path",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_path",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_op",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_op",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_from",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_from",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ShouldSerializefrom",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Apply",
+ "Parameters": [
+ {
+ "Name": "objectToApplyTo",
+ "Type": "T0"
+ },
+ {
+ "Name": "adapter",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "op",
+ "Type": "System.String"
+ },
+ {
+ "Name": "path",
+ "Type": "System.String"
+ },
+ {
+ "Name": "from",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": [
+ {
+ "ParameterName": "TModel",
+ "ParameterPosition": 0,
+ "Class": true,
+ "BaseTypeOrInterfaces": []
+ }
+ ]
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Operations.OperationType",
+ "Visibility": "Public",
+ "Kind": "Enumeration",
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Field",
+ "Name": "Add",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "0"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Remove",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "1"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Replace",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "2"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Move",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "3"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Copy",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "4"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Test",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "5"
+ },
+ {
+ "Kind": "Field",
+ "Name": "Invalid",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "6"
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Helpers.GetValueResult",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_PropertyValue",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_HasError",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "propertyValue",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "hasError",
+ "Type": "System.Boolean"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Exceptions.JsonPatchException",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "System.Exception",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_FailedOperation",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.JsonPatch.Operations.Operation",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_AffectedObject",
+ "Parameters": [],
+ "ReturnType": "System.Object",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "jsonPatchError",
+ "Type": "Microsoft.AspNetCore.JsonPatch.JsonPatchError"
+ },
+ {
+ "Name": "innerException",
+ "Type": "System.Exception"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "jsonPatchError",
+ "Type": "Microsoft.AspNetCore.JsonPatch.JsonPatchError"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "message",
+ "Type": "System.String"
+ },
+ {
+ "Name": "innerException",
+ "Type": "System.Exception"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Converters.JsonPatchDocumentConverter",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Newtonsoft.Json.JsonConverter",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "CanConvert",
+ "Parameters": [
+ {
+ "Name": "objectType",
+ "Type": "System.Type"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ReadJson",
+ "Parameters": [
+ {
+ "Name": "reader",
+ "Type": "Newtonsoft.Json.JsonReader"
+ },
+ {
+ "Name": "objectType",
+ "Type": "System.Type"
+ },
+ {
+ "Name": "existingValue",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "serializer",
+ "Type": "Newtonsoft.Json.JsonSerializer"
+ }
+ ],
+ "ReturnType": "System.Object",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "WriteJson",
+ "Parameters": [
+ {
+ "Name": "writer",
+ "Type": "Newtonsoft.Json.JsonWriter"
+ },
+ {
+ "Name": "value",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "serializer",
+ "Type": "Newtonsoft.Json.JsonSerializer"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Converters.TypedJsonPatchDocumentConverter",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.JsonPatch.Converters.JsonPatchDocumentConverter",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "ReadJson",
+ "Parameters": [
+ {
+ "Name": "reader",
+ "Type": "Newtonsoft.Json.JsonReader"
+ },
+ {
+ "Name": "objectType",
+ "Type": "System.Type"
+ },
+ {
+ "Name": "existingValue",
+ "Type": "System.Object"
+ },
+ {
+ "Name": "serializer",
+ "Type": "Newtonsoft.Json.JsonSerializer"
+ }
+ ],
+ "ReturnType": "System.Object",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapterWithTest",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.JsonPatch.Adapters.ObjectAdapter",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapterWithTest"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Add",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Copy",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Move",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Remove",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Replace",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapter",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ContractResolver",
+ "Parameters": [],
+ "ReturnType": "Newtonsoft.Json.Serialization.IContractResolver",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_LogErrorAction",
+ "Parameters": [],
+ "ReturnType": "System.Action",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Test",
+ "Parameters": [
+ {
+ "Name": "operation",
+ "Type": "Microsoft.AspNetCore.JsonPatch.Operations.Operation"
+ },
+ {
+ "Name": "objectToApplyTo",
+ "Type": "System.Object"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.JsonPatch.Adapters.IObjectAdapterWithTest",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "contractResolver",
+ "Type": "Newtonsoft.Json.Serialization.IContractResolver"
+ },
+ {
+ "Name": "logErrorAction",
+ "Type": "System.Action"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Features/JsonPatch/test/CustomNamingStrategyTests.cs b/src/Features/JsonPatch/test/CustomNamingStrategyTests.cs
new file mode 100644
index 0000000000..ebc45874d9
--- /dev/null
+++ b/src/Features/JsonPatch/test/CustomNamingStrategyTests.cs
@@ -0,0 +1,153 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Dynamic;
+using Newtonsoft.Json.Serialization;
+using Xunit;
+
+namespace Microsoft.AspNetCore.JsonPatch
+{
+ public class CustomNamingStrategyTests
+ {
+ [Fact]
+ public void AddProperty_ToDynamicTestObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ dynamic targetObject = new DynamicTestObject();
+ targetObject.Test = 1;
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Add("NewInt", 1);
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+
+ // Assert
+ Assert.Equal(1, targetObject.customNewInt);
+ Assert.Equal(1, targetObject.Test);
+ }
+
+ [Fact]
+ public void CopyPropertyValue_ToDynamicTestObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ dynamic targetObject = new DynamicTestObject();
+ targetObject.customStringProperty = "A";
+ targetObject.customAnotherStringProperty = "B";
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Copy("StringProperty", "AnotherStringProperty");
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+
+ // Assert
+ Assert.Equal("A", targetObject.customAnotherStringProperty);
+ }
+
+ [Fact]
+ public void MovePropertyValue_ForExpandoObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ dynamic targetObject = new ExpandoObject();
+ targetObject.customStringProperty = "A";
+ targetObject.customAnotherStringProperty = "B";
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Move("StringProperty", "AnotherStringProperty");
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+ var cont = targetObject as IDictionary;
+ cont.TryGetValue("customStringProperty", out var valueFromDictionary);
+
+ // Assert
+ Assert.Equal("A", targetObject.customAnotherStringProperty);
+ Assert.Null(valueFromDictionary);
+ }
+
+ [Fact]
+ public void RemoveProperty_FromDictionaryObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ var targetObject = new Dictionary()
+ {
+ { "customTest", 1},
+ };
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Remove("Test");
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+ var cont = targetObject as IDictionary;
+ cont.TryGetValue("customTest", out var valueFromDictionary);
+
+ // Assert
+ Assert.Equal(0, valueFromDictionary);
+ }
+
+ [Fact]
+ public void ReplacePropertyValue_ForExpandoObject_WithCustomNamingStrategy()
+ {
+ // Arrange
+ var contractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new TestNamingStrategy()
+ };
+
+ dynamic targetObject = new ExpandoObject();
+ targetObject.customTest = 1;
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Replace("Test", 2);
+ patchDocument.ContractResolver = contractResolver;
+
+ // Act
+ patchDocument.ApplyTo(targetObject);
+
+ // Assert
+ Assert.Equal(2, targetObject.customTest);
+ }
+
+ private class TestNamingStrategy : NamingStrategy
+ {
+ public new bool ProcessDictionaryKeys => true;
+
+ public override string GetDictionaryKey(string key)
+ {
+ return "custom" + key;
+ }
+
+ protected override string ResolvePropertyName(string name)
+ {
+ return name;
+ }
+ }
+ }
+}
diff --git a/src/Features/JsonPatch/test/IntegrationTests/AnonymousObjectIntegrationTest.cs b/src/Features/JsonPatch/test/IntegrationTests/AnonymousObjectIntegrationTest.cs
new file mode 100644
index 0000000000..4f290aae2f
--- /dev/null
+++ b/src/Features/JsonPatch/test/IntegrationTests/AnonymousObjectIntegrationTest.cs
@@ -0,0 +1,190 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.JsonPatch.Exceptions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests
+{
+ public class AnonymousObjectIntegrationTest
+ {
+ [Fact]
+ public void AddNewProperty_ShouldFail()
+ {
+ // Arrange
+ var targetObject = new { };
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Add("NewProperty", 4);
+
+ // Act
+ var exception = Assert.Throws(() =>
+ {
+ patchDocument.ApplyTo(targetObject);
+ });
+
+ // Assert
+ Assert.Equal("The target location specified by path segment 'NewProperty' was not found.",
+ exception.Message);
+ }
+
+ [Fact]
+ public void AddNewProperty_ToNestedAnonymousObject_ShouldFail()
+ {
+ // Arrange
+ dynamic targetObject = new
+ {
+ Test = 1,
+ nested = new { }
+ };
+
+ var patchDocument = new JsonPatchDocument();
+ patchDocument.Add("Nested/NewInt", 1);
+
+ // Act
+ var exception = Assert.Throws