#nullable enable 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 Boolean _includeNonPublic; private readonly JsonSerializerCase _jsonSerializerCase; private Converter(Object? source, Type targetType, ref Object? targetInstance, Boolean includeNonPublic, JsonSerializerCase jsonSerializerCase) { this._targetType = targetInstance != null ? targetInstance.GetType() : targetType; this._includeNonPublic = includeNonPublic; this._jsonSerializerCase = jsonSerializerCase; 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); } internal static Object? FromJsonResult(Object? source, JsonSerializerCase jsonSerializerCase, Type? targetType = null, Boolean 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, Boolean includeNonPublic) => 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) { String targetPropertyName = MemberInfoNameCache.GetOrAdd(targetProperty, x => AttributeCache.DefaultCache.Value.RetrieveOne(x)?.PropertyName ?? x.Name.GetNameWithCase(this._jsonSerializerCase)); 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(IEnumerable objects, IList list) { Type? parameterType = GetAddMethodParameterType(this._targetType); if(parameterType == null) { return; } foreach(Object item in objects) { try { _ = list.Add(FromJsonResult(item, this._jsonSerializerCase, 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], this._jsonSerializerCase, 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.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 == AddMethodName && m.IsPublic && m.GetParameters().Length == 2); // skip if we don't have a compatible add method if(addMethod == null) { return; } global::System.Reflection.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, this._jsonSerializerCase, 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) { global::System.Collections.Generic.IEnumerable properties = PropertyTypeCache.DefaultCache.Value.RetrieveFilteredProperties(this._targetType, false, p => p.CanWrite); foreach(PropertyInfo property in properties) { Object sourcePropertyValue = this.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.DefaultCache.Value.RetrieveAllFields(this._targetType)) { Object sourcePropertyValue = this.GetSourcePropertyValue(sourceProperties, field); if(sourcePropertyValue == null) { continue; } Object? targetPropertyValue = FromJsonResult(sourcePropertyValue, this._jsonSerializerCase, field.FieldType, this._includeNonPublic); try { field.SetValue(this._target, targetPropertyValue); } catch { // ignored } } } } } }