#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