-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
SerializationHelper.cs
273 lines (239 loc) · 12.6 KB
/
SerializationHelper.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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
// This file is part of Hangfire. Copyright © 2019 Hangfire OÜ.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading;
using Hangfire.Annotations;
using Newtonsoft.Json;
namespace Hangfire.Common
{
public enum SerializationOption
{
/// <summary>
/// For internal data using isolated settings that can't be changed from user code.
/// </summary>
Internal,
/// <summary>
/// For internal data using isolated settings with types information (<see cref="TypeNameHandling.Objects"/> setting)
/// that can't be changed from user code.
/// </summary>
TypedInternal,
/// <summary>
/// For user data like arguments and parameters, configurable via <see cref="SerializationHelper.SetUserSerializerSettings"/>.
/// </summary>
User
}
/// <summary>
/// Provides methods to serialize/deserialize data with Hangfire default settings.
/// Isolates internal serialization process from user interference including `JsonConvert.DefaultSettings` modification.
/// </summary>
public static class SerializationHelper
{
private static readonly Lazy<JsonSerializerSettings> InternalSerializerSettings =
new Lazy<JsonSerializerSettings>(GetInternalSettings, LazyThreadSafetyMode.PublicationOnly);
private static JsonSerializerSettings _userSerializerSettings;
/// <summary>
/// Serializes data with <see cref="SerializationOption.Internal"/> option.
/// Use this method to serialize internal data. Using isolated settings that can't be changed from user code.
/// </summary>
public static string Serialize<T>([CanBeNull] T value)
{
return Serialize(value, SerializationOption.Internal);
}
/// <summary>
/// Serializes data with specified option.
/// Use <see cref="SerializationOption.Internal"/> option to serialize internal data.
/// Use <see cref="SerializationOption.TypedInternal"/> option if you need to store type information.
/// Use <see cref="SerializationOption.User"/> option to serialize user data like arguments and parameters,
/// configurable via <see cref="SetUserSerializerSettings"/>.
/// </summary>
public static string Serialize<T>([CanBeNull] T value, SerializationOption option)
{
return Serialize(value, typeof(T), option);
}
/// <summary>
/// Serializes data with specified option.
/// Use <see cref="SerializationOption.Internal"/> option to serialize internal data.
/// Use <see cref="SerializationOption.TypedInternal"/> option if you need to store type information.
/// Use <see cref="SerializationOption.User"/> option to serialize user data like arguments and parameters,
/// configurable via <see cref="SetUserSerializerSettings"/>.
/// </summary>
public static string Serialize([CanBeNull] object value, [CanBeNull] Type type, SerializationOption option)
{
if (value == null) return null;
if (GlobalConfiguration.HasCompatibilityLevel(CompatibilityLevel.Version_170))
{
var serializerSettings = GetSerializerSettings(option);
if (option == SerializationOption.User)
{
var formatting = serializerSettings?.Formatting ?? Formatting.None;
return JsonConvert.SerializeObject(value, type, formatting, serializerSettings);
}
// For internal purposes we should ensure that JsonConvert.DefaultSettings don't affect
// the serialization process, and the only way is to create a custom serializer.
using (var stringWriter = new StringWriter(new StringBuilder(256), CultureInfo.InvariantCulture))
using (var jsonWriter = new JsonTextWriter(stringWriter))
{
var serializer = JsonSerializer.Create(serializerSettings);
serializer.Serialize(jsonWriter, value, type);
return stringWriter.ToString();
}
}
else
{
// Previously almost all the data was serialized with the user settings, except
// when we explicitly needed to persist the type information. In the latter case
// custom settings passed to serializer, identical to TypedInternal.
var serializerSettings = option == SerializationOption.TypedInternal
? GetLegacyTypedSerializerSettings()
: GetUserSerializerSettings();
// JsonConvert is used here, because previously global default settings affected
// the serialization process.
var formatting = serializerSettings?.Formatting ?? Formatting.None;
return JsonConvert.SerializeObject(value, type, formatting, serializerSettings);
}
}
/// <summary>
/// Deserializes data with <see cref="SerializationOption.Internal"/> option.
/// Use this method to deserialize internal data. Using isolated settings that can't be changed from user code.
/// </summary>
public static object Deserialize([CanBeNull] string value, [NotNull] Type type)
{
return Deserialize(value, type, SerializationOption.Internal);
}
/// <summary>
/// Deserializes data with specified option.
/// Use <see cref="SerializationOption.Internal"/> to deserialize internal data.
/// Use <see cref="SerializationOption.TypedInternal"/> if deserializable internal data has type names information.
/// Use <see cref="SerializationOption.User"/> to deserialize user data like arguments and parameters,
/// configurable via <see cref="SetUserSerializerSettings"/>.
/// </summary>
public static object Deserialize([CanBeNull] string value, [NotNull] Type type, SerializationOption option)
{
if (type == null) throw new ArgumentNullException(nameof(type));
if (value == null) return null;
Exception exception = null;
if (option != SerializationOption.User)
{
var serializerSettings = GetSerializerSettings(option);
try
{
// For internal purposes we should ensure that JsonConvert.DefaultSettings don't affect
// the deserialization process, and the only way is to create a custom serializer.
using (var stringReader = new StringReader(value))
using (var jsonReader = new JsonTextReader(stringReader))
{
var serializer = JsonSerializer.Create(serializerSettings);
return serializer.Deserialize(jsonReader, type);
}
}
catch (Exception ex) when (ex.IsCatchableExceptionType())
{
// If there was an exception, we should try to deserialize the value using user-based
// settings, because prior to 1.7.0 they were used for almost everything. So we are saving
// the exception to re-throw it if even serializer based on user settings couldn't handle
// our value. In that case an original exception should be thrown as it is the reason.
exception = ex;
}
}
try
{
return JsonConvert.DeserializeObject(value, type, GetSerializerSettings(SerializationOption.User));
}
catch (Exception ex) when (exception != null && ex.IsCatchableExceptionType())
{
ExceptionDispatchInfo.Capture(exception).Throw();
throw;
}
}
/// <summary>
/// Deserializes data with <see cref="SerializationOption.Internal"/> option.
/// Use this method to deserialize internal data. Using isolated settings that can't be changed from user code.
/// </summary>
public static T Deserialize<T>([CanBeNull] string value)
{
if (value == null) return default(T);
return Deserialize<T>(value, SerializationOption.Internal);
}
/// <summary>
/// Deserializes data with specified option.
/// Use <see cref="SerializationOption.Internal"/> to deserialize internal data.
/// Use <see cref="SerializationOption.TypedInternal"/> if deserializable internal data has type names information.
/// Use <see cref="SerializationOption.User"/> to deserialize user data like arguments and parameters,
/// configurable via <see cref="SetUserSerializerSettings"/>.
/// </summary>
public static T Deserialize<T>([CanBeNull] string value, SerializationOption option)
{
if (value == null) return default(T);
return (T) Deserialize(value, typeof(T), option);
}
internal static JsonSerializerSettings GetInternalSettings()
{
var serializerSettings = new JsonSerializerSettings();
SetSimpleTypeNameAssemblyFormat(serializerSettings);
serializerSettings.TypeNameHandling = TypeNameHandling.Auto;
serializerSettings.DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate;
serializerSettings.NullValueHandling = NullValueHandling.Ignore;
serializerSettings.CheckAdditionalContent = true; // Default option in JsonConvert.Deserialize method
serializerSettings.MaxDepth = 128;
#if NETSTANDARD2_0
serializerSettings.SerializationBinder = new TypeHelperSerializationBinder();
#else
serializerSettings.Binder = new TypeHelperSerializationBinder();
#endif
return serializerSettings;
}
internal static void SetUserSerializerSettings([CanBeNull] JsonSerializerSettings settings)
{
Volatile.Write(ref _userSerializerSettings, settings);
}
private static JsonSerializerSettings GetLegacyTypedSerializerSettings()
{
var serializerSettings = new JsonSerializerSettings();
serializerSettings.TypeNameHandling = TypeNameHandling.Objects;
serializerSettings.MaxDepth = 128;
SetSimpleTypeNameAssemblyFormat(serializerSettings);
return serializerSettings;
}
private static void SetSimpleTypeNameAssemblyFormat(JsonSerializerSettings serializerSettings)
{
// Setting TypeNameAssemblyFormatHandling to Simple. Using reflection, because latest versions
// of Newtonsoft.Json contain breaking changes.
var typeNameAssemblyFormatHandling =
typeof(JsonSerializerSettings).GetRuntimeProperty("TypeNameAssemblyFormatHandling");
var typeNameAssemblyFormat = typeof(JsonSerializerSettings).GetRuntimeProperty("TypeNameAssemblyFormat");
var property = typeNameAssemblyFormatHandling ?? typeNameAssemblyFormat;
property.SetValue(serializerSettings, Enum.Parse(property.PropertyType, "Simple"));
}
private static JsonSerializerSettings GetSerializerSettings(SerializationOption serializationOption)
{
switch (serializationOption)
{
case SerializationOption.Internal:
case SerializationOption.TypedInternal: return InternalSerializerSettings.Value;
case SerializationOption.User: return GetUserSerializerSettings();
default: throw new ArgumentOutOfRangeException(nameof(serializationOption), serializationOption, null);
}
}
private static JsonSerializerSettings GetUserSerializerSettings()
{
return Volatile.Read(ref _userSerializerSettings);
}
}
}