using System; using System.Collections.Generic; using System.Collections.Concurrent; using System.Linq; using System.Reflection; using Unosquare.Swan.Attributes; namespace Unosquare.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 partial class Json { private class SerializerOptions { private static readonly ConcurrentDictionary, MemberInfo>> TypeCache = new ConcurrentDictionary, MemberInfo>>(); private readonly String[] _includeProperties; private readonly String[] _excludeProperties; private readonly Dictionary> _parentReferences = new Dictionary>(); public SerializerOptions( Boolean format, String typeSpecifier, String[] includeProperties, String[] excludeProperties = null, Boolean includeNonPublic = true, IReadOnlyCollection parentReferences = null) { this._includeProperties = includeProperties; this._excludeProperties = excludeProperties; this.IncludeNonPublic = includeNonPublic; this.Format = format; this.TypeSpecifier = typeSpecifier; if(parentReferences == null) { return; } foreach(WeakReference parentReference in parentReferences.Where(x => x.IsAlive)) { _ = this.IsObjectPresent(parentReference.Target); } } public Boolean Format { get; } public String TypeSpecifier { get; } public Boolean IncludeNonPublic { get; } internal Boolean IsObjectPresent(Object target) { Int32 hashCode = target.GetHashCode(); if(this._parentReferences.ContainsKey(hashCode)) { if(this._parentReferences[hashCode].Any(p => ReferenceEquals(p.Target, target))) { return true; } this._parentReferences[hashCode].Add(new WeakReference(target)); return false; } this._parentReferences.Add(hashCode, new List { new WeakReference(target) }); return false; } internal Dictionary GetProperties(Type targetType) => GetPropertiesCache(targetType) .When(() => this._includeProperties?.Length > 0, query => query.Where(p => this._includeProperties.Contains(p.Key.Item1))) .When(() => this._excludeProperties?.Length > 0, query => query.Where(p => !this._excludeProperties.Contains(p.Key.Item1))) .ToDictionary(x => x.Key.Item2, x => x.Value); private static Dictionary, MemberInfo> GetPropertiesCache(Type targetType) { if(TypeCache.TryGetValue(targetType, out Dictionary, MemberInfo> current)) { return current; } List fields = new List(PropertyTypeCache.RetrieveAllProperties(targetType).Where(p => p.CanRead)); // If the target is a struct (value type) navigate the fields. if(targetType.IsValueType()) { fields.AddRange(FieldTypeCache.RetrieveAllFields(targetType)); } Dictionary, MemberInfo> value = fields .ToDictionary( x => Tuple.Create(x.Name, x.GetCustomAttribute()?.PropertyName ?? x.Name), x => x); _ = TypeCache.TryAdd(targetType, value); return value; } } } }