-
-
Notifications
You must be signed in to change notification settings - Fork 353
/
JsonSerializer.cs
161 lines (131 loc) · 5.69 KB
/
JsonSerializer.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
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Rebus.Extensions;
using Rebus.Messages;
#pragma warning disable 1998
namespace Rebus.Serialization.Json;
/// <summary>
/// Implementation of <see cref="ISerializer"/> that uses Newtonsoft JSON.NET internally, with some pretty robust settings
/// (i.e. full type info is included in the serialized format in order to support deserializing "unknown" types like
/// implementations of interfaces, etc)
/// </summary>
class JsonSerializer : ISerializer
{
/// <summary>
/// Proper content type when a message has been serialized with this serializer (or another compatible JSON serializer) and it uses the standard UTF8 encoding
/// </summary>
public const string JsonUtf8ContentType = "application/json;charset=utf-8";
/// <summary>
/// Contents type when the content is JSON
/// </summary>
public const string JsonContentType = "application/json";
static readonly JsonSerializerSettings DefaultSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
static readonly Encoding DefaultEncoding = Encoding.UTF8;
readonly JsonSerializerSettings _settings;
readonly Encoding _encoding;
readonly IMessageTypeNameConvention _messageTypeNameConvention;
readonly string _encodingHeaderValue;
public JsonSerializer(IMessageTypeNameConvention messageTypeNameConvention, JsonSerializerSettings jsonSerializerSettings = null, Encoding encoding = null)
{
_messageTypeNameConvention = messageTypeNameConvention ?? throw new ArgumentNullException(nameof(messageTypeNameConvention));
_settings = jsonSerializerSettings ?? DefaultSettings;
_encoding = encoding ?? DefaultEncoding;
_encodingHeaderValue = $"{JsonContentType};charset={_encoding.HeaderName}";
}
/// <summary>
/// Serializes the given <see cref="Message"/> into a <see cref="TransportMessage"/>
/// </summary>
public async Task<TransportMessage> Serialize(Message message)
{
var body = message.Body;
var jsonText = JsonConvert.SerializeObject(body, _settings);
var bytes = _encoding.GetBytes(jsonText);
var headers = message.Headers.Clone();
headers[Headers.ContentType] = _encodingHeaderValue;
if (!headers.ContainsKey(Headers.Type) && body != null)
{
headers[Headers.Type] = _messageTypeNameConvention.GetTypeName(body.GetType());
}
return new TransportMessage(headers, bytes);
}
/// <summary>
/// Deserializes the given <see cref="TransportMessage"/> back into a <see cref="Message"/>
/// </summary>
public async Task<Message> Deserialize(TransportMessage transportMessage)
{
var contentType = transportMessage.Headers.GetValue(Headers.ContentType);
if (string.Equals(contentType, JsonUtf8ContentType, StringComparison.OrdinalIgnoreCase))
{
return GetMessage(transportMessage, DefaultEncoding);
}
if (string.Equals(contentType, _encodingHeaderValue, StringComparison.OrdinalIgnoreCase))
{
return GetMessage(transportMessage, _encoding);
}
if (contentType != null && contentType.StartsWith(JsonContentType))
{
var encoding = GetEncoding(contentType);
return GetMessage(transportMessage, encoding);
}
throw new FormatException($"Unknown content type: '{contentType}' - must be '{JsonContentType}' (e.g. '{JsonUtf8ContentType}') for the JSON serialier to work");
}
Encoding GetEncoding(string contentType)
{
var parts = contentType.Split(';');
var charset = parts
.Select(token => token.Split('='))
.Where(tokens => tokens.Length == 2)
.FirstOrDefault(tokens => tokens[0] == "charset");
if (charset == null)
{
return _encoding;
}
var encodingName = charset[1];
try
{
return Encoding.GetEncoding(encodingName);
}
catch (Exception exception)
{
throw new FormatException($"Could not turn charset '{encodingName}' into proper encoding!", exception);
}
}
Message GetMessage(TransportMessage transportMessage, Encoding bodyEncoding)
{
var bodyString = bodyEncoding.GetString(transportMessage.Body);
var type = GetTypeOrNull(transportMessage);
var bodyObject = Deserialize(bodyString, type);
var headers = transportMessage.Headers.Clone();
return new Message(headers, bodyObject);
}
Type GetTypeOrNull(TransportMessage transportMessage)
{
if (!transportMessage.Headers.TryGetValue(Headers.Type, out var typeName)) return null;
var type = _messageTypeNameConvention.GetType(typeName) ?? throw new FormatException($"Could not get .NET type named '{typeName}'");
return type;
}
object Deserialize(string bodyString, Type type)
{
try
{
return type == null
? JsonConvert.DeserializeObject(bodyString, _settings)
: JsonConvert.DeserializeObject(bodyString, type, _settings);
}
catch (Exception exception)
{
if (bodyString.Length > 32768)
{
throw new FormatException($"Could not deserialize JSON text (original length: {bodyString.Length}): '{Limit(bodyString, 5000)}'", exception);
}
throw new FormatException($"Could not deserialize JSON text: '{bodyString}'", exception);
}
}
static string Limit(string bodyString, int maxLength) => string.Concat(bodyString.Substring(0, maxLength), " (......)");
}