Add support for deserializing non-public members

But only ones with ParameterAttribute applied.
This commit is contained in:
Haacked
2014-12-30 10:10:32 -08:00
parent fdd9073e43
commit 543d1bb863
6 changed files with 93 additions and 37 deletions

View File

@@ -96,15 +96,21 @@ namespace Octokit.Tests
} }
[Fact] [Fact]
public void UnencodesBase64Strings() public void DeserializesProtectedProperties()
{ {
const string json = "{\"name\":\"RmVycmlzIEJ1ZWxsZXI=\",\"description\":\"stuff\",\"content\":\"RGF5IG9mZg==\"}"; const string json = "{\"content\":\"hello\"}";
var someObject = new SimpleJsonSerializer().Deserialize<SomeObject>(json); var someObject = new SimpleJsonSerializer().Deserialize<AnotherObject>(json);
Assert.Equal("Ferris Bueller", someObject.Name); Assert.Equal("*hello*", someObject.Content);
Assert.Equal("Day off", someObject.Content); }
Assert.Equal("stuff", someObject.Description);
public class AnotherObject
{
[Parameter(Key = "content")]
protected string EncodedContent { get; set; }
public string Content { get { return "*" + EncodedContent + "*"; } }
} }
[Fact] [Fact]

View File

@@ -30,7 +30,6 @@ namespace Octokit
var url = ApiUrls.RepositoryContent(owner, name, path); var url = ApiUrls.RepositoryContent(owner, name, path);
return await ApiConnection.GetAll<RepositoryContent>(url); return await ApiConnection.GetAll<RepositoryContent>(url);
// return new List<RepositoryContent> { await ApiConnection.Get<RepositoryContent>(url) }
} }
/// <summary> /// <summary>

View File

@@ -19,6 +19,8 @@ namespace Octokit
CanWrite = propertyInfo.CanWrite; CanWrite = propertyInfo.CanWrite;
IsStatic = ReflectionUtils.GetGetterMethodInfo(propertyInfo).IsStatic; IsStatic = ReflectionUtils.GetGetterMethodInfo(propertyInfo).IsStatic;
IsPublic = ReflectionUtils.GetGetterMethodInfo(propertyInfo).IsPublic; IsPublic = ReflectionUtils.GetGetterMethodInfo(propertyInfo).IsPublic;
CanDeserialize = (IsPublic || HasParameterAttribute) && !IsStatic && CanWrite;
} }
public PropertyOrField(FieldInfo fieldInfo) : this((MemberInfo)fieldInfo) public PropertyOrField(FieldInfo fieldInfo) : this((MemberInfo)fieldInfo)
@@ -29,6 +31,8 @@ namespace Octokit
CanWrite = true; CanWrite = true;
IsStatic = fieldInfo.IsStatic; IsStatic = fieldInfo.IsStatic;
IsPublic = fieldInfo.IsPublic; IsPublic = fieldInfo.IsPublic;
CanDeserialize = (IsPublic || HasParameterAttribute) && !IsStatic && CanWrite && !fieldInfo.IsInitOnly;
} }
protected PropertyOrField(MemberInfo memberInfo) protected PropertyOrField(MemberInfo memberInfo)
@@ -36,6 +40,7 @@ namespace Octokit
MemberInfo = memberInfo; MemberInfo = memberInfo;
Base64Encoded = memberInfo.GetCustomAttribute<SerializeAsBase64Attribute>() != null; Base64Encoded = memberInfo.GetCustomAttribute<SerializeAsBase64Attribute>() != null;
SerializeNull = memberInfo.GetCustomAttribute<SerializeNullAttribute>() != null; SerializeNull = memberInfo.GetCustomAttribute<SerializeNullAttribute>() != null;
HasParameterAttribute = memberInfo.GetCustomAttribute<ParameterAttribute>() != null;
} }
public bool CanRead { get; private set; } public bool CanRead { get; private set; }
@@ -50,6 +55,8 @@ namespace Octokit
public bool IsPublic { get; private set; } public bool IsPublic { get; private set; }
public bool HasParameterAttribute { get; private set; }
public MemberInfo MemberInfo { get; private set; } public MemberInfo MemberInfo { get; private set; }
public object GetValue(object instance) public object GetValue(object instance)
@@ -75,5 +82,44 @@ namespace Octokit
} }
throw new InvalidOperationException("Property and Field cannot both be null"); throw new InvalidOperationException("Property and Field cannot both be null");
} }
public string JsonFieldName
{
get { return MemberInfo.GetJsonFieldName(); }
}
public ReflectionUtils.SetDelegate SetDelegate
{
get
{
if (_propertyInfo != null)
{
return ReflectionUtils.GetSetMethod(_propertyInfo);
}
if (_fieldInfo != null)
{
return ReflectionUtils.GetSetMethod(_fieldInfo);
}
throw new InvalidOperationException("Property and Field cannot both be null");
}
}
public Type Type
{
get
{
if (_propertyInfo != null)
{
return _propertyInfo.PropertyType;
}
if (_fieldInfo != null)
{
return _fieldInfo.FieldType;
}
throw new InvalidOperationException("Property and Field cannot both be null");
}
}
public bool CanDeserialize { get; set; }
} }
} }

