RaspberryIO_26/Swan.Tiny/Formatters/Json.Converter.cs

260 lines
10 KiB
C#

#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 {
/// <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, global::System.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 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<String, Object> sourceProperties, MemberInfo targetProperty) {
String targetPropertyName = MemberInfoNameCache.GetOrAdd(targetProperty, x => AttributeCache.DefaultCache.Value.RetrieveOne<JsonPropertyAttribute>(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<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(IEnumerable<Object> 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<Object> 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<String, Object> 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<String, Object> 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<String, Object> sourceProperties) {
if(this._targetType.IsValueType) {
this.PopulateFields(sourceProperties);
}
this.PopulateProperties(sourceProperties);
}
private void PopulateProperties(IDictionary<String, Object> sourceProperties) {
global::System.Collections.Generic.IEnumerable<global::System.Reflection.PropertyInfo> 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<String, Object> 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
}
}
}
}
}
}