Skip to content
This repository has been archived by the owner on Nov 15, 2018. It is now read-only.

Commit

Permalink
Add new adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
jbagga committed Aug 21, 2017
1 parent a063e6f commit ff90ebc
Show file tree
Hide file tree
Showing 13 changed files with 1,628 additions and 222 deletions.
168 changes: 123 additions & 45 deletions src/Microsoft.AspNetCore.JsonPatch/Internal/DynamicObjectAdapter.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
// 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.Dynamic;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.CSharp.RuntimeBinder;
using Newtonsoft.Json.Serialization;

namespace Microsoft.AspNetCore.JsonPatch.Internal
Expand All @@ -16,12 +20,11 @@ public bool TryAdd(
object value,
out string errorMessage)
{
var dictionary = (IDictionary<string, object>)target;

var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);

// As per JsonPatch spec, if a key already exists, adding should replace the existing value
dictionary[key] = ConvertValue(dictionary, key, value);
if(!TrySetDynamicObjectProperty(target, contractResolver, segment, value))
{
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
return false;
}

errorMessage = null;
return true;
Expand All @@ -34,10 +37,12 @@ public bool TryGet(
out object value,
out string errorMessage)
{
var dictionary = (IDictionary<string, object>)target;

var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);
value = dictionary[key];
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out value))
{
value = null;
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
return false;
}

errorMessage = null;
return true;
Expand All @@ -49,21 +54,31 @@ public bool TryRemove(
IContractResolver contractResolver,
out string errorMessage)
{
var dictionary = (IDictionary<string, object>)target;
object property = null;
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out property))
{
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
return false;
}

var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);
// 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());
}

// As per JsonPatch spec, the target location must exist for remove to be successful
if (!dictionary.ContainsKey(key))
if (!TrySetDynamicObjectProperty(target, contractResolver, segment, value))
{
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
return false;
}

dictionary.Remove(key);

errorMessage = null;
return true;

}

public bool TryReplace(
Expand All @@ -73,19 +88,26 @@ public bool TryReplace(
object value,
out string errorMessage)
{
var dictionary = (IDictionary<string, object>)target;
object property = null;
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out property))
{
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
return false;
}

var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);
object convertedValue = null;
if (!TryConvertValue(value, property.GetType(), out convertedValue))
{
errorMessage = Resources.FormatInvalidValueForProperty(value);
return false;
}

// As per JsonPatch spec, the target location must exist for remove to be successful
if (!dictionary.ContainsKey(key))
if (!TrySetDynamicObjectProperty(target, contractResolver, segment, convertedValue))
{
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
return false;
}

dictionary[key] = ConvertValue(dictionary, key, value);

errorMessage = null;
return true;
}
Expand All @@ -97,47 +119,103 @@ public bool TryTraverse(
out object nextTarget,
out string errorMessage)
{
var dynamicObject = target as DynamicObject;
var dynamicObject = target as IDynamicMetaObjectProvider;
if (dynamicObject == null)
{
nextTarget = null;
errorMessage = null;
return false;
}

object property = null;
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out property))
{
nextTarget = null;
errorMessage = null;
return false;
}
else
{
nextTarget = property;
errorMessage = null;
return true;
}
}

private bool TryGetDynamicObjectProperty(
object target,
IContractResolver contractResolver,
string segment,
out object property)
{
var jsonDynamicContract = contractResolver.ResolveContract(target.GetType()) as JsonDynamicContract;

var propertyName = jsonDynamicContract?.PropertyNameResolver(segment);

var binder = CSharp.RuntimeBinder.Binder.GetMember(CSharpBinderFlags.None,
propertyName,
target.GetType(),
new List<CSharpArgumentInfo>{
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
});

var callsite = CallSite<Func<CallSite, object, object>>.Create(binder);

try
{
property = callsite.Target(callsite, target);
return true;
}
catch
{
property = null;
return false;
}
}

var dictionary = (IDictionary<string, object>)dynamicObject;
private bool TrySetDynamicObjectProperty(
object target,
IContractResolver contractResolver,
string segment,
object value)
{
var jsonDynamicContract = contractResolver.ResolveContract(target.GetType()) as JsonDynamicContract;

var propertyName = jsonDynamicContract?.PropertyNameResolver(segment);

var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);
var binder = CSharp.RuntimeBinder.Binder.SetMember(CSharpBinderFlags.None,
propertyName,
target.GetType(),
new List<CSharpArgumentInfo>{
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
});

if (dictionary.ContainsKey(key))
var callsite = CallSite<Func<CallSite, object, object, object>>.Create(binder);

try
{
nextTarget = dictionary[key];
errorMessage = null;
callsite.Target(callsite, target, value);
return true;
}
else
catch
{
nextTarget = null;
errorMessage = null;
return false;
}
}

private object ConvertValue(IDictionary<string, object> dictionary, string key, object newValue)
private bool TryConvertValue(object value, Type propertyType, out object convertedValue)
{
object existingValue = null;
if (dictionary.TryGetValue(key, out existingValue))
{
if (existingValue != null)
{
var conversionResult = ConversionResultProvider.ConvertTo(newValue, existingValue.GetType());
if (conversionResult.CanBeConverted)
{
return conversionResult.ConvertedInstance;
}
}
}
return newValue;
var conversionResult = ConversionResultProvider.ConvertTo(value, propertyType);
if (!conversionResult.CanBeConverted)
{
convertedValue = null;
return false;
}

convertedValue = conversionResult.ConvertedInstance;
return true;
}
}
}
143 changes: 143 additions & 0 deletions src/Microsoft.AspNetCore.JsonPatch/Internal/ExpandoObjectAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// 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;

namespace Microsoft.AspNetCore.JsonPatch.Internal
{
public class ExpandoObjectAdapter : IAdapter
{
public bool TryAdd(
object target,
string segment,
IContractResolver contractResolver,
object value,
out string errorMessage)
{
var dictionary = (IDictionary<string, object>)target;

var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);

// As per JsonPatch spec, if a key already exists, adding should replace the existing value
dictionary[key] = ConvertValue(dictionary, key, value);

errorMessage = null;
return true;
}

public bool TryGet(
object target,
string segment,
IContractResolver contractResolver,
out object value,
out string errorMessage)
{
var dictionary = (IDictionary<string, object>)target;

var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);
value = dictionary[key];

errorMessage = null;
return true;
}

public bool TryRemove(
object target,
string segment,
IContractResolver contractResolver,
out string errorMessage)
{
var dictionary = (IDictionary<string, object>)target;

var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);

// As per JsonPatch spec, the target location must exist for remove to be successful
if (!dictionary.ContainsKey(key))
{
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
return false;
}

dictionary.Remove(key);

errorMessage = null;
return true;
}

public bool TryReplace(
object target,
string segment,
IContractResolver contractResolver,
object value,
out string errorMessage)
{
var dictionary = (IDictionary<string, object>)target;

var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);

// As per JsonPatch spec, the target location must exist for remove to be successful
if (!dictionary.ContainsKey(key))
{
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
return false;
}

dictionary[key] = ConvertValue(dictionary, key, value);

errorMessage = null;
return true;
}

public bool TryTraverse(
object target,
string segment,
IContractResolver contractResolver,
out object nextTarget,
out string errorMessage)
{
var expandoObject = target as ExpandoObject;
if (expandoObject == null)
{
errorMessage = null;
nextTarget = null;
return false;
}

var dictionary = (IDictionary<string, object>)expandoObject;

var key = dictionary.GetKeyUsingCaseInsensitiveSearch(segment);

if (dictionary.ContainsKey(key))
{
nextTarget = dictionary[key];
errorMessage = null;
return true;
}
else
{
nextTarget = null;
errorMessage = null;
return false;
}
}

private object ConvertValue(IDictionary<string, object> dictionary, string key, object newValue)
{
object existingValue = null;
if (dictionary.TryGetValue(key, out existingValue))
{
if (existingValue != null)
{
var conversionResult = ConversionResultProvider.ConvertTo(newValue, existingValue.GetType());
if (conversionResult.CanBeConverted)
{
return conversionResult.ConvertedInstance;
}
}
}
return newValue;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
namespace Microsoft.AspNetCore.JsonPatch.Internal
{
// Helper methods to allow case-insensitive key search
public static class DynamicObjectDictionaryExtensions
public static class ExpandoObjectDictionaryExtensions
{
internal static string GetKeyUsingCaseInsensitiveSearch(
this IDictionary<string, object> propertyDictionary,
Expand Down
Loading

0 comments on commit ff90ebc

Please sign in to comment.