Add JObjectAdapter to support JSON Patch for JObject properties (#12908)
* Add JObjectAdapter to support JSON Patch for JObject properties * Add missing import * Update ref * Ignore Rider .idea folder * Add JsonPatch.sln * Add test project to solution * Add tests for JObject support * Remove unrelated test
This commit is contained in:
parent
677fb870fd
commit
1c8ab0933e
|
|
@ -40,3 +40,4 @@ launchSettings.json
|
||||||
msbuild.ProjectImports.zip
|
msbuild.ProjectImports.zip
|
||||||
StyleCop.Cache
|
StyleCop.Cache
|
||||||
UpgradeLog.htm
|
UpgradeLog.htm
|
||||||
|
.idea
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
|
||||||
|
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}") = "Microsoft.AspNetCore.JsonPatch", "src\Microsoft.AspNetCore.JsonPatch.csproj", "{B2094419-9ED4-4733-B15D-60314118B61C}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.JsonPatch.Tests", "test\Microsoft.AspNetCore.JsonPatch.Tests.csproj", "{4F34177F-6E1E-4880-A2CA-0511EFEDB395}"
|
||||||
|
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
|
||||||
|
{B2094419-9ED4-4733-B15D-60314118B61C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B2094419-9ED4-4733-B15D-60314118B61C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B2094419-9ED4-4733-B15D-60314118B61C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{B2094419-9ED4-4733-B15D-60314118B61C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{B2094419-9ED4-4733-B15D-60314118B61C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{B2094419-9ED4-4733-B15D-60314118B61C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{B2094419-9ED4-4733-B15D-60314118B61C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B2094419-9ED4-4733-B15D-60314118B61C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B2094419-9ED4-4733-B15D-60314118B61C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{B2094419-9ED4-4733-B15D-60314118B61C}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{B2094419-9ED4-4733-B15D-60314118B61C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{B2094419-9ED4-4733-B15D-60314118B61C}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{4F34177F-6E1E-4880-A2CA-0511EFEDB395}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4F34177F-6E1E-4880-A2CA-0511EFEDB395}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4F34177F-6E1E-4880-A2CA-0511EFEDB395}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{4F34177F-6E1E-4880-A2CA-0511EFEDB395}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{4F34177F-6E1E-4880-A2CA-0511EFEDB395}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{4F34177F-6E1E-4880-A2CA-0511EFEDB395}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{4F34177F-6E1E-4880-A2CA-0511EFEDB395}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4F34177F-6E1E-4880-A2CA-0511EFEDB395}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4F34177F-6E1E-4880-A2CA-0511EFEDB395}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{4F34177F-6E1E-4880-A2CA-0511EFEDB395}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{4F34177F-6E1E-4880-A2CA-0511EFEDB395}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{4F34177F-6E1E-4880-A2CA-0511EFEDB395}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
|
|
@ -201,6 +201,16 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||||
bool TryTest(object target, string segment, Newtonsoft.Json.Serialization.IContractResolver contractResolver, object value, out string errorMessage);
|
bool TryTest(object target, string segment, Newtonsoft.Json.Serialization.IContractResolver contractResolver, object value, out string errorMessage);
|
||||||
bool TryTraverse(object target, string segment, Newtonsoft.Json.Serialization.IContractResolver contractResolver, out object nextTarget, out string errorMessage);
|
bool TryTraverse(object target, string segment, Newtonsoft.Json.Serialization.IContractResolver contractResolver, out object nextTarget, out string errorMessage);
|
||||||
}
|
}
|
||||||
|
public partial class JObjectAdapter : Microsoft.AspNetCore.JsonPatch.Internal.IAdapter
|
||||||
|
{
|
||||||
|
public JObjectAdapter() { }
|
||||||
|
public virtual bool TryAdd(object target, string segment, Newtonsoft.Json.Serialization.IContractResolver contractResolver, object value, out string errorMessage) { throw null; }
|
||||||
|
public virtual bool TryGet(object target, string segment, Newtonsoft.Json.Serialization.IContractResolver contractResolver, out object value, out string errorMessage) { throw null; }
|
||||||
|
public virtual bool TryRemove(object target, string segment, Newtonsoft.Json.Serialization.IContractResolver contractResolver, out string errorMessage) { throw null; }
|
||||||
|
public virtual bool TryReplace(object target, string segment, Newtonsoft.Json.Serialization.IContractResolver contractResolver, object value, out string errorMessage) { throw null; }
|
||||||
|
public virtual bool TryTest(object target, string segment, Newtonsoft.Json.Serialization.IContractResolver contractResolver, object value, out string errorMessage) { throw null; }
|
||||||
|
public virtual bool TryTraverse(object target, string segment, Newtonsoft.Json.Serialization.IContractResolver contractResolver, out object nextTarget, out string errorMessage) { throw null; }
|
||||||
|
}
|
||||||
public partial class ListAdapter : Microsoft.AspNetCore.JsonPatch.Internal.IAdapter
|
public partial class ListAdapter : Microsoft.AspNetCore.JsonPatch.Internal.IAdapter
|
||||||
{
|
{
|
||||||
public ListAdapter() { }
|
public ListAdapter() { }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.AspNetCore.JsonPatch.Internal;
|
using Microsoft.AspNetCore.JsonPatch.Internal;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
|
@ -29,6 +30,10 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
||||||
|
|
||||||
var jsonContract = contractResolver.ResolveContract(target.GetType());
|
var jsonContract = contractResolver.ResolveContract(target.GetType());
|
||||||
|
|
||||||
|
if (target is JObject)
|
||||||
|
{
|
||||||
|
return new JObjectAdapter();
|
||||||
|
}
|
||||||
if (target is IList)
|
if (target is IList)
|
||||||
{
|
{
|
||||||
return new ListAdapter();
|
return new ListAdapter();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
using Microsoft.AspNetCore.JsonPatch.Internal;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||||
|
{
|
||||||
|
public class JObjectAdapter : IAdapter
|
||||||
|
{
|
||||||
|
public virtual bool TryAdd(
|
||||||
|
object target,
|
||||||
|
string segment,
|
||||||
|
IContractResolver contractResolver,
|
||||||
|
object value,
|
||||||
|
out string errorMessage)
|
||||||
|
{
|
||||||
|
var obj = (JObject) target;
|
||||||
|
|
||||||
|
obj[segment] = JToken.FromObject(value);
|
||||||
|
|
||||||
|
errorMessage = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool TryGet(
|
||||||
|
object target,
|
||||||
|
string segment,
|
||||||
|
IContractResolver contractResolver,
|
||||||
|
out object value,
|
||||||
|
out string errorMessage)
|
||||||
|
{
|
||||||
|
var obj = (JObject) target;
|
||||||
|
|
||||||
|
if (!obj.ContainsKey(segment))
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = obj[segment];
|
||||||
|
errorMessage = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool TryRemove(
|
||||||
|
object target,
|
||||||
|
string segment,
|
||||||
|
IContractResolver contractResolver,
|
||||||
|
out string errorMessage)
|
||||||
|
{
|
||||||
|
var obj = (JObject) target;
|
||||||
|
|
||||||
|
if (!obj.ContainsKey(segment))
|
||||||
|
{
|
||||||
|
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Remove(segment);
|
||||||
|
errorMessage = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool TryReplace(
|
||||||
|
object target,
|
||||||
|
string segment,
|
||||||
|
IContractResolver contractResolver,
|
||||||
|
object value,
|
||||||
|
out string errorMessage)
|
||||||
|
{
|
||||||
|
var obj = (JObject) target;
|
||||||
|
|
||||||
|
if (!obj.ContainsKey(segment))
|
||||||
|
{
|
||||||
|
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj[segment] = JToken.FromObject(value);
|
||||||
|
|
||||||
|
errorMessage = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool TryTest(
|
||||||
|
object target,
|
||||||
|
string segment,
|
||||||
|
IContractResolver contractResolver,
|
||||||
|
object value,
|
||||||
|
out string errorMessage)
|
||||||
|
{
|
||||||
|
var obj = (JObject) target;
|
||||||
|
|
||||||
|
if (!obj.ContainsKey(segment))
|
||||||
|
{
|
||||||
|
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentValue = obj[segment];
|
||||||
|
|
||||||
|
if (currentValue == null || string.IsNullOrEmpty(currentValue.ToString()))
|
||||||
|
{
|
||||||
|
errorMessage = Resources.FormatValueForTargetSegmentCannotBeNullOrEmpty(segment);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(value)))
|
||||||
|
{
|
||||||
|
errorMessage = Resources.FormatValueNotEqualToTestValue(currentValue, value, segment);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMessage = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool TryTraverse(
|
||||||
|
object target,
|
||||||
|
string segment,
|
||||||
|
IContractResolver contractResolver,
|
||||||
|
out object nextTarget,
|
||||||
|
out string errorMessage)
|
||||||
|
{
|
||||||
|
var obj = (JObject) target;
|
||||||
|
|
||||||
|
if (!obj.ContainsKey(segment))
|
||||||
|
{
|
||||||
|
nextTarget = null;
|
||||||
|
errorMessage = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTarget = obj[segment];
|
||||||
|
errorMessage = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Dynamic;
|
||||||
|
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||||
|
using Microsoft.AspNetCore.JsonPatch.Operations;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.JsonPatch
|
||||||
|
{
|
||||||
|
public class JsonPatchDocumentJObjectTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ApplyTo_Array_Add()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new ObjectWithJObject{ CustomData = JObject.FromObject(new { Emails = new[] { "foo@bar.com" } })};
|
||||||
|
var patch = new JsonPatchDocument<ObjectWithJObject>();
|
||||||
|
|
||||||
|
patch.Operations.Add(new Operation<ObjectWithJObject>("add", "/CustomData/Emails/-", null, "foo@baz.com"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
patch.ApplyTo(model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("foo@baz.com", model.CustomData["Emails"][1].Value<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApplyTo_Model_Test1()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new ObjectWithJObject{ CustomData = JObject.FromObject(new { Email = "foo@bar.com", Name = "Bar" })};
|
||||||
|
var patch = new JsonPatchDocument<ObjectWithJObject>();
|
||||||
|
|
||||||
|
patch.Operations.Add(new Operation<ObjectWithJObject>("test", "/CustomData/Email", null, "foo@baz.com"));
|
||||||
|
patch.Operations.Add(new Operation<ObjectWithJObject>("add", "/CustomData/Name", null, "Bar Baz"));
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.Throws<JsonPatchException>(() => patch.ApplyTo(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApplyTo_Model_Test2()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new ObjectWithJObject{ CustomData = JObject.FromObject(new { Email = "foo@bar.com", Name = "Bar" })};
|
||||||
|
var patch = new JsonPatchDocument<ObjectWithJObject>();
|
||||||
|
|
||||||
|
patch.Operations.Add(new Operation<ObjectWithJObject>("test", "/CustomData/Email", null, "foo@bar.com"));
|
||||||
|
patch.Operations.Add(new Operation<ObjectWithJObject>("add", "/CustomData/Name", null, "Bar Baz"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
patch.ApplyTo(model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("Bar Baz", model.CustomData["Name"].Value<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApplyTo_Model_Copy()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new ObjectWithJObject{ CustomData = JObject.FromObject(new { Email = "foo@bar.com" })};
|
||||||
|
var patch = new JsonPatchDocument<ObjectWithJObject>();
|
||||||
|
|
||||||
|
patch.Operations.Add(new Operation<ObjectWithJObject>("copy", "/CustomData/UserName", "/CustomData/Email"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
patch.ApplyTo(model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("foo@bar.com", model.CustomData["UserName"].Value<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApplyTo_Model_Remove()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new ObjectWithJObject{ CustomData = JObject.FromObject(new { FirstName = "Foo", LastName = "Bar" })};
|
||||||
|
var patch = new JsonPatchDocument<ObjectWithJObject>();
|
||||||
|
|
||||||
|
patch.Operations.Add(new Operation<ObjectWithJObject>("remove", "/CustomData/LastName", null));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
patch.ApplyTo(model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(model.CustomData.ContainsKey("LastName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApplyTo_Model_Move()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new ObjectWithJObject{ CustomData = JObject.FromObject(new { FirstName = "Bar" })};
|
||||||
|
var patch = new JsonPatchDocument<ObjectWithJObject>();
|
||||||
|
|
||||||
|
patch.Operations.Add(new Operation<ObjectWithJObject>("move", "/CustomData/LastName", "/CustomData/FirstName"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
patch.ApplyTo(model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(model.CustomData.ContainsKey("FirstName"));
|
||||||
|
Assert.Equal("Bar", model.CustomData["LastName"].Value<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApplyTo_Model_Add()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new ObjectWithJObject();
|
||||||
|
var patch = new JsonPatchDocument<ObjectWithJObject>();
|
||||||
|
|
||||||
|
patch.Operations.Add(new Operation<ObjectWithJObject>("add", "/CustomData/Name", null, "Foo"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
patch.ApplyTo(model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("Foo", model.CustomData["Name"].Value<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApplyTo_Model_Replace()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new ObjectWithJObject{ CustomData = JObject.FromObject(new { Email = "foo@bar.com", Name = "Bar" })};
|
||||||
|
var patch = new JsonPatchDocument<ObjectWithJObject>();
|
||||||
|
|
||||||
|
patch.Operations.Add(new Operation<ObjectWithJObject>("replace", "/CustomData/Email", null, "foo@baz.com"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
patch.ApplyTo(model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("foo@baz.com", model.CustomData["Email"].Value<string>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.JsonPatch
|
||||||
|
{
|
||||||
|
public class ObjectWithJObject
|
||||||
|
{
|
||||||
|
public JObject CustomData { get; set; } = new JObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue