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 { /// /// 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. /// public static partial class Json { private class Converter { private static readonly ConcurrentDictionary MemberInfoNameCache = new ConcurrentDictionary(); private static readonly ConcurrentDictionary ListAddMethodCache = new ConcurrentDictionary(); 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); } /// /// Converts a json deserialized object (simple type, dictionary or list) to a new instance of the specified target type. /// /// The source. /// Type of the target. /// if set to true [include non public]. /// The target object. 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 sourceProperties, MemberInfo targetProperty) { String targetPropertyName = MemberInfoNameCache.GetOrAdd( targetProperty, x => Runtime.AttributeCache.RetrieveOne(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 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 sourceProperties: this.PopulateObject(sourceProperties); break; // Case 2.1: Source is List, Target is Array case List sourceList when target is Array targetArray: this.PopulateArray(sourceList, targetArray); break; // Case 2.2: Source is List, Target is IList case List 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 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 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 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 sourceProperty in sourceProperties) { try { Object targetEntryValue = FromJsonResult( sourceProperty.Value, targetEntryType, this._includeNonPublic); targetDictionary.Add(sourceProperty.Key, targetEntryValue); } catch { // ignored } } } private void PopulateObject(IDictionary sourceProperties) { if(this._targetType.IsValueType()) { this.PopulateFields(sourceProperties); } this.PopulateProperties(sourceProperties); } private void PopulateProperties(IDictionary sourceProperties) { IEnumerable 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 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 } } } } } }