|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Microsoft.CSharp.RuntimeBinder;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using CSharpBinder = Microsoft.CSharp.RuntimeBinder;
namespace Microsoft.AspNetCore.JsonPatch.Internal;
/// <summary>
/// This API supports infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class DynamicObjectAdapter : IAdapter
{
public virtual bool TryAdd(
object target,
string segment,
IContractResolver contractResolver,
object value,
out string errorMessage)
{
if (!TrySetDynamicObjectProperty(target, contractResolver, segment, value, out errorMessage))
{
return false;
}
errorMessage = null;
return true;
}
public virtual bool TryGet(
object target,
string segment,
IContractResolver contractResolver,
out object value,
out string errorMessage)
{
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out value, out errorMessage))
{
value = null;
return false;
}
errorMessage = null;
return true;
}
public virtual bool TryRemove(
object target,
string segment,
IContractResolver contractResolver,
out string errorMessage)
{
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
{
return false;
}
// 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().IsValueType
&& Nullable.GetUnderlyingType(property.GetType()) == null)
{
value = Activator.CreateInstance(property.GetType());
}
if (!TrySetDynamicObjectProperty(target, contractResolver, segment, value, out errorMessage))
{
return false;
}
errorMessage = null;
return true;
}
public virtual bool TryReplace(
object target,
string segment,
IContractResolver contractResolver,
object value,
out string errorMessage)
{
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
{
return false;
}
if (!TryConvertValue(value, property.GetType(), contractResolver, out var convertedValue))
{
errorMessage = Resources.FormatInvalidValueForProperty(value);
return false;
}
if (!TryRemove(target, segment, contractResolver, out errorMessage))
{
return false;
}
if (!TrySetDynamicObjectProperty(target, contractResolver, segment, convertedValue, out errorMessage))
{
return false;
}
errorMessage = null;
return true;
}
public virtual bool TryTest(
object target,
string segment,
IContractResolver contractResolver,
object value,
out string errorMessage)
{
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
{
return false;
}
if (!TryConvertValue(value, property.GetType(), contractResolver, out var convertedValue))
{
errorMessage = Resources.FormatInvalidValueForProperty(value);
return false;
}
if (!JToken.DeepEquals(JsonConvert.SerializeObject(property), JsonConvert.SerializeObject(convertedValue)))
{
errorMessage = Resources.FormatValueNotEqualToTestValue(property, value, segment);
return false;
}
else
{
errorMessage = null;
return true;
}
}
public virtual bool TryTraverse(
object target,
string segment,
IContractResolver contractResolver,
out object nextTarget,
out string errorMessage)
{
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
{
nextTarget = null;
return false;
}
else
{
nextTarget = property;
errorMessage = null;
return true;
}
}
protected virtual bool TryGetDynamicObjectProperty(
object target,
IContractResolver contractResolver,
string segment,
out object value,
out string errorMessage)
{
var jsonDynamicContract = (JsonDynamicContract)contractResolver.ResolveContract(target.GetType());
var propertyName = jsonDynamicContract.PropertyNameResolver(segment);
var binder = CSharpBinder.Binder.GetMember(
CSharpBinderFlags.None,
propertyName,
target.GetType(),
new List<CSharpArgumentInfo>
{
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
});
var callsite = CallSite<Func<CallSite, object, object>>.Create(binder);
try
{
value = callsite.Target(callsite, target);
errorMessage = null;
return true;
}
catch (RuntimeBinderException)
{
value = null;
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
return false;
}
}
protected virtual bool TrySetDynamicObjectProperty(
object target,
IContractResolver contractResolver,
string segment,
object value,
out string errorMessage)
{
var jsonDynamicContract = (JsonDynamicContract)contractResolver.ResolveContract(target.GetType());
var propertyName = jsonDynamicContract.PropertyNameResolver(segment);
var binder = CSharpBinder.Binder.SetMember(
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, object>>.Create(binder);
try
{
callsite.Target(callsite, target, value);
errorMessage = null;
return true;
}
catch (RuntimeBinderException)
{
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
return false;
}
}
protected virtual bool TryConvertValue(object value, Type propertyType, out object convertedValue)
{
return TryConvertValue(value, propertyType, null, out convertedValue);
}
protected virtual bool TryConvertValue(object value, Type propertyType, IContractResolver contractResolver, out object convertedValue)
{
var conversionResult = ConversionResultProvider.ConvertTo(value, propertyType, contractResolver);
if (!conversionResult.CanBeConverted)
{
convertedValue = null;
return false;
}
convertedValue = conversionResult.ConvertedInstance;
return true;
}
}
|