View File

@@ -2,17 +2,31 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Octokit.Internal;
using Octokit.Reflection; using Octokit.Reflection;
namespace Octokit namespace Octokit
{ {
internal static class ReflectionExtensions internal static class ReflectionExtensions
{ {
public static string GetJsonFieldName(this MemberInfo memberInfo)
{
var memberName = memberInfo.Name;
var paramAttr = memberInfo.GetCustomAttribute<ParameterAttribute>();
if (paramAttr != null && !string.IsNullOrEmpty(paramAttr.Key))
{
memberName = paramAttr.Key;
}
return memberName.ToRubyCase();
}
public static IEnumerable<PropertyOrField> GetPropertiesAndFields(this Type type) public static IEnumerable<PropertyOrField> GetPropertiesAndFields(this Type type)
{ {
return ReflectionUtils.GetProperties(type).Select(property => new PropertyOrField(property)) return ReflectionUtils.GetProperties(type).Select(property => new PropertyOrField(property))
.Union(ReflectionUtils.GetFields(type).Select(field => new PropertyOrField(field))) .Union(ReflectionUtils.GetFields(type).Select(field => new PropertyOrField(field)))
.Where(p => p.IsPublic && !p.IsStatic); .Where(p => (p.IsPublic || p.HasParameterAttribute) && !p.IsStatic);
} }
public static bool IsDateTimeOffset(this Type type) public static bool IsDateTimeOffset(this Type type)

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection; using System.Reflection;
using Octokit.Reflection; using Octokit.Reflection;
@@ -27,15 +28,7 @@ namespace Octokit.Internal
protected override string MapClrMemberToJsonFieldName(MemberInfo member) protected override string MapClrMemberToJsonFieldName(MemberInfo member)
{ {
var memberName = member.Name; return member.GetJsonFieldName();
var paramAttr = member.GetCustomAttribute<ParameterAttribute>();
if (paramAttr != null && !string.IsNullOrEmpty(paramAttr.Key))
{
memberName = paramAttr.Key;
}
return memberName.ToRubyCase();
} }
internal override IDictionary<string, ReflectionUtils.GetDelegate> GetterValueFactory(Type type) internal override IDictionary<string, ReflectionUtils.GetDelegate> GetterValueFactory(Type type)
@@ -141,25 +134,16 @@ namespace Octokit.Internal
} }
} }
var deserialized = base.DeserializeObject(value, type); return base.DeserializeObject(value, type);
}
// Handle base64 encoding internal override IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> SetterValueFactory(Type type)
foreach (var propertyInfo in type.GetPropertiesAndFields()) {
{ return type.GetPropertiesAndFields()
if (!propertyInfo.CanRead) continue; .Where(p => p.CanDeserialize)
if (!propertyInfo.CanWrite) continue; .ToDictionary(
if (propertyInfo.Base64Encoded) p => p.JsonFieldName,
{ p => new KeyValuePair<Type, ReflectionUtils.SetDelegate>(p.Type, p.SetDelegate));
var propertyValue = propertyInfo.GetValue(deserialized) as string;
if (propertyValue != null)
{
var unencoded = propertyValue.FromBase64String();
propertyInfo.SetValue(deserialized, unencoded);
}
}
}
return deserialized;
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using Octokit.Internal;
namespace Octokit namespace Octokit
{ {
@@ -16,7 +17,13 @@ namespace Octokit
/// <summary> /// <summary>
/// The encoded content if this is a file. Otherwise it's null. /// The encoded content if this is a file. Otherwise it's null.
/// </summary> /// </summary>
public string Content { get; set; } [Parameter(Key = "content")]
protected string EncodedContent { get; set; }
public string Content
{
get { return EncodedContent.FromBase64String(); }
}
/// <summary> /// <summary>
/// Path to the target file in the repository if this is a symlink. Otherwise it's null. /// Path to the target file in the repository if this is a symlink. Otherwise it's null.