File: Internal\PocoAdapter.cs
Web Access
Project: src\src\Features\JsonPatch.SystemTextJson\src\Microsoft.AspNetCore.JsonPatch.SystemTextJson.csproj (Microsoft.AspNetCore.JsonPatch.SystemTextJson)
// 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.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization.Metadata;
using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Helpers;
 
namespace Microsoft.AspNetCore.JsonPatch.SystemTextJson.Internal;
 
internal class PocoAdapter : IAdapter
{
    public virtual bool TryAdd(
        object target,
        string segment,
        JsonSerializerOptions serializerOptions,
        object value,
        out string errorMessage)
    {
        if (!TryGetJsonProperty(target, serializerOptions, segment, out var jsonProperty))
        {
            errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
            return false;
        }
 
        if (jsonProperty.Set == null)
        {
            errorMessage = Resources.FormatCannotUpdateProperty(segment);
            return false;
        }
 
        if (!TryConvertValue(value, jsonProperty.PropertyType, serializerOptions, out var convertedValue))
        {
            errorMessage = Resources.FormatInvalidValueForProperty(value);
            return false;
        }
 
        jsonProperty.Set(target, convertedValue);
 
        errorMessage = null;
        return true;
    }
 
    public virtual bool TryGet(
        object target,
        string segment,
        JsonSerializerOptions serializerOptions,
        out object value,
        out string errorMessage)
    {
        if (!TryGetJsonProperty(target, serializerOptions, segment, out var jsonProperty))
        {
            errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
            value = null;
            return false;
        }
 
        if (jsonProperty.Get == null)
        {
            errorMessage = Resources.FormatCannotReadProperty(segment);
            value = null;
            return false;
        }
 
        value = jsonProperty.Get(target);
        errorMessage = null;
        return true;
    }
 
    public virtual bool TryRemove(
        object target,
        string segment,
        JsonSerializerOptions serializerOptions,
        out string errorMessage)
    {
        if (!TryGetJsonProperty(target, serializerOptions, segment, out var jsonProperty))
        {
            errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
            return false;
        }
 
        if (jsonProperty.Set == null)
        {
            errorMessage = Resources.FormatCannotUpdateProperty(segment);
            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 (jsonProperty.PropertyType.IsValueType
            && Nullable.GetUnderlyingType(jsonProperty.PropertyType) == null)
        {
            value = Activator.CreateInstance(jsonProperty.PropertyType);
        }
 
        jsonProperty.Set(target, value);
 
        errorMessage = null;
        return true;
    }
 
    public virtual bool TryReplace(
        object target,
        string segment,
        JsonSerializerOptions serializerOptions,
        object value,
        out string errorMessage)
    {
        if (!TryGetJsonProperty(target, serializerOptions, segment, out var jsonProperty))
        {
            errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
            return false;
        }
 
        if (jsonProperty.Set == null)
        {
            errorMessage = Resources.FormatCannotUpdateProperty(segment);
            return false;
        }
 
        if (!TryConvertValue(value, jsonProperty.PropertyType, serializerOptions, out var convertedValue))
        {
            errorMessage = Resources.FormatInvalidValueForProperty(value);
            return false;
        }
 
        jsonProperty.Set(target, convertedValue);
 
        errorMessage = null;
        return true;
    }
 
    public virtual bool TryTest(
        object target,
        string segment,
        JsonSerializerOptions serializerOptions,
        object value,
        out string errorMessage)
    {
        if (!TryGetJsonProperty(target, serializerOptions, segment, out var jsonProperty))
        {
            errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
            return false;
        }
 
        if (jsonProperty.Get == null)
        {
            errorMessage = Resources.FormatCannotReadProperty(segment);
            return false;
        }
 
        if (!TryConvertValue(value, jsonProperty.PropertyType, serializerOptions, out var convertedValue))
        {
            errorMessage = Resources.FormatInvalidValueForProperty(value);
            return false;
        }
 
        var currentValue = jsonProperty.Get(target);
        if (!JsonUtilities.DeepEquals(currentValue, convertedValue, serializerOptions))
        {
            errorMessage = Resources.FormatValueNotEqualToTestValue(currentValue, value, segment);
            return false;
        }
 
        errorMessage = null;
        return true;
    }
 
    public virtual bool TryTraverse(
        object target,
        string segment,
        JsonSerializerOptions serializerOptions,
        out object value,
        out string errorMessage)
    {
        if (target == null)
        {
            value = null;
            errorMessage = null;
            return false;
        }
 
        if (TryGetJsonProperty(target, serializerOptions, segment, out var jsonProperty))
        {
            value = jsonProperty.Get(target);
            errorMessage = null;
            return true;
        }
 
        value = null;
        errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
        return false;
    }
 
    protected virtual bool TryGetJsonProperty(
        object target,
        JsonSerializerOptions serializerOptions,
        string segment,
        out JsonPropertyInfo jsonProperty)
    {
        var typeInfo = serializerOptions.GetTypeInfo(target.GetType());
        if (typeInfo is not null)
        {
            var pocoProperty = typeInfo
                .Properties
                .FirstOrDefault(p => string.Equals(p.Name, segment, ExtractStringComparison(serializerOptions)));
 
            if (pocoProperty != null)
            {
                jsonProperty = pocoProperty;
                return true;
            }
        }
 
        jsonProperty = null;
        return false;
    }
 
    protected virtual bool TryConvertValue(object value, Type propertyType, JsonSerializerOptions serializerOptions, out object convertedValue)
    {
        var conversionResult = ConversionResultProvider.ConvertTo(value, propertyType, serializerOptions);
        if (!conversionResult.CanBeConverted)
        {
            convertedValue = null;
            return false;
        }
 
        convertedValue = conversionResult.ConvertedInstance;
        return true;
    }
 
    private static StringComparison ExtractStringComparison(JsonSerializerOptions serializerOptions)
        => serializerOptions.PropertyNameCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
}