diff --git a/Octokit/Http/SimpleJsonSerializer.cs b/Octokit/Http/SimpleJsonSerializer.cs index fb01c8d8..232167e6 100644 --- a/Octokit/Http/SimpleJsonSerializer.cs +++ b/Octokit/Http/SimpleJsonSerializer.cs @@ -22,6 +22,9 @@ namespace Octokit.Internal class GitHubSerializerStrategy : PocoJsonSerializerStrategy { + readonly List _membersWhichShouldPublishNull + = new List(); + protected override string MapClrMemberNameToJsonFieldName(string clrPropertyName) { var rubyCased = clrPropertyName.ToRubyCase(); @@ -29,6 +32,41 @@ namespace Octokit.Internal return rubyCased; } + internal override IDictionary GetterValueFactory(Type type) + { + var fullName = type.FullName + "-"; + + // sometimes Octokit needs to send a null with the payload so the user + // can unset the value of a property. + // This method uses the same checks as PocoJsonSerializerStrategy + // 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)) + { + if (!propertyInfo.CanRead) + continue; + var getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); + if (getMethod.IsStatic || !getMethod.IsPublic) + continue; + var attribute = propertyInfo.GetCustomAttribute(); + if (attribute == null) + continue; + _membersWhichShouldPublishNull.Add(fullName + MapClrMemberNameToJsonFieldName(propertyInfo.Name)); + } + foreach (var fieldInfo in ReflectionUtils.GetFields(type)) + { + if (fieldInfo.IsStatic || !fieldInfo.IsPublic) + continue; + var attribute = fieldInfo.GetCustomAttribute(); + if (attribute == null) + continue; + _membersWhichShouldPublishNull.Add(fullName + MapClrMemberNameToJsonFieldName(fieldInfo.Name)); + } + + return base.GetterValueFactory(type); + } + // This is overridden so that null values are omitted from serialized objects. [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")] protected override bool TrySerializeUnknownTypes(object input, out object output) @@ -45,20 +83,9 @@ namespace Octokit.Internal var value = getter.Value(input); if (value == null) { - continue; - - // sometimes Octokit needs to send a null - // so look for this attribute when serializing - // XXX: we don't know which property we have at this point - // so this reflection trick doesn't work - - // TODO: make this magic work - //var property = type.GetProperty(getter.Key); - //var attribute = property.GetCustomAttribute(); - //if (attribute == null) - //{ - // continue; - //} + var key = type.FullName + "-" + getter.Key; + if (!_membersWhichShouldPublishNull.Contains(key)) + continue; } jsonObject.Add(MapClrMemberNameToJsonFieldName(getter.Key), value); diff --git a/Octokit/Models/Request/IssueUpdate.cs b/Octokit/Models/Request/IssueUpdate.cs index b1da00a9..86e809a5 100644 --- a/Octokit/Models/Request/IssueUpdate.cs +++ b/Octokit/Models/Request/IssueUpdate.cs @@ -2,6 +2,7 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; +using Octokit.Internal; namespace Octokit { @@ -29,6 +30,7 @@ namespace Octokit /// /// Only users with push access can set the assignee for new issues. The assignee is silently dropped otherwise. /// + [SerializeNull] public string Assignee { get; set; } /// @@ -38,6 +40,7 @@ namespace Octokit /// Only users with push access can set the milestone for new issues. The milestone is silently dropped /// otherwise /// + [SerializeNull] public int? Milestone { get; set; } ///