Skip to content

Commit dec7382

Browse files
authored
Adding support for custom JsonConverter(s) when deserializing JSON Patch value object (#30974)
* - Adding a new ConvertTo overload to ConversionResultProvider that takes an IContractResolver and hooking it up for ListAdapter - Unit test to verify above works for heterogenous collections
2 parents a104f9d + 5cf6359 commit dec7382

File tree

6 files changed

+214
-49
lines changed

6 files changed

+214
-49
lines changed

src/Features/JsonPatch/src/Internal/ConversionResultProvider.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Reflection;
66
using Newtonsoft.Json;
7+
using Newtonsoft.Json.Serialization;
78

89
namespace Microsoft.AspNetCore.JsonPatch.Internal
910
{
@@ -14,6 +15,11 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
1415
public static class ConversionResultProvider
1516
{
1617
public static ConversionResult ConvertTo(object value, Type typeToConvertTo)
18+
{
19+
return ConvertTo(value, typeToConvertTo, null);
20+
}
21+
22+
internal static ConversionResult ConvertTo(object value, Type typeToConvertTo, IContractResolver contractResolver)
1723
{
1824
if (value == null)
1925
{
@@ -28,8 +34,20 @@ public static ConversionResult ConvertTo(object value, Type typeToConvertTo)
2834
{
2935
try
3036
{
31-
var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), typeToConvertTo);
32-
return new ConversionResult(true, deserialized);
37+
if (contractResolver == null)
38+
{
39+
var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), typeToConvertTo);
40+
return new ConversionResult(true, deserialized);
41+
}
42+
else
43+
{
44+
var serializerSettings = new JsonSerializerSettings()
45+
{
46+
ContractResolver = contractResolver
47+
};
48+
var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), typeToConvertTo, serializerSettings);
49+
return new ConversionResult(true, deserialized);
50+
}
3351
}
3452
catch
3553
{

src/Features/JsonPatch/src/Internal/ListAdapter.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public virtual bool TryAdd(
3636
return false;
3737
}
3838

39-
if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
39+
if (!TryConvertValue(value, typeArgument, segment, contractResolver, out var convertedValue, out errorMessage))
4040
{
4141
return false;
4242
}
@@ -138,7 +138,7 @@ public virtual bool TryReplace(
138138
return false;
139139
}
140140

141-
if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
141+
if (!TryConvertValue(value, typeArgument, segment, contractResolver, out var convertedValue, out errorMessage))
142142
{
143143
return false;
144144
}
@@ -175,7 +175,7 @@ public virtual bool TryTest(
175175
return false;
176176
}
177177

178-
if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
178+
if (!TryConvertValue(value, typeArgument, segment, contractResolver, out var convertedValue, out errorMessage))
179179
{
180180
return false;
181181
}
@@ -235,7 +235,24 @@ protected virtual bool TryConvertValue(
235235
out object convertedValue,
236236
out string errorMessage)
237237
{
238-
var conversionResult = ConversionResultProvider.ConvertTo(originalValue, listTypeArgument);
238+
return TryConvertValue(
239+
originalValue,
240+
listTypeArgument,
241+
segment,
242+
null,
243+
out convertedValue,
244+
out errorMessage);
245+
}
246+
247+
protected virtual bool TryConvertValue(
248+
object originalValue,
249+
Type listTypeArgument,
250+
string segment,
251+
IContractResolver contractResolver,
252+
out object convertedValue,
253+
out string errorMessage)
254+
{
255+
var conversionResult = ConversionResultProvider.ConvertTo(originalValue, listTypeArgument, contractResolver);
239256
if (!conversionResult.CanBeConverted)
240257
{
241258
convertedValue = null;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
#nullable enable
2+
~virtual Microsoft.AspNetCore.JsonPatch.Internal.ListAdapter.TryConvertValue(object originalValue, System.Type listTypeArgument, string segment, Newtonsoft.Json.Serialization.IContractResolver contractResolver, out object convertedValue, out string errorMessage) -> bool
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using Newtonsoft.Json;
7+
using Newtonsoft.Json.Converters;
8+
using Newtonsoft.Json.Linq;
9+
using Newtonsoft.Json.Serialization;
10+
using Xunit;
11+
12+
namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests
13+
{
14+
public class HeterogenousCollectionTests
15+
{
16+
[Fact]
17+
public void AddItemToList()
18+
{
19+
// Arrange
20+
var targetObject = new Canvas()
21+
{
22+
Items = new List<Shape>()
23+
};
24+
25+
var circleJObject = JObject.Parse(@"{
26+
Type: 'Circle',
27+
ShapeProperty: 'Shape property',
28+
CircleProperty: 'Circle property'
29+
}");
30+
31+
var patchDocument = new JsonPatchDocument
32+
{
33+
ContractResolver = new CanvasContractResolver()
34+
};
35+
36+
patchDocument.Add("/Items/-", circleJObject);
37+
38+
// Act
39+
patchDocument.ApplyTo(targetObject);
40+
41+
// Assert
42+
var circle = targetObject.Items[0] as Circle;
43+
Assert.NotNull(circle);
44+
Assert.Equal("Shape property", circle.ShapeProperty);
45+
Assert.Equal("Circle property", circle.CircleProperty);
46+
}
47+
}
48+
49+
public class CanvasContractResolver : DefaultContractResolver
50+
{
51+
protected override JsonConverter ResolveContractConverter(Type objectType)
52+
{
53+
if (objectType == typeof(Shape))
54+
{
55+
return new ShapeJsonConverter();
56+
}
57+
58+
return base.ResolveContractConverter(objectType);
59+
}
60+
}
61+
62+
public class ShapeJsonConverter : CustomCreationConverter<Shape>
63+
{
64+
private const string TypeProperty = "Type";
65+
66+
public override bool CanRead => true;
67+
68+
public override Shape Create(Type objectType)
69+
{
70+
throw new NotImplementedException();
71+
}
72+
73+
public override object ReadJson(
74+
JsonReader reader,
75+
Type objectType,
76+
object existingValue,
77+
JsonSerializer serializer)
78+
{
79+
var jObject = JObject.Load(reader);
80+
81+
var target = CreateShape(jObject);
82+
serializer.Populate(jObject.CreateReader(), target);
83+
84+
return target;
85+
}
86+
87+
private Shape CreateShape(JObject jObject)
88+
{
89+
var typeProperty = jObject.GetValue(TypeProperty).ToString();
90+
91+
switch (typeProperty)
92+
{
93+
case "Circle":
94+
return new Circle();
95+
96+
case "Rectangle":
97+
return new Rectangle();
98+
}
99+
100+
throw new NotSupportedException();
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)