Skip to content

Adding support for custom JsonConverter(s) when deserializing JSON Patch value object #30974

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/Features/JsonPatch/src/Internal/ConversionResultProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace Microsoft.AspNetCore.JsonPatch.Internal
{
Expand All @@ -14,6 +15,11 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
public static class ConversionResultProvider
{
public static ConversionResult ConvertTo(object value, Type typeToConvertTo)
{
return ConvertTo(value, typeToConvertTo, null);
}

internal static ConversionResult ConvertTo(object value, Type typeToConvertTo, IContractResolver contractResolver)
{
if (value == null)
{
Expand All @@ -28,8 +34,20 @@ public static ConversionResult ConvertTo(object value, Type typeToConvertTo)
{
try
{
var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), typeToConvertTo);
return new ConversionResult(true, deserialized);
if (contractResolver == null)
{
var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), typeToConvertTo);
return new ConversionResult(true, deserialized);
}
else
{
var serializerSettings = new JsonSerializerSettings()
{
ContractResolver = contractResolver
};
var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), typeToConvertTo, serializerSettings);
return new ConversionResult(true, deserialized);
}
}
catch
{
Expand Down
25 changes: 21 additions & 4 deletions src/Features/JsonPatch/src/Internal/ListAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public virtual bool TryAdd(
return false;
}

if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
if (!TryConvertValue(value, typeArgument, segment, contractResolver, out var convertedValue, out errorMessage))
{
return false;
}
Expand Down Expand Up @@ -138,7 +138,7 @@ public virtual bool TryReplace(
return false;
}

if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
if (!TryConvertValue(value, typeArgument, segment, contractResolver, out var convertedValue, out errorMessage))
{
return false;
}
Expand Down Expand Up @@ -175,7 +175,7 @@ public virtual bool TryTest(
return false;
}

if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
if (!TryConvertValue(value, typeArgument, segment, contractResolver, out var convertedValue, out errorMessage))
{
return false;
}
Expand Down Expand Up @@ -235,7 +235,24 @@ protected virtual bool TryConvertValue(
out object convertedValue,
out string errorMessage)
{
var conversionResult = ConversionResultProvider.ConvertTo(originalValue, listTypeArgument);
return TryConvertValue(
originalValue,
listTypeArgument,
segment,
null,
out convertedValue,
out errorMessage);
}

protected virtual bool TryConvertValue(
object originalValue,
Type listTypeArgument,
string segment,
IContractResolver contractResolver,
out object convertedValue,
out string errorMessage)
{
var conversionResult = ConversionResultProvider.ConvertTo(originalValue, listTypeArgument, contractResolver);
if (!conversionResult.CanBeConverted)
{
convertedValue = null;
Expand Down
1 change: 1 addition & 0 deletions src/Features/JsonPatch/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
~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
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// 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 Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Xunit;

namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests
{
public class HeterogenousCollectionTests
{
[Fact]
public void AddItemToList()
{
// Arrange
var targetObject = new Canvas()
{
Items = new List<Shape>()
};

var circleJObject = JObject.Parse(@"{
Type: 'Circle',
ShapeProperty: 'Shape property',
CircleProperty: 'Circle property'
}");

var patchDocument = new JsonPatchDocument
{
ContractResolver = new CanvasContractResolver()
};

patchDocument.Add("/Items/-", circleJObject);

// Act
patchDocument.ApplyTo(targetObject);

// Assert
var circle = targetObject.Items[0] as Circle;
Assert.NotNull(circle);
Assert.Equal("Shape property", circle.ShapeProperty);
Assert.Equal("Circle property", circle.CircleProperty);
}
}

public class CanvasContractResolver : DefaultContractResolver
{
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (objectType == typeof(Shape))
{
return new ShapeJsonConverter();
}

return base.ResolveContractConverter(objectType);
}
}

public class ShapeJsonConverter : CustomCreationConverter<Shape>
{
private const string TypeProperty = "Type";

public override bool CanRead => true;

public override Shape Create(Type objectType)
{
throw new NotImplementedException();
}

public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
var jObject = JObject.Load(reader);

var target = CreateShape(jObject);
serializer.Populate(jObject.CreateReader(), target);

return target;
}

private Shape CreateShape(JObject jObject)
{
var typeProperty = jObject.GetValue(TypeProperty).ToString();

switch (typeProperty)
{
case "Circle":
return new Circle();

case "Rectangle":
return new Rectangle();
}

throw new NotSupportedException();
}
}
}
Loading