-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
SimpleJsonSerializer.cs
256 lines (223 loc) · 10.4 KB
/
SimpleJsonSerializer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Octokit.Reflection;
namespace Octokit.Internal
{
public class SimpleJsonSerializer : IJsonSerializer
{
static readonly GitHubSerializerStrategy _serializationStrategy = new GitHubSerializerStrategy();
public string Serialize(object item)
{
return SimpleJson.SerializeObject(item, _serializationStrategy);
}
public T Deserialize<T>(string json)
{
return SimpleJson.DeserializeObject<T>(json, _serializationStrategy);
}
internal static string SerializeEnum(Enum value)
{
return _serializationStrategy.SerializeEnumHelper(value).ToString();
}
internal static object DeserializeEnum(string value, Type type)
{
return _serializationStrategy.DeserializeEnumHelper(value, type);
}
class GitHubSerializerStrategy : PocoJsonSerializerStrategy
{
readonly List<string> _membersWhichShouldPublishNull = new List<string>();
ConcurrentDictionary<Type, ConcurrentDictionary<object, object>> _cachedEnums = new ConcurrentDictionary<Type, ConcurrentDictionary<object, object>>();
protected override string MapClrMemberToJsonFieldName(MemberInfo member)
{
return member.GetJsonFieldName();
}
internal override IDictionary<string, ReflectionUtils.GetDelegate> GetterValueFactory(Type type)
{
var propertiesAndFields = type.GetPropertiesAndFields().Where(p => p.CanSerialize).ToList();
foreach (var property in propertiesAndFields.Where(p => p.SerializeNull))
{
var key = type.FullName + "-" + property.JsonFieldName;
_membersWhichShouldPublishNull.Add(key);
}
return propertiesAndFields
.ToDictionary(
p => p.JsonFieldName,
p => p.GetDelegate);
}
// 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)
{
Ensure.ArgumentNotNull(input, nameof(input));
var type = input.GetType();
var getters = GetCache[type];
if (ReflectionUtils.IsStringEnumWrapper(type))
{
// Handle StringEnum<T> by getting the underlying enum value, then using the enum serializer
// Note this will throw if the StringEnum<T> was initialized using a string that is not a valid enum member
var inputEnum = (getters["value"](input) as Enum);
if (inputEnum != null)
{
output = SerializeEnum(inputEnum);
return true;
}
}
var jsonObject = new JsonObject();
foreach (var getter in getters)
{
if (getter.Value != null)
{
var value = getter.Value(input);
if (value == null)
{
var key = type.FullName + "-" + getter.Key;
if (!_membersWhichShouldPublishNull.Contains(key))
continue;
}
jsonObject.Add(getter.Key, value);
}
}
output = jsonObject;
return true;
}
internal object SerializeEnumHelper(Enum p)
{
return SerializeEnum(p);
}
[SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase",
Justification = "The API expects lowercase values")]
protected override object SerializeEnum(Enum p)
{
return p.ToParameter();
}
internal object DeserializeEnumHelper(string value, Type type)
{
var cachedEnumsForType = _cachedEnums.GetOrAdd(type, t =>
{
var enumsForType = new ConcurrentDictionary<object, object>();
// Try to get all custom attributes, this happens only once per type
var fields = type.GetRuntimeFields();
foreach (var field in fields)
{
if (field.Name == "value__")
continue;
var attribute = (ParameterAttribute)field.GetCustomAttribute(typeof(ParameterAttribute));
if (attribute != null)
{
enumsForType.GetOrAdd(attribute.Value, _ => field.GetValue(null));
}
}
return enumsForType;
});
// If type cache does not contain enum value and has no custom attribute, add it for future loops
return cachedEnumsForType.GetOrAdd(value, v => Enum.Parse(type, value, ignoreCase: true));
}
private string _type;
// Overridden to handle enums.
public override object DeserializeObject(object value, Type type)
{
var stringValue = value as string;
var jsonValue = value as JsonObject;
if (stringValue != null)
{
// If it's a nullable type, use the underlying type
if (ReflectionUtils.IsNullableType(type))
{
type = Nullable.GetUnderlyingType(type);
}
var typeInfo = ReflectionUtils.GetTypeInfo(type);
if (typeInfo.IsEnum)
{
return DeserializeEnumHelper(stringValue, type);
}
if (ReflectionUtils.IsTypeGenericeCollectionInterface(type))
{
// OAuth tokens might be a string of comma-separated values
// we should only try this if the return array is a collection of strings
var innerType = ReflectionUtils.GetGenericListElementType(type);
if (innerType.IsAssignableFrom(typeof(string)))
{
return stringValue.Split(',');
}
}
if (ReflectionUtils.IsStringEnumWrapper(type))
{
return Activator.CreateInstance(type, stringValue);
}
}
else if (jsonValue != null)
{
if (type == typeof(Activity))
{
_type = jsonValue["type"].ToString();
}
}
if (type == typeof(ActivityPayload))
{
var payloadType = GetPayloadType(_type);
return base.DeserializeObject(value, payloadType);
}
if (ReflectionUtils.IsStringEnumWrapper(type))
{
// this check is a workaround for https://github.com/octokit/octokit.net/issues/2052
// as the API is returning a null value where the enum is
// expecting something like a string
//
// this should be removed once we can confirm the GitHub API
// is no longer returning a null for the parent Team's
// permission value
return Activator.CreateInstance(type, "null");
}
return base.DeserializeObject(value, type);
}
internal override IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> SetterValueFactory(Type type)
{
return type.GetPropertiesAndFields()
.Where(p => p.CanDeserialize)
.ToDictionary(
p => p.JsonFieldName,
p => new KeyValuePair<Type, ReflectionUtils.SetDelegate>(p.Type, p.SetDelegate));
}
private static Type GetPayloadType(string activityType)
{
switch (activityType)
{
case "CheckRunEvent":
return typeof(CheckRunEventPayload);
case "CheckSuiteEvent":
return typeof(CheckSuiteEventPayload);
case "CommitCommentEvent":
return typeof(CommitCommentPayload);
case "CreateEvent":
return typeof(CreateEventPayload);
case "DeleteEvent":
return typeof(DeleteEventPayload);
case "ForkEvent":
return typeof(ForkEventPayload);
case "IssueCommentEvent":
return typeof(IssueCommentPayload);
case "IssuesEvent":
return typeof(IssueEventPayload);
case "PullRequestEvent":
return typeof(PullRequestEventPayload);
case "PullRequestReviewEvent":
return typeof(PullRequestReviewEventPayload);
case "PullRequestReviewCommentEvent":
return typeof(PullRequestCommentPayload);
case "PushEvent":
return typeof(PushEventPayload);
case "ReleaseEvent":
return typeof(ReleaseEventPayload);
case "StatusEvent":
return typeof(StatusEventPayload);
case "WatchEvent":
return typeof(StarredEventPayload);
}
return typeof(ActivityPayload);
}
}
}
}