335 lines
13 KiB
C#
335 lines
13 KiB
C#
namespace Unosquare.Swan.Formatters
|
|
{
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using Attributes;
|
|
|
|
/// <summary>
|
|
/// A very simple, light-weight JSON library written by Mario
|
|
/// to teach Geo how things are done
|
|
///
|
|
/// This is an useful helper for small tasks but it doesn't represent a full-featured
|
|
/// serializer such as the beloved Json.NET.
|
|
/// </summary>
|
|
public static partial class Json
|
|
{
|
|
private class Converter
|
|
{
|
|
private static readonly ConcurrentDictionary<MemberInfo, string> MemberInfoNameCache =
|
|
new ConcurrentDictionary<MemberInfo, string>();
|
|
|
|
private static readonly ConcurrentDictionary<Type, Type> ListAddMethodCache = new ConcurrentDictionary<Type, Type>();
|
|
|
|
private readonly object _target;
|
|
private readonly Type _targetType;
|
|
private readonly bool _includeNonPublic;
|
|
|
|
private Converter(
|
|
object source,
|
|
Type targetType,
|
|
ref object targetInstance,
|
|
bool includeNonPublic)
|
|
{
|
|
_targetType = targetInstance != null ? targetInstance.GetType() : targetType;
|
|
_includeNonPublic = includeNonPublic;
|
|
|
|
if (source == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var sourceType = source.GetType();
|
|
|
|
if (_targetType == null || _targetType == typeof(object)) _targetType = sourceType;
|
|
if (sourceType == _targetType)
|
|
{
|
|
_target = source;
|
|
return;
|
|
}
|
|
|
|
if (!TrySetInstance(targetInstance, source, ref _target))
|
|
return;
|
|
|
|
ResolveObject(source, ref _target);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a json deserialized object (simple type, dictionary or list) to a new instance of the specified target type.
|
|
/// </summary>
|
|
/// <param name="source">The source.</param>
|
|
/// <param name="targetType">Type of the target.</param>
|
|
/// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param>
|
|
/// <returns>The target object.</returns>
|
|
internal static object FromJsonResult(object source,
|
|
Type targetType,
|
|
bool includeNonPublic)
|
|
{
|
|
object nullRef = null;
|
|
return new Converter(source, targetType, ref nullRef, includeNonPublic).GetResult();
|
|
}
|
|
|
|
private static object FromJsonResult(object source,
|
|
Type targetType,
|
|
ref object targetInstance,
|
|
bool includeNonPublic)
|
|
{
|
|
return new Converter(source, targetType, ref targetInstance, includeNonPublic).GetResult();
|
|
}
|
|
|
|
private static Type GetAddMethodParameterType(Type targetType)
|
|
=> ListAddMethodCache.GetOrAdd(targetType,
|
|
x => x.GetMethods()
|
|
.FirstOrDefault(
|
|
m => m.Name.Equals(AddMethodName) && m.IsPublic && m.GetParameters().Length == 1)?
|
|
.GetParameters()[0]
|
|
.ParameterType);
|
|
|
|
private static void GetByteArray(string sourceString, ref object target)
|
|
{
|
|
try
|
|
{
|
|
target = Convert.FromBase64String(sourceString);
|
|
} // Try conversion from Base 64
|
|
catch
|
|
{
|
|
target = Encoding.UTF8.GetBytes(sourceString);
|
|
} // Get the string bytes in UTF8
|
|
}
|
|
|
|
private static object GetSourcePropertyValue(IDictionary<string, object> sourceProperties,
|
|
MemberInfo targetProperty)
|
|
{
|
|
var targetPropertyName = MemberInfoNameCache.GetOrAdd(
|
|
targetProperty,
|
|
x => Runtime.AttributeCache.RetrieveOne<JsonPropertyAttribute>(x)?.PropertyName ?? x.Name);
|
|
|
|
return sourceProperties.GetValueOrDefault(targetPropertyName);
|
|
}
|
|
|
|
private bool TrySetInstance(object targetInstance, object source, ref object target)
|
|
{
|
|
if (targetInstance == null)
|
|
{
|
|
// Try to create a default instance
|
|
try
|
|
{
|
|
source.CreateTarget(_targetType, _includeNonPublic, ref target);
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
target = targetInstance;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private object GetResult() => _target ?? _targetType.GetDefault();
|
|
|
|
private void ResolveObject(object source, ref object target)
|
|
{
|
|
switch (source)
|
|
{
|
|
// Case 0: Special Cases Handling (Source and Target are of specific convertible types)
|
|
// Case 0.1: Source is string, Target is byte[]
|
|
case string sourceString when _targetType == typeof(byte[]):
|
|
GetByteArray(sourceString, ref target);
|
|
break;
|
|
|
|
// Case 1.1: Source is Dictionary, Target is IDictionary
|
|
case Dictionary<string, object> sourceProperties when target is IDictionary targetDictionary:
|
|
PopulateDictionary(sourceProperties, targetDictionary);
|
|
break;
|
|
|
|
// Case 1.2: Source is Dictionary, Target is not IDictionary (i.e. it is a complex type)
|
|
case Dictionary<string, object> sourceProperties:
|
|
PopulateObject(sourceProperties);
|
|
break;
|
|
|
|
// Case 2.1: Source is List, Target is Array
|
|
case List<object> sourceList when target is Array targetArray:
|
|
PopulateArray(sourceList, targetArray);
|
|
break;
|
|
|
|
// Case 2.2: Source is List, Target is IList
|
|
case List<object> sourceList when target is IList targetList:
|
|
PopulateIList(sourceList, targetList);
|
|
break;
|
|
|
|
// Case 3: Source is a simple type; Attempt conversion
|
|
default:
|
|
var sourceStringValue = source.ToStringInvariant();
|
|
|
|
// Handle basic types or enumerations if not
|
|
if (!_targetType.TryParseBasicType(sourceStringValue, out target))
|
|
GetEnumValue(sourceStringValue, ref target);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void PopulateIList(IList<object> objects, IList list)
|
|
{
|
|
var parameterType = GetAddMethodParameterType(_targetType);
|
|
if (parameterType == null) return;
|
|
|
|
foreach (var item in objects)
|
|
{
|
|
try
|
|
{
|
|
list.Add(FromJsonResult(
|
|
item,
|
|
parameterType,
|
|
_includeNonPublic));
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
|
|
private void PopulateArray(IList<object> objects, Array array)
|
|
{
|
|
var elementType = _targetType.GetElementType();
|
|
|
|
for (var i = 0; i < objects.Count; i++)
|
|
{
|
|
try
|
|
{
|
|
var targetItem = FromJsonResult(
|
|
objects[i],
|
|
elementType,
|
|
_includeNonPublic);
|
|
array.SetValue(targetItem, i);
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
|
|
private void GetEnumValue(string sourceStringValue, ref object target)
|
|
{
|
|
var enumType = Nullable.GetUnderlyingType(_targetType);
|
|
if (enumType == null && _targetType.GetTypeInfo().IsEnum) enumType = _targetType;
|
|
if (enumType == null) return;
|
|
|
|
try
|
|
{
|
|
target = Enum.Parse(enumType, sourceStringValue);
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
|
|
private void PopulateDictionary(IDictionary<string, object> sourceProperties, IDictionary targetDictionary)
|
|
{
|
|
// find the add method of the target dictionary
|
|
var addMethod = _targetType.GetMethods()
|
|
.FirstOrDefault(
|
|
m => m.Name.Equals(AddMethodName) && m.IsPublic && m.GetParameters().Length == 2);
|
|
|
|
// skip if we don't have a compatible add method
|
|
if (addMethod == null) return;
|
|
var addMethodParameters = addMethod.GetParameters();
|
|
if (addMethodParameters[0].ParameterType != typeof(string)) return;
|
|
|
|
// Retrieve the target entry type
|
|
var targetEntryType = addMethodParameters[1].ParameterType;
|
|
|
|
// Add the items to the target dictionary
|
|
foreach (var sourceProperty in sourceProperties)
|
|
{
|
|
try
|
|
{
|
|
var targetEntryValue = FromJsonResult(
|
|
sourceProperty.Value,
|
|
targetEntryType,
|
|
_includeNonPublic);
|
|
targetDictionary.Add(sourceProperty.Key, targetEntryValue);
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
|
|
private void PopulateObject(IDictionary<string, object> sourceProperties)
|
|
{
|
|
if (_targetType.IsValueType())
|
|
{
|
|
PopulateFields(sourceProperties);
|
|
}
|
|
|
|
PopulateProperties(sourceProperties);
|
|
}
|
|
|
|
private void PopulateProperties(IDictionary<string, object> sourceProperties)
|
|
{
|
|
var properties = PropertyTypeCache.RetrieveFilteredProperties(_targetType, false, p => p.CanWrite);
|
|
|
|
foreach (var property in properties)
|
|
{
|
|
var sourcePropertyValue = GetSourcePropertyValue(sourceProperties, property);
|
|
if (sourcePropertyValue == null) continue;
|
|
|
|
try
|
|
{
|
|
var currentPropertyValue = !property.PropertyType.IsArray
|
|
? property.GetCacheGetMethod(_includeNonPublic)(_target)
|
|
: null;
|
|
|
|
var targetPropertyValue = FromJsonResult(
|
|
sourcePropertyValue,
|
|
property.PropertyType,
|
|
ref currentPropertyValue,
|
|
_includeNonPublic);
|
|
|
|
property.GetCacheSetMethod(_includeNonPublic)(_target, new[] { targetPropertyValue });
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
|
|
private void PopulateFields(IDictionary<string, object> sourceProperties)
|
|
{
|
|
foreach (var field in FieldTypeCache.RetrieveAllFields(_targetType))
|
|
{
|
|
var sourcePropertyValue = GetSourcePropertyValue(sourceProperties, field);
|
|
if (sourcePropertyValue == null) continue;
|
|
|
|
var targetPropertyValue = FromJsonResult(
|
|
sourcePropertyValue,
|
|
field.FieldType,
|
|
_includeNonPublic);
|
|
|
|
try
|
|
{
|
|
field.SetValue(_target, targetPropertyValue);
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |