diff --git a/Octokit/Helpers/PropertyOrField.cs b/Octokit/Helpers/PropertyOrField.cs new file mode 100644 index 00000000..703b157c --- /dev/null +++ b/Octokit/Helpers/PropertyOrField.cs @@ -0,0 +1,79 @@ +using System; +using System.Reflection; +using Octokit.Helpers; +using Octokit.Internal; +using Octokit.Reflection; + +namespace Octokit +{ + internal class PropertyOrField + { + readonly PropertyInfo _propertyInfo; + readonly FieldInfo _fieldInfo; + + public PropertyOrField(PropertyInfo propertyInfo) : this((MemberInfo)propertyInfo) + { + _propertyInfo = propertyInfo; + + CanRead = propertyInfo.CanRead; + CanWrite = propertyInfo.CanWrite; + IsStatic = ReflectionUtils.GetGetterMethodInfo(propertyInfo).IsStatic; + IsPublic = ReflectionUtils.GetGetterMethodInfo(propertyInfo).IsPublic; + } + + public PropertyOrField(FieldInfo fieldInfo) : this((MemberInfo)fieldInfo) + { + _fieldInfo = fieldInfo; + + CanRead = true; + CanWrite = true; + IsStatic = fieldInfo.IsStatic; + IsPublic = fieldInfo.IsPublic; + } + + protected PropertyOrField(MemberInfo memberInfo) + { + MemberInfo = memberInfo; + Base64Encoded = memberInfo.GetCustomAttribute() != null; + SerializeNull = memberInfo.GetCustomAttribute() != null; + } + + public bool CanRead { get; private set; } + + public bool CanWrite { get; private set; } + + public bool Base64Encoded { get; private set; } + + public bool SerializeNull { get; private set; } + + public bool IsStatic { get; private set; } + + public bool IsPublic { get; private set; } + + public MemberInfo MemberInfo { get; private set; } + + public object GetValue(object instance) + { + if (_propertyInfo != null) + return _propertyInfo.GetValue(instance); + if (_fieldInfo != null) + return _fieldInfo.GetValue(instance); + throw new InvalidOperationException("Property and Field cannot both be null"); + } + + public void SetValue(object instance, object value) + { + if (_propertyInfo != null) + { + _propertyInfo.SetValue(instance, value); + return; + } + if (_fieldInfo != null) + { + _fieldInfo.SetValue(instance, value); + return; + } + throw new InvalidOperationException("Property and Field cannot both be null"); + } + } +} diff --git a/Octokit/Helpers/ReflectionExtensions.cs b/Octokit/Helpers/ReflectionExtensions.cs index 0e35ce7e..945a53a0 100644 --- a/Octokit/Helpers/ReflectionExtensions.cs +++ b/Octokit/Helpers/ReflectionExtensions.cs @@ -1,13 +1,20 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Octokit.Reflection; namespace Octokit { internal static class ReflectionExtensions { + public static IEnumerable GetPropertiesAndFields(this Type type) + { + return ReflectionUtils.GetProperties(type).Select(property => new PropertyOrField(property)) + .Union(ReflectionUtils.GetFields(type).Select(field => new PropertyOrField(field))) + .Where(p => p.IsPublic && !p.IsStatic); + } + public static bool IsDateTimeOffset(this Type type) { return type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?); diff --git a/Octokit/Helpers/StringExtensions.cs b/Octokit/Helpers/StringExtensions.cs index cc8f1705..333884d7 100644 --- a/Octokit/Helpers/StringExtensions.cs +++ b/Octokit/Helpers/StringExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; +using System.Text; using System.Text.RegularExpressions; namespace Octokit @@ -31,6 +32,17 @@ namespace Octokit return System.Net.WebUtility.UrlEncode(input); } + public static string ToBase64String(this string input) + { + return Convert.ToBase64String(Encoding.UTF8.GetBytes(input)); + } + + public static string FromBase64String(this string encoded) + { + var decodedBytes = Convert.FromBase64String(encoded); + return Encoding.UTF8.GetString(decodedBytes, 0, decodedBytes.Length); + } + static readonly Regex _optionalQueryStringRegex = new Regex("\\{\\?([^}]+)\\}"); public static Uri ExpandUriTemplate(this string template, object values) { diff --git a/Octokit/Http/SimpleJsonSerializer.cs b/Octokit/Http/SimpleJsonSerializer.cs index 94b11a66..1cd3e7c0 100644 --- a/Octokit/Http/SimpleJsonSerializer.cs +++ b/Octokit/Http/SimpleJsonSerializer.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Text; -using Octokit.Helpers; using Octokit.Reflection; namespace Octokit.Internal @@ -24,8 +22,8 @@ namespace Octokit.Internal class GitHubSerializerStrategy : PocoJsonSerializerStrategy { - readonly List membersWhichShouldPublishNull = new List(); - readonly List membersWhichShouldBeBase64Encoded = new List(); + readonly List _membersWhichShouldPublishNull = new List(); + readonly List _membersWhichShouldBeBase64Encoded = new List(); protected override string MapClrMemberToJsonFieldName(MemberInfo member) { @@ -50,37 +48,17 @@ namespace Octokit.Internal // to identify the right fields and properties to serialize // but it then filters on the presence of SerializeNullAttribute. - foreach (var propertyInfo in ReflectionUtils.GetProperties(type)) + foreach (var propertyOrField in type.GetPropertiesAndFields()) { - if (!propertyInfo.CanRead) + if (!propertyOrField.CanRead) continue; - var getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); - if (getMethod.IsStatic || !getMethod.IsPublic) - continue; - var base64Attribute = propertyInfo.GetCustomAttribute(); - if (base64Attribute != null) + if (propertyOrField.Base64Encoded) { - membersWhichShouldBeBase64Encoded.Add(fullName + MapClrMemberToJsonFieldName(propertyInfo)); + _membersWhichShouldBeBase64Encoded.Add(fullName + MapClrMemberToJsonFieldName(propertyOrField.MemberInfo)); } - var serializeNullAttribute = propertyInfo.GetCustomAttribute(); - if (serializeNullAttribute == null) + if (!propertyOrField.SerializeNull) continue; - membersWhichShouldPublishNull.Add(fullName + MapClrMemberToJsonFieldName(propertyInfo)); - } - - foreach (var fieldInfo in ReflectionUtils.GetFields(type)) - { - if (fieldInfo.IsStatic || !fieldInfo.IsPublic) - continue; - var base64Attribute = fieldInfo.GetCustomAttribute(); - if (base64Attribute != null) - { - membersWhichShouldBeBase64Encoded.Add(fullName + MapClrMemberToJsonFieldName(fieldInfo)); - } - var attribute = fieldInfo.GetCustomAttribute(); - if (attribute == null) - continue; - membersWhichShouldPublishNull.Add(fullName + MapClrMemberToJsonFieldName(fieldInfo)); + _membersWhichShouldPublishNull.Add(fullName + MapClrMemberToJsonFieldName(propertyOrField.MemberInfo)); } return base.GetterValueFactory(type); @@ -103,16 +81,16 @@ namespace Octokit.Internal if (value == null) { var key = type.FullName + "-" + getter.Key; - if (!membersWhichShouldPublishNull.Contains(key)) + if (!_membersWhichShouldPublishNull.Contains(key)) continue; } else { var key = type.FullName + "-" + getter.Key; - if (membersWhichShouldBeBase64Encoded.Contains(key)) + if (_membersWhichShouldBeBase64Encoded.Contains(key)) { var stringValue = value as string ?? ""; - value = Convert.ToBase64String(Encoding.UTF8.GetBytes(stringValue)); + value = stringValue.ToBase64String(); } } jsonObject.Add(getter.Key, value); @@ -166,34 +144,21 @@ namespace Octokit.Internal var deserialized = base.DeserializeObject(value, type); // Handle base64 encoding - foreach (var propertyInfo in ReflectionUtils.GetProperties(type)) + foreach (var propertyInfo in type.GetPropertiesAndFields()) { if (!propertyInfo.CanRead) continue; if (!propertyInfo.CanWrite) continue; - if (propertyInfo.GetCustomAttribute() != null) + if (propertyInfo.Base64Encoded) { var propertyValue = propertyInfo.GetValue(deserialized) as string; if (propertyValue != null) { - var unencoded = Encoding.UTF8.GetString(Convert.FromBase64String(propertyValue)); + var unencoded = propertyValue.FromBase64String(); propertyInfo.SetValue(deserialized, unencoded); } } } - foreach (var fieldInfo in ReflectionUtils.GetFields(type)) - { - if (fieldInfo.GetCustomAttribute() != null) - { - var propertyValue = fieldInfo.GetValue(deserialized) as string; - if (propertyValue != null) - { - var unencoded = Encoding.UTF8.GetString(Convert.FromBase64String(propertyValue)); - fieldInfo.SetValue(deserialized, unencoded); - } - } - } - return deserialized; } } diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj index 4cd84b5d..47a8a758 100644 --- a/Octokit/Octokit-Mono.csproj +++ b/Octokit/Octokit-Mono.csproj @@ -356,6 +356,7 @@ + \ No newline at end of file diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj index c9fb49cb..359f7619 100644 --- a/Octokit/Octokit-MonoAndroid.csproj +++ b/Octokit/Octokit-MonoAndroid.csproj @@ -368,6 +368,7 @@ + \ No newline at end of file diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj index 1c48d90f..e114b1c8 100644 --- a/Octokit/Octokit-Monotouch.csproj +++ b/Octokit/Octokit-Monotouch.csproj @@ -363,6 +363,7 @@ + diff --git a/Octokit/Octokit-Portable.csproj b/Octokit/Octokit-Portable.csproj index 07419f45..3c6615ed 100644 --- a/Octokit/Octokit-Portable.csproj +++ b/Octokit/Octokit-Portable.csproj @@ -354,6 +354,7 @@ + diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj index 50f4c6a4..d9350893 100644 --- a/Octokit/Octokit-netcore45.csproj +++ b/Octokit/Octokit-netcore45.csproj @@ -358,6 +358,7 @@ + diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index 96fa68a9..192848c8 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -75,6 +75,7 @@ +