Initial commit providing the AdapterFactory directly to the ObjectAdapter to all for customization of the Adapter selection and the ability to override the built in adapters to leverage their ability as much as possible.

This commit is contained in:
DHumphreys 2018-05-11 15:08:09 -04:00 committed by Ryan Nowak
parent d7ac5c57ef
commit 4d92d76b64
12 changed files with 263 additions and 68 deletions

View File

@ -0,0 +1,39 @@
using Microsoft.AspNetCore.JsonPatch.Internal;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.AspNetCore.JsonPatch.Adapters
{
/// <summary>
/// The default AdapterFactory to be used for resolving <see cref="IAdapter"/>.
/// </summary>
public class AdapterFactory : IAdapterFactory
{
/// <inheritdoc />
public virtual IAdapter Create(object target, IContractResolver contractResolver)
{
var jsonContract = contractResolver.ResolveContract(target.GetType());
if (target 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();
}
}
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.AspNetCore.JsonPatch.Internal;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.AspNetCore.JsonPatch.Adapters
{
/// <summary>
/// Defines the operations used for loading an <see cref="IAdapter"/> based on the current object and ContractResolver.
/// </summary>
public interface IAdapterFactory
{
/// <summary>
/// Creates an <see cref="IAdapter"/> for the current object
/// </summary>
/// <param name="target">The target object</param>
/// <param name="contractResolver">The current contract resolver</param>
/// <returns>The needed <see cref="IAdapter"/></returns>
IAdapter Create(object target, IContractResolver contractResolver);
}
}

View File

@ -18,17 +18,37 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
/// <param name="logErrorAction">The <see cref="Action"/> for logging <see cref="JsonPatchError"/>.</param>
public ObjectAdapter(
IContractResolver contractResolver,
Action<JsonPatchError> logErrorAction)
Action<JsonPatchError> logErrorAction):
this(contractResolver, logErrorAction, new AdapterFactory())
{
ContractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
LogErrorAction = logErrorAction;
}
/// <summary>
/// Initializes a new instance of <see cref="ObjectAdapter"/>.
/// </summary>
/// <param name="contractResolver">The <see cref="IContractResolver"/>.</param>
/// <param name="logErrorAction">The <see cref="Action"/> for logging <see cref="JsonPatchError"/>.</param>
/// <param name="adapterFactory">The <see cref="IAdapterFactory"/> to use when creating adaptors.</param>
public ObjectAdapter(
IContractResolver contractResolver,
Action<JsonPatchError> logErrorAction,
IAdapterFactory adapterFactory)
{
ContractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
LogErrorAction = logErrorAction;
AdapterFactory = adapterFactory ?? throw new ArgumentNullException(nameof(adapterFactory));
}
/// <summary>
/// Gets or sets the <see cref="IContractResolver"/>.
/// </summary>
public IContractResolver ContractResolver { get; }
/// <summary>
/// Gets or sets the <see cref="IAdapterFactory"/>
/// </summary>
public IAdapterFactory AdapterFactory { get; set; }
/// <summary>
/// Action for logging <see cref="JsonPatchError"/>.
/// </summary>
@ -75,7 +95,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
}
var parsedPath = new ParsedPath(path);
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
var visitor = new ObjectVisitor(parsedPath, ContractResolver, AdapterFactory);
var target = objectToApplyTo;
if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
@ -144,7 +164,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
private void Remove(string path, object objectToApplyTo, Operation operationToReport)
{
var parsedPath = new ParsedPath(path);
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
var visitor = new ObjectVisitor(parsedPath, ContractResolver, AdapterFactory);
var target = objectToApplyTo;
if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
@ -175,7 +195,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
}
var parsedPath = new ParsedPath(operation.path);
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
var visitor = new ObjectVisitor(parsedPath, ContractResolver, AdapterFactory);
var target = objectToApplyTo;
if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
@ -239,7 +259,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
}
var parsedPath = new ParsedPath(operation.path);
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
var visitor = new ObjectVisitor(parsedPath, ContractResolver, AdapterFactory);
var target = objectToApplyTo;
if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
@ -281,7 +301,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
propertyValue = null;
var parsedPath = new ParsedPath(fromLocation);
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
var visitor = new ObjectVisitor(parsedPath, ContractResolver, AdapterFactory);
var target = objectToGetValueFrom;
if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
{
public class DictionaryAdapter<TKey, TValue> : IAdapter
{
public bool TryAdd(
public virtual bool TryAdd(
object target,
string segment,
IContractResolver contractResolver,
@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryGet(
public virtual bool TryGet(
object target,
string segment,
IContractResolver contractResolver,
@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryRemove(
public virtual bool TryRemove(
object target,
string segment,
IContractResolver contractResolver,
@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryReplace(
public virtual bool TryReplace(
object target,
string segment,
IContractResolver contractResolver,
@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryTest(
public virtual bool TryTest(
object target,
string segment,
IContractResolver contractResolver,
@ -177,7 +177,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
}
}
public bool TryTraverse(
public virtual bool TryTraverse(
object target,
string segment,
IContractResolver contractResolver,
@ -208,7 +208,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
}
}
private bool TryConvertKey(string key, out TKey convertedKey, out string errorMessage)
protected virtual bool TryConvertKey(string key, out TKey convertedKey, out string errorMessage)
{
var conversionResult = ConversionResultProvider.ConvertTo(key, typeof(TKey));
if (conversionResult.CanBeConverted)
@ -225,7 +225,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
}
}
private bool TryConvertValue(object value, out TValue convertedValue, out string errorMessage)
protected virtual bool TryConvertValue(object value, out TValue convertedValue, out string errorMessage)
{
var conversionResult = ConversionResultProvider.ConvertTo(value, typeof(TValue));
if (conversionResult.CanBeConverted)

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
{
public class DynamicObjectAdapter : IAdapter
{
public bool TryAdd(
public virtual bool TryAdd(
object target,
string segment,
IContractResolver contractResolver,
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryGet(
public virtual bool TryGet(
object target,
string segment,
IContractResolver contractResolver,
@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryRemove(
public virtual bool TryRemove(
object target,
string segment,
IContractResolver contractResolver,
@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
}
public bool TryReplace(
public virtual bool TryReplace(
object target,
string segment,
IContractResolver contractResolver,
@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryTest(
public virtual bool TryTest(
object target,
string segment,
IContractResolver contractResolver,
@ -135,7 +135,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
}
}
public bool TryTraverse(
public virtual bool TryTraverse(
object target,
string segment,
IContractResolver contractResolver,
@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
}
}
private bool TryGetDynamicObjectProperty(
protected virtual bool TryGetDynamicObjectProperty(
object target,
IContractResolver contractResolver,
string segment,
@ -191,7 +191,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
}
}
private bool TrySetDynamicObjectProperty(
protected virtual bool TrySetDynamicObjectProperty(
object target,
IContractResolver contractResolver,
string segment,
@ -227,7 +227,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
}
}
private bool TryConvertValue(object value, Type propertyType, out object convertedValue)
protected virtual bool TryConvertValue(object value, Type propertyType, out object convertedValue)
{
var conversionResult = ConversionResultProvider.ConvertTo(value, propertyType);
if (!conversionResult.CanBeConverted)

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
{
public class ListAdapter : IAdapter
{
public bool TryAdd(
public virtual bool TryAdd(
object target,
string segment,
IContractResolver contractResolver,
@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryGet(
public virtual bool TryGet(
object target,
string segment,
IContractResolver contractResolver,
@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryRemove(
public virtual bool TryRemove(
object target,
string segment,
IContractResolver contractResolver,
@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryReplace(
public virtual bool TryReplace(
object target,
string segment,
IContractResolver contractResolver,
@ -152,7 +152,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryTest(
public virtual bool TryTest(
object target,
string segment,
IContractResolver contractResolver,
@ -189,7 +189,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
}
}
public bool TryTraverse(
public virtual bool TryTraverse(
object target,
string segment,
IContractResolver contractResolver,
@ -224,7 +224,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
private bool TryConvertValue(
protected virtual bool TryConvertValue(
object originalValue,
Type listTypeArgument,
string segment,
@ -244,7 +244,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
private bool TryGetListTypeArgument(IList list, out Type listTypeArgument, out string errorMessage)
protected virtual 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();
@ -272,7 +272,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
}
}
private bool TryGetPositionInfo(
protected virtual bool TryGetPositionInfo(
IList list,
string segment,
OperationType operationType,
@ -318,7 +318,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
}
}
private struct PositionInfo
protected struct PositionInfo
{
public PositionInfo(PositionType type, int index)
{
@ -330,7 +330,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
public int Index { get; }
}
private enum PositionType
protected enum PositionType
{
Index, // valid index
EndOfList, // '-'
@ -338,7 +338,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
OutOfBounds
}
private enum OperationType
protected enum OperationType
{
Add,
Remove,

View File

@ -3,19 +3,38 @@
using System;
using System.Collections;
using Microsoft.AspNetCore.JsonPatch.Adapters;
using Newtonsoft.Json.Serialization;
namespace Microsoft.AspNetCore.JsonPatch.Internal
{
public class ObjectVisitor
{
private readonly IAdapterFactory _adapterFactory;
private readonly IContractResolver _contractResolver;
private readonly ParsedPath _path;
/// <summary>
/// Initializes a new instance of <see cref="ObjectVisitor"/>.
/// </summary>
/// <param name="path">The path of the JsonPatch operation</param>
/// <param name="contractResolver">The <see cref="IContractResolver"/>.</param>
public ObjectVisitor(ParsedPath path, IContractResolver contractResolver)
:this(path, contractResolver, new AdapterFactory())
{
}
/// <summary>
/// Initializes a new instance of <see cref="ObjectVisitor"/>.
/// </summary>
/// <param name="path">The path of the JsonPatch operation</param>
/// <param name="contractResolver">The <see cref="IContractResolver"/>.</param>
/// <param name="adapterFactory">The <see cref="IAdapterFactory"/> to use when creating adaptors.</param>
public ObjectVisitor(ParsedPath path, IContractResolver contractResolver, IAdapterFactory adapterFactory)
{
_path = path;
_contractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
_adapterFactory = adapterFactory ?? throw new ArgumentNullException(nameof(adapterFactory));
}
public bool TryVisit(ref object target, out IAdapter adapter, out string errorMessage)
@ -48,25 +67,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
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();
}
return _adapterFactory.Create(targetObject, _contractResolver);
}
}
}

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
{
public class PocoAdapter : IAdapter
{
public bool TryAdd(
public virtual bool TryAdd(
object target,
string segment,
IContractResolver contractResolver,
@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryGet(
public virtual bool TryGet(
object target,
string segment,
IContractResolver contractResolver,
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryRemove(
public virtual bool TryRemove(
object target,
string segment,
IContractResolver contractResolver,
@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryReplace(
public virtual bool TryReplace(
object target,
string segment,
IContractResolver
@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryTest(
public virtual bool TryTest(
object target,
string segment,
IContractResolver
@ -171,7 +171,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return true;
}
public bool TryTraverse(
public virtual bool TryTraverse(
object target,
string segment,
IContractResolver contractResolver,
@ -197,7 +197,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return false;
}
private bool TryGetJsonProperty(
protected virtual bool TryGetJsonProperty(
object target,
IContractResolver contractResolver,
string segment,
@ -220,7 +220,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
return false;
}
private bool TryConvertValue(object value, Type propertyType, out object convertedValue)
protected virtual bool TryConvertValue(object value, Type propertyType, out object convertedValue)
{
var conversionResult = ConversionResultProvider.ConvertTo(value, propertyType);
if (!conversionResult.CanBeConverted)

View File

@ -174,7 +174,7 @@ namespace Microsoft.AspNetCore.JsonPatch
throw new ArgumentNullException(nameof(objectToApplyTo));
}
ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction: null));
ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, null, new AdapterFactory()));
}
/// <summary>
@ -183,13 +183,28 @@ namespace Microsoft.AspNetCore.JsonPatch
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
/// <param name="logErrorAction">Action to log errors</param>
public void ApplyTo(object objectToApplyTo, Action<JsonPatchError> logErrorAction)
{
ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction, new AdapterFactory()), logErrorAction);
}
/// <summary>
/// Apply this JsonPatchDocument
/// </summary>
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
/// <param name="adapter">IObjectAdapter instance to use when applying</param>
/// <param name="logErrorAction">Action to log errors</param>
public void ApplyTo(object objectToApplyTo, IObjectAdapter adapter, Action<JsonPatchError> logErrorAction)
{
if (objectToApplyTo == null)
{
throw new ArgumentNullException(nameof(objectToApplyTo));
}
var adapter = new ObjectAdapter(ContractResolver, logErrorAction);
if (adapter == null)
{
throw new ArgumentNullException(nameof(adapter));
}
foreach (var op in Operations)
{
try

View File

@ -697,7 +697,7 @@ namespace Microsoft.AspNetCore.JsonPatch
throw new ArgumentNullException(nameof(objectToApplyTo));
}
ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction: null));
ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, null, new AdapterFactory()));
}
/// <summary>
@ -706,13 +706,28 @@ namespace Microsoft.AspNetCore.JsonPatch
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
/// <param name="logErrorAction">Action to log errors</param>
public void ApplyTo(TModel objectToApplyTo, Action<JsonPatchError> logErrorAction)
{
ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction, new AdapterFactory()), logErrorAction);
}
/// <summary>
/// Apply this JsonPatchDocument
/// </summary>
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
/// <param name="adapter">IObjectAdapter instance to use when applying</param>
/// <param name="logErrorAction">Action to log errors</param>
public void ApplyTo(TModel objectToApplyTo, IObjectAdapter adapter, Action<JsonPatchError> logErrorAction)
{
if (objectToApplyTo == null)
{
throw new ArgumentNullException(nameof(objectToApplyTo));
}
var adapter = new ObjectAdapter(ContractResolver, logErrorAction);
if (adapter == null)
{
throw new ArgumentNullException(nameof(adapter));
}
foreach (var op in Operations)
{
try

View File

@ -0,0 +1,73 @@
using Microsoft.AspNetCore.JsonPatch.Adapters;
using Microsoft.AspNetCore.JsonPatch.Internal;
using Moq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Text;
using Xunit;
namespace Microsoft.AspNetCore.JsonPatch.Test.Adapters
{
public class AdapterFactoryTests
{
[Fact]
public void GetListAdapterForListTargets()
{
// Arrange
AdapterFactory factory = new AdapterFactory();
//Act:
IAdapter adapter = factory.Create(new List<string>(), new DefaultContractResolver());
// Assert
Assert.Equal(typeof(ListAdapter), adapter.GetType());
}
[Fact]
public void GetDictionaryAdapterForDictionaryObjects()
{
// Arrange
AdapterFactory factory = new AdapterFactory();
//Act:
IAdapter adapter = factory.Create(new Dictionary<string, string>(), new DefaultContractResolver());
// Assert
Assert.Equal(typeof(DictionaryAdapter<string, string>), adapter.GetType());
}
private class PocoModel
{}
[Fact]
public void GetPocoAdapterForGenericObjects()
{
// Arrange
AdapterFactory factory = new AdapterFactory();
//Act:
IAdapter adapter = factory.Create(new PocoModel(), new DefaultContractResolver());
// Assert
Assert.Equal(typeof(PocoAdapter), adapter.GetType());
}
[Fact]
public void GetDynamicAdapterForGenericObjects()
{
// Arrange
AdapterFactory factory = new AdapterFactory();
//Act:
IAdapter adapter = factory.Create(new TestDynamicObject(), new DefaultContractResolver());
// Assert
Assert.Equal(typeof(DynamicObjectAdapter), adapter.GetType());
}
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Text;
namespace Microsoft.AspNetCore.JsonPatch.Test.Adapters
{
public class TestDynamicObject : DynamicObject
{ }
}