302 lines
11 KiB
C#
302 lines
11 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using Unosquare.Swan.Attributes;
|
|
|
|
namespace Unosquare.Swan.Formatters {
|
|
/// <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 Boolean _includeNonPublic;
|
|
|
|
private Converter(
|
|
Object source,
|
|
Type targetType,
|
|
ref Object targetInstance,
|
|
Boolean includeNonPublic) {
|
|
this._targetType = targetInstance != null ? targetInstance.GetType() : targetType;
|
|
this._includeNonPublic = includeNonPublic;
|
|
|
|
if(source == null) {
|
|
return;
|
|
}
|
|
|
|
Type sourceType = source.GetType();
|
|
|
|
if(this._targetType == null || this._targetType == typeof(Object)) {
|
|
this._targetType = sourceType;
|
|
}
|
|
|
|
if(sourceType == this._targetType) {
|
|
this._target = source;
|
|
return;
|
|
}
|
|
|
|
if(!this.TrySetInstance(targetInstance, source, ref this._target)) {
|
|
return;
|
|
}
|
|
|
|
this.ResolveObject(source, ref this._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,
|
|
Boolean includeNonPublic) {
|
|
Object nullRef = null;
|
|
return new Converter(source, targetType, ref nullRef, includeNonPublic).GetResult();
|
|
}
|
|
|
|
private static Object FromJsonResult(Object source,
|
|
Type targetType,
|
|
ref Object targetInstance,
|
|
Boolean includeNonPublic) => 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) {
|
|
String targetPropertyName = MemberInfoNameCache.GetOrAdd(
|
|
targetProperty,
|
|
x => Runtime.AttributeCache.RetrieveOne<JsonPropertyAttribute>(x)?.PropertyName ?? x.Name);
|
|
|
|
return sourceProperties.GetValueOrDefault(targetPropertyName);
|
|
}
|
|
|
|
private Boolean TrySetInstance(Object targetInstance, Object source, ref Object target) {
|
|
if(targetInstance == null) {
|
|
// Try to create a default instance
|
|
try {
|
|
source.CreateTarget(this._targetType, this._includeNonPublic, ref target);
|
|
} catch {
|
|
return false;
|
|
}
|
|
} else {
|
|
target = targetInstance;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private Object GetResult() => this._target ?? this._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 this._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:
|
|
this.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:
|
|
this.PopulateObject(sourceProperties);
|
|
break;
|
|
|
|
// Case 2.1: Source is List, Target is Array
|
|
case List<Object> sourceList when target is Array targetArray:
|
|
this.PopulateArray(sourceList, targetArray);
|
|
break;
|
|
|
|
// Case 2.2: Source is List, Target is IList
|
|
case List<Object> sourceList when target is IList targetList:
|
|
this.PopulateIList(sourceList, targetList);
|
|
break;
|
|
|
|
// Case 3: Source is a simple type; Attempt conversion
|
|
default:
|
|
String sourceStringValue = source.ToStringInvariant();
|
|
|
|
// Handle basic types or enumerations if not
|
|
if(!this._targetType.TryParseBasicType(sourceStringValue, out target)) {
|
|
this.GetEnumValue(sourceStringValue, ref target);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void PopulateIList(IList<Object> objects, IList list) {
|
|
Type parameterType = GetAddMethodParameterType(this._targetType);
|
|
if(parameterType == null) {
|
|
return;
|
|
}
|
|
|
|
foreach(Object item in objects) {
|
|
try {
|
|
_ = list.Add(FromJsonResult(
|
|
item,
|
|
parameterType,
|
|
this._includeNonPublic));
|
|
} catch {
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
|
|
private void PopulateArray(IList<Object> objects, Array array) {
|
|
Type elementType = this._targetType.GetElementType();
|
|
|
|
for(Int32 i = 0; i < objects.Count; i++) {
|
|
try {
|
|
Object targetItem = FromJsonResult(
|
|
objects[i],
|
|
elementType,
|
|
this._includeNonPublic);
|
|
array.SetValue(targetItem, i);
|
|
} catch {
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
|
|
private void GetEnumValue(String sourceStringValue, ref Object target) {
|
|
Type enumType = Nullable.GetUnderlyingType(this._targetType);
|
|
if(enumType == null && this._targetType.GetTypeInfo().IsEnum) {
|
|
enumType = this._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
|
|
MethodInfo addMethod = this._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;
|
|
}
|
|
|
|
ParameterInfo[] addMethodParameters = addMethod.GetParameters();
|
|
if(addMethodParameters[0].ParameterType != typeof(String)) {
|
|
return;
|
|
}
|
|
|
|
// Retrieve the target entry type
|
|
Type targetEntryType = addMethodParameters[1].ParameterType;
|
|
|
|
// Add the items to the target dictionary
|
|
foreach(KeyValuePair<String, Object> sourceProperty in sourceProperties) {
|
|
try {
|
|
Object targetEntryValue = FromJsonResult(
|
|
sourceProperty.Value,
|
|
targetEntryType,
|
|
this._includeNonPublic);
|
|
targetDictionary.Add(sourceProperty.Key, targetEntryValue);
|
|
} catch {
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
|
|
private void PopulateObject(IDictionary<String, Object> sourceProperties) {
|
|
if(this._targetType.IsValueType()) {
|
|
this.PopulateFields(sourceProperties);
|
|
}
|
|
|
|
this.PopulateProperties(sourceProperties);
|
|
}
|
|
|
|
private void PopulateProperties(IDictionary<String, Object> sourceProperties) {
|
|
IEnumerable<PropertyInfo> properties = PropertyTypeCache.RetrieveFilteredProperties(this._targetType, false, p => p.CanWrite);
|
|
|
|
foreach(PropertyInfo property in properties) {
|
|
Object sourcePropertyValue = GetSourcePropertyValue(sourceProperties, property);
|
|
if(sourcePropertyValue == null) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
Object currentPropertyValue = !property.PropertyType.IsArray
|
|
? property.GetCacheGetMethod(this._includeNonPublic)(this._target)
|
|
: null;
|
|
|
|
Object targetPropertyValue = FromJsonResult(
|
|
sourcePropertyValue,
|
|
property.PropertyType,
|
|
ref currentPropertyValue,
|
|
this._includeNonPublic);
|
|
|
|
property.GetCacheSetMethod(this._includeNonPublic)(this._target, new[] { targetPropertyValue });
|
|
} catch {
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
|
|
private void PopulateFields(IDictionary<String, Object> sourceProperties) {
|
|
foreach(FieldInfo field in FieldTypeCache.RetrieveAllFields(this._targetType)) {
|
|
Object sourcePropertyValue = GetSourcePropertyValue(sourceProperties, field);
|
|
if(sourcePropertyValue == null) {
|
|
continue;
|
|
}
|
|
|
|
Object targetPropertyValue = FromJsonResult(
|
|
sourcePropertyValue,
|
|
field.FieldType,
|
|
this._includeNonPublic);
|
|
|
|
try {
|
|
field.SetValue(this._target, targetPropertyValue);
|
|
} catch {
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |