Add attribute to serialize strings as base64 encoded

This commit is contained in:
Haacked
2014-12-29 22:51:16 -08:00
parent 99b93c1293
commit 83bc99256a
9 changed files with 119 additions and 12 deletions

View File

@@ -1,4 +1,4 @@
using System; using Octokit.Helpers;
using Octokit.Internal; using Octokit.Internal;
using Xunit; using Xunit;
@@ -63,6 +63,21 @@ namespace Octokit.Tests
Assert.Equal("{\"int\":42,\"bool\":true}", json); Assert.Equal("{\"int\":42,\"bool\":true}", json);
} }
[Fact]
public void HandlesBase64EncodedStrings()
{
var item = new SomeObject
{
Name = "Ferris Bueller",
Content = "Day off",
Description = "stuff"
};
var json = new SimpleJsonSerializer().Serialize(item);
Assert.Equal("{\"name\":\"RmVycmlzIEJ1ZWxsZXI=\",\"description\":\"stuff\",\"content\":\"RGF5IG9mZg==\"}", json);
}
} }
public class TheDeserializeMethod public class TheDeserializeMethod
@@ -80,6 +95,18 @@ namespace Octokit.Tests
Assert.True(sample.Private); Assert.True(sample.Private);
} }
[Fact]
public void UnencodesBase64Strings()
{
const string json = "{\"name\":\"RmVycmlzIEJ1ZWxsZXI=\",\"description\":\"stuff\",\"content\":\"RGF5IG9mZg==\"}";
var someObject = new SimpleJsonSerializer().Deserialize<SomeObject>(json);
Assert.Equal("Ferris Bueller", someObject.Name);
Assert.Equal("Day off", someObject.Content);
Assert.Equal("stuff", someObject.Description);
}
[Fact] [Fact]
public void CanDeserializeOrganization() public void CanDeserializeOrganization()
{ {
@@ -154,5 +181,16 @@ namespace Octokit.Tests
[Parameter(Key = "_links")] [Parameter(Key = "_links")]
public string Links { get; set; } public string Links { get; set; }
} }
public class SomeObject
{
[SerializeAsBase64]
public string Name { get; set; }
[SerializeAsBase64]
public string Content;
public string Description { get; set; }
}
} }
} }

View File

@@ -0,0 +1,12 @@
using System;
namespace Octokit.Helpers
{
/// <summary>
/// Attribute used to denote that a string property should be serialized as a base64 encoded string.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class SerializeAsBase64Attribute : Attribute
{
}
}

View File

@@ -1,8 +1,9 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection; using System.Reflection;
using System.Text;
using Octokit.Helpers;
using Octokit.Reflection; using Octokit.Reflection;
namespace Octokit.Internal namespace Octokit.Internal
@@ -23,8 +24,8 @@ namespace Octokit.Internal
class GitHubSerializerStrategy : PocoJsonSerializerStrategy class GitHubSerializerStrategy : PocoJsonSerializerStrategy
{ {
readonly List<string> _membersWhichShouldPublishNull readonly List<string> membersWhichShouldPublishNull = new List<string>();
= new List<string>(); readonly List<string> membersWhichShouldBeBase64Encoded = new List<string>();
protected override string MapClrMemberToJsonFieldName(MemberInfo member) protected override string MapClrMemberToJsonFieldName(MemberInfo member)
{ {
@@ -56,19 +57,30 @@ namespace Octokit.Internal
var getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); var getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo);
if (getMethod.IsStatic || !getMethod.IsPublic) if (getMethod.IsStatic || !getMethod.IsPublic)
continue; continue;
var attribute = propertyInfo.GetCustomAttribute<SerializeNullAttribute>(); var base64Attribute = propertyInfo.GetCustomAttribute<SerializeAsBase64Attribute>();
if (attribute == null) if (base64Attribute != null)
{
membersWhichShouldBeBase64Encoded.Add(fullName + MapClrMemberToJsonFieldName(propertyInfo));
}
var serializeNullAttribute = propertyInfo.GetCustomAttribute<SerializeNullAttribute>();
if (serializeNullAttribute == null)
continue; continue;
_membersWhichShouldPublishNull.Add(fullName + MapClrMemberToJsonFieldName(propertyInfo)); membersWhichShouldPublishNull.Add(fullName + MapClrMemberToJsonFieldName(propertyInfo));
} }
foreach (var fieldInfo in ReflectionUtils.GetFields(type)) foreach (var fieldInfo in ReflectionUtils.GetFields(type))
{ {
if (fieldInfo.IsStatic || !fieldInfo.IsPublic) if (fieldInfo.IsStatic || !fieldInfo.IsPublic)
continue; continue;
var base64Attribute = fieldInfo.GetCustomAttribute<SerializeAsBase64Attribute>();
if (base64Attribute != null)
{
membersWhichShouldBeBase64Encoded.Add(fullName + MapClrMemberToJsonFieldName(fieldInfo));
}
var attribute = fieldInfo.GetCustomAttribute<SerializeNullAttribute>(); var attribute = fieldInfo.GetCustomAttribute<SerializeNullAttribute>();
if (attribute == null) if (attribute == null)
continue; continue;
_membersWhichShouldPublishNull.Add(fullName + MapClrMemberToJsonFieldName(fieldInfo)); membersWhichShouldPublishNull.Add(fullName + MapClrMemberToJsonFieldName(fieldInfo));
} }
return base.GetterValueFactory(type); return base.GetterValueFactory(type);
@@ -91,10 +103,18 @@ namespace Octokit.Internal
if (value == null) if (value == null)
{ {
var key = type.FullName + "-" + getter.Key; var key = type.FullName + "-" + getter.Key;
if (!_membersWhichShouldPublishNull.Contains(key)) if (!membersWhichShouldPublishNull.Contains(key))
continue; continue;
} }
else
{
var key = type.FullName + "-" + getter.Key;
if (membersWhichShouldBeBase64Encoded.Contains(key))
{
var stringValue = value as string ?? "";
value = Convert.ToBase64String(Encoding.UTF8.GetBytes(stringValue));
}
}
jsonObject.Add(getter.Key, value); jsonObject.Add(getter.Key, value);
} }
} }
@@ -143,7 +163,38 @@ namespace Octokit.Internal
} }
} }
return base.DeserializeObject(value, type); var deserialized = base.DeserializeObject(value, type);
// Handle base64 encoding
foreach (var propertyInfo in ReflectionUtils.GetProperties(type))
{
if (!propertyInfo.CanRead) continue;
if (!propertyInfo.CanWrite) continue;
if (propertyInfo.GetCustomAttribute<SerializeAsBase64Attribute>() != null)
{
var propertyValue = propertyInfo.GetValue(deserialized) as string;
if (propertyValue != null)
{
var unencoded = Encoding.UTF8.GetString(Convert.FromBase64String(propertyValue));
propertyInfo.SetValue(deserialized, unencoded);
}
}
}
foreach (var fieldInfo in ReflectionUtils.GetFields(type))
{
if (fieldInfo.GetCustomAttribute<SerializeAsBase64Attribute>() != 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;
} }
} }
} }

View File

@@ -352,6 +352,7 @@
<Compile Include="Models\Response\DirectoryContent.cs" /> <Compile Include="Models\Response\DirectoryContent.cs" />
<Compile Include="Models\Request\CreateFileRequest.cs" /> <Compile Include="Models\Request\CreateFileRequest.cs" />
<Compile Include="Models\Request\Signature.cs" /> <Compile Include="Models\Request\Signature.cs" />
<Compile Include="Helpers\SerializeAsBase64Attribute.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View File

@@ -362,6 +362,7 @@
<Compile Include="Models\Response\DirectoryContent.cs" /> <Compile Include="Models\Response\DirectoryContent.cs" />
<Compile Include="Models\Request\CreateFileRequest.cs" /> <Compile Include="Models\Request\CreateFileRequest.cs" />
<Compile Include="Models\Request\Signature.cs" /> <Compile Include="Models\Request\Signature.cs" />
<Compile Include="Helpers\SerializeAsBase64Attribute.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" />
</Project> </Project>

View File

@@ -357,6 +357,7 @@
<Compile Include="Models\Response\DirectoryContent.cs" /> <Compile Include="Models\Response\DirectoryContent.cs" />
<Compile Include="Models\Request\CreateFileRequest.cs" /> <Compile Include="Models\Request\CreateFileRequest.cs" />
<Compile Include="Models\Request\Signature.cs" /> <Compile Include="Models\Request\Signature.cs" />
<Compile Include="Helpers\SerializeAsBase64Attribute.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.MonoTouch.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.MonoTouch.CSharp.targets" />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />

View File

@@ -350,6 +350,7 @@
<Compile Include="Models\Response\DirectoryContent.cs" /> <Compile Include="Models\Response\DirectoryContent.cs" />
<Compile Include="Models\Request\CreateFileRequest.cs" /> <Compile Include="Models\Request\CreateFileRequest.cs" />
<Compile Include="Models\Request\Signature.cs" /> <Compile Include="Models\Request\Signature.cs" />
<Compile Include="Helpers\SerializeAsBase64Attribute.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<CodeAnalysisDictionary Include="..\CustomDictionary.xml"> <CodeAnalysisDictionary Include="..\CustomDictionary.xml">

View File

@@ -354,6 +354,7 @@
<Compile Include="Models\Response\DirectoryContent.cs" /> <Compile Include="Models\Response\DirectoryContent.cs" />
<Compile Include="Models\Request\CreateFileRequest.cs" /> <Compile Include="Models\Request\CreateFileRequest.cs" />
<Compile Include="Models\Request\Signature.cs" /> <Compile Include="Models\Request\Signature.cs" />
<Compile Include="Helpers\SerializeAsBase64Attribute.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<CodeAnalysisDictionary Include="..\CustomDictionary.xml"> <CodeAnalysisDictionary Include="..\CustomDictionary.xml">

View File

@@ -365,6 +365,7 @@
<Compile Include="Models\Request\UserUpdate.cs" /> <Compile Include="Models\Request\UserUpdate.cs" />
<Compile Include="Helpers\StringExtensions.cs" /> <Compile Include="Helpers\StringExtensions.cs" />
<Compile Include="Clients\RepositoriesClient.cs" /> <Compile Include="Clients\RepositoriesClient.cs" />
<Compile Include="Helpers\SerializeAsBase64Attribute.cs" />
<Compile Include="SimpleJson.cs" /> <Compile Include="SimpleJson.cs" />
<Compile Include="Models\Request\NewRepository.cs" /> <Compile Include="Models\Request\NewRepository.cs" />
<Compile Include="Clients\UsersClient.cs" /> <Compile Include="Clients\UsersClient.cs" />