Skip to content

Commit d76ab63

Browse files
authored
Manual serialization (#623)
1 parent fa3bc2d commit d76ab63

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2398
-801
lines changed

src/Sentry/Extensibility/SentryStackTraceFactory.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ internal IEnumerable<SentryStackFrame> CreateFrames(StackTrace stackTrace, bool
9191
var firstFrame = true;
9292
foreach (var stackFrame in frames)
9393
{
94+
if (stackFrame is null)
95+
{
96+
continue;
97+
}
98+
9499
// Remove the frames until the call for capture with the SDK
95100
if (firstFrame
96101
&& isCurrentStackTrace
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
3+
namespace Sentry.Internal.Extensions
4+
{
5+
internal static class EnumExtensions
6+
{
7+
public static T ParseEnum<T>(this string str) where T : struct, Enum =>
8+
(T)Enum.Parse(typeof(T), str, true);
9+
}
10+
}
Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
using System.Net.Http;
2+
using System.Text.Json;
3+
using System.Threading;
24
using System.Threading.Tasks;
3-
using Newtonsoft.Json.Linq;
45

56
namespace Sentry.Internal.Extensions
67
{
78
internal static class HttpClientExtensions
89
{
9-
public static async Task<JToken> ReadAsJsonAsync(this HttpContent content)
10+
public static async Task<JsonElement> ReadAsJsonAsync(
11+
this HttpContent content,
12+
CancellationToken cancellationToken = default)
1013
{
11-
var raw = await content.ReadAsStringAsync().ConfigureAwait(false);
12-
return JToken.Parse(raw);
14+
using var stream = await content.ReadAsStreamAsync().ConfigureAwait(false);
15+
using var jsonDocument = await JsonDocument.ParseAsync(stream, default, cancellationToken).ConfigureAwait(false);
16+
17+
return jsonDocument.RootElement.Clone();
1318
}
1419
}
1520
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text.Json;
5+
using Sentry.Protocol;
6+
7+
namespace Sentry.Internal.Extensions
8+
{
9+
// TODO: refactor this mess
10+
internal static class JsonExtensions
11+
{
12+
public static void Deconstruct(this JsonProperty jsonProperty, out string name, out JsonElement value)
13+
{
14+
name = jsonProperty.Name;
15+
value = jsonProperty.Value;
16+
}
17+
18+
public static void WriteDictionaryValue(
19+
this Utf8JsonWriter writer,
20+
IEnumerable<KeyValuePair<string, object?>>? dic)
21+
{
22+
if (dic != null)
23+
{
24+
writer.WriteStartObject();
25+
26+
foreach (var (key, value) in dic)
27+
{
28+
writer.WriteDynamic(key, value);
29+
}
30+
31+
writer.WriteEndObject();
32+
}
33+
else
34+
{
35+
writer.WriteNullValue();
36+
}
37+
}
38+
39+
public static void WriteDictionaryValue(
40+
this Utf8JsonWriter writer,
41+
IEnumerable<KeyValuePair<string, string?>>? dic)
42+
{
43+
if (dic != null)
44+
{
45+
writer.WriteStartObject();
46+
47+
foreach (var (key, value) in dic)
48+
{
49+
writer.WriteString(key, value);
50+
}
51+
52+
writer.WriteEndObject();
53+
}
54+
else
55+
{
56+
writer.WriteNullValue();
57+
}
58+
}
59+
60+
public static void WriteDictionary(
61+
this Utf8JsonWriter writer,
62+
string propertyName,
63+
IEnumerable<KeyValuePair<string, string?>>? dic)
64+
{
65+
writer.WritePropertyName(propertyName);
66+
writer.WriteDictionaryValue(dic);
67+
}
68+
69+
public static IReadOnlyDictionary<string, object?>? GetObjectDictionary(this JsonElement json)
70+
{
71+
if (json.ValueKind != JsonValueKind.Object)
72+
{
73+
return null;
74+
}
75+
76+
var result = new Dictionary<string, object?>();
77+
78+
foreach (var (name, value) in json.EnumerateObject())
79+
{
80+
result[name] = value.GetDynamic();
81+
}
82+
83+
return result;
84+
}
85+
86+
public static IReadOnlyDictionary<string, string?>? GetDictionary(this JsonElement json)
87+
{
88+
if (json.ValueKind == JsonValueKind.Null)
89+
{
90+
return null;
91+
}
92+
93+
var result = new Dictionary<string, string?>(StringComparer.Ordinal);
94+
95+
foreach (var (name, value) in json.EnumerateObject())
96+
{
97+
result[name] = value.GetString();
98+
}
99+
100+
return result;
101+
}
102+
103+
public static JsonElement? GetPropertyOrNull(this JsonElement json, string name)
104+
{
105+
if (json.ValueKind != JsonValueKind.Object)
106+
{
107+
return null;
108+
}
109+
110+
if (json.TryGetProperty(name, out var result))
111+
{
112+
if (json.ValueKind == JsonValueKind.Undefined ||
113+
json.ValueKind == JsonValueKind.Null)
114+
{
115+
return null;
116+
}
117+
118+
return result;
119+
}
120+
121+
return null;
122+
}
123+
124+
public static TOut Pipe<TIn, TOut>(this TIn input, Func<TIn, TOut> pipe) => pipe(input);
125+
126+
public static object? GetDynamic(this JsonElement json) => json.ValueKind switch
127+
{
128+
JsonValueKind.True => true,
129+
JsonValueKind.False => false,
130+
JsonValueKind.Number => json.GetDouble(),
131+
JsonValueKind.String => json.GetString(),
132+
JsonValueKind.Array => json.EnumerateArray().Select(GetDynamic).ToArray(),
133+
JsonValueKind.Object => json.GetObjectDictionary(),
134+
_ => null
135+
};
136+
137+
public static string GetStringOrThrow(this JsonElement json) =>
138+
json.GetString() ?? throw new InvalidOperationException("JSON string is null.");
139+
}
140+
}

src/Sentry/Internal/Http/HttpTransport.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Net.Http;
66
using System.Threading;
77
using System.Threading.Tasks;
8-
using Newtonsoft.Json.Linq;
98
using Sentry.Extensibility;
109
using Sentry.Infrastructure;
1110
using Sentry.Internal.Extensions;
@@ -112,8 +111,8 @@ public async Task SendEnvelopeAsync(Envelope envelope, CancellationToken cancell
112111
}
113112
else if (_options.DiagnosticLogger?.IsEnabled(SentryLevel.Error) == true)
114113
{
115-
var responseJson = await response.Content.ReadAsJsonAsync().ConfigureAwait(false);
116-
var errorMessage = responseJson.SelectToken("detail")?.Value<string>() ?? DefaultErrorMessage;
114+
var responseJson = await response.Content.ReadAsJsonAsync(cancellationToken).ConfigureAwait(false);
115+
var errorMessage = responseJson.GetPropertyOrNull("detail")?.GetString() ?? DefaultErrorMessage;
117116

118117
_options.DiagnosticLogger?.Log(
119118
SentryLevel.Error,

src/Sentry/Internal/Json.cs

Lines changed: 7 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,19 @@
1-
using System;
2-
using System.Collections;
3-
using System.IO;
4-
using System.Linq;
5-
using System.Reflection;
6-
using System.Text;
7-
using System.Threading;
8-
using System.Threading.Tasks;
9-
using Newtonsoft.Json;
10-
using Newtonsoft.Json.Converters;
11-
using Newtonsoft.Json.Serialization;
1+
using System.Text.Json;
122

133
namespace Sentry.Internal
144
{
15-
[AttributeUsage(AttributeTargets.Property)]
16-
internal class DontSerializeEmptyAttribute : Attribute {}
17-
185
internal static class Json
196
{
20-
private class ContractResolver : DefaultContractResolver
21-
{
22-
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
23-
{
24-
var jsonProperty = base.CreateProperty(member, memberSerialization);
25-
var property = jsonProperty.DeclaringType.GetProperty(jsonProperty.UnderlyingName);
26-
27-
// DontSerializeEmpty
28-
if (jsonProperty.ShouldSerialize is null &&
29-
property?.GetCustomAttribute<DontSerializeEmptyAttribute>() is {})
30-
{
31-
// Collections
32-
if (property.PropertyType.GetInterfaces().Any(i => i == typeof(IEnumerable)))
33-
{
34-
jsonProperty.ShouldSerialize = o =>
35-
{
36-
if (property.GetValue(o) is IEnumerable value)
37-
{
38-
return !value.Cast<object>().Any();
39-
}
40-
41-
return true;
42-
};
43-
}
44-
}
45-
46-
return jsonProperty;
47-
}
48-
}
49-
50-
private static readonly Encoding Encoding = new UTF8Encoding(false, true);
51-
private static readonly StringEnumConverter StringEnumConverter = new StringEnumConverter();
52-
53-
private static readonly JsonSerializer Serializer = new JsonSerializer
54-
{
55-
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
56-
NullValueHandling = NullValueHandling.Ignore,
57-
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
58-
Formatting = Formatting.None,
59-
Converters = {StringEnumConverter},
60-
DateFormatHandling = DateFormatHandling.IsoDateFormat,
61-
ContractResolver = new ContractResolver()
62-
};
63-
64-
private static JsonTextWriter CreateWriter(Stream stream) => new JsonTextWriter(
65-
new StreamWriter(stream, Encoding, 1024, true)
66-
);
67-
68-
private static JsonTextReader CreateReader(Stream stream) => new JsonTextReader(
69-
new StreamReader(stream, Encoding, false, 1024, true)
70-
);
71-
72-
public static void SerializeToStream(object obj, Stream stream)
7+
public static JsonElement Parse(byte[] json)
738
{
74-
using var writer = CreateWriter(stream);
75-
Serializer.Serialize(writer, obj);
9+
using var jsonDocument = JsonDocument.Parse(json);
10+
return jsonDocument.RootElement.Clone();
7611
}
7712

78-
public static T DeserializeFromStream<T>(Stream stream)
79-
{
80-
using var reader = CreateReader(stream);
81-
return Serializer.Deserialize<T>(reader);
82-
}
83-
84-
public static byte[] SerializeToByteArray(object obj)
85-
{
86-
using var buffer = new MemoryStream();
87-
SerializeToStream(obj, buffer);
88-
89-
return buffer.ToArray();
90-
}
91-
92-
public static T DeserializeFromByteArray<T>(byte[] data)
93-
{
94-
using var buffer = new MemoryStream(data);
95-
return DeserializeFromStream<T>(buffer);
96-
}
97-
98-
public static string Serialize(object obj) =>
99-
Encoding.GetString(SerializeToByteArray(obj));
100-
101-
public static T Deserialize<T>(string json) =>
102-
DeserializeFromByteArray<T>(Encoding.GetBytes(json));
103-
104-
public static async Task SerializeToStreamAsync(
105-
object obj,
106-
Stream stream,
107-
CancellationToken cancellationToken = default)
13+
public static JsonElement Parse(string json)
10814
{
109-
using var writer = CreateWriter(stream);
110-
Serializer.Serialize(writer, obj);
111-
await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
15+
using var jsonDocument = JsonDocument.Parse(json);
16+
return jsonDocument.RootElement.Clone();
11217
}
11318
}
11419
}

src/Sentry/PlatformAbstractions/RuntimeInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ internal static void SetNetCoreVersion(Runtime runtime)
8383
{
8484
// https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-308424100
8585
var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
86-
#if NET5_0
86+
#if NET5_0 || NETCOREAPP3_0
8787
var assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
8888
#else
8989
var assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);

0 commit comments

Comments
 (0)