using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using Swan.Reflection; namespace 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 bool _includeNonPublic; private readonly JsonSerializerCase _jsonSerializerCase; private Converter( object? source, Type targetType, ref object? targetInstance, bool includeNonPublic, JsonSerializerCase jsonSerializerCase) { _targetType = targetInstance != null ? targetInstance.GetType() : targetType; _includeNonPublic = includeNonPublic; _jsonSerializerCase = jsonSerializerCase; 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); } internal static object? FromJsonResult( object? source, JsonSerializerCase jsonSerializerCase, Type? targetType = null, bool includeNonPublic = false) { object? nullRef = null; return new Converter(source, targetType ?? typeof(object), ref nullRef, includeNonPublic, jsonSerializerCase).GetResult(); } private static object? FromJsonResult(object source, Type targetType, ref object? targetInstance, bool includeNonPublic) { return new Converter(source, targetType, ref targetInstance, includeNonPublic, JsonSerializerCase.None).GetResult(); } private static Type? GetAddMethodParameterType(Type targetType) => ListAddMethodCache.GetOrAdd(targetType, x => x.GetMethods() .FirstOrDefault( m => m.Name == 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 (FormatException) { target = Encoding.UTF8.GetBytes(sourceString); } // Get the string bytes in UTF8 } private object GetSourcePropertyValue( IDictionary sourceProperties, MemberInfo targetProperty) { var targetPropertyName = MemberInfoNameCache.GetOrAdd( targetProperty, x => AttributeCache.DefaultCache.Value.RetrieveOne(x)?.PropertyName ?? x.Name.GetNameWithCase(_jsonSerializerCase)); 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 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 sourceProperties: PopulateObject(sourceProperties); break; // Case 2.1: Source is List, Target is Array case List sourceList when target is Array targetArray: PopulateArray(sourceList, targetArray); break; // Case 2.2: Source is List, Target is IList case List 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(IEnumerable objects, IList list) { var parameterType = GetAddMethodParameterType(_targetType); if (parameterType == null) return; foreach (var item in objects) { try { list.Add(FromJsonResult( item, _jsonSerializerCase, parameterType, _includeNonPublic)); } catch { // ignored } } } private void PopulateArray(IList objects, Array array) { var elementType = _targetType.GetElementType(); for (var i = 0; i < objects.Count; i++) { try { var targetItem = FromJsonResult( objects[i], _jsonSerializerCase, 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.IsEnum) enumType = _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 var addMethod = _targetType.GetMethods() .FirstOrDefault( m => m.Name == 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, _jsonSerializerCase, targetEntryType, _includeNonPublic); targetDictionary.Add(sourceProperty.Key, targetEntryValue); } catch { // ignored } } } private void PopulateObject(IDictionary sourceProperties) { if (_targetType.IsValueType) { PopulateFields(sourceProperties); } PopulateProperties(sourceProperties); } private void PopulateProperties(IDictionary sourceProperties) { var properties = PropertyTypeCache.DefaultCache.Value.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 sourceProperties) { foreach (var field in FieldTypeCache.DefaultCache.Value.RetrieveAllFields(_targetType)) { var sourcePropertyValue = GetSourcePropertyValue(sourceProperties, field); if (sourcePropertyValue == null) continue; var targetPropertyValue = FromJsonResult( sourcePropertyValue, _jsonSerializerCase, field.FieldType, _includeNonPublic); try { field.SetValue(_target, targetPropertyValue); } catch { // ignored } } } } } }