260 lines
10 KiB
C#
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|