Skip to content

Commit a00ab50

Browse files
committed
1 parent 792ab16 commit a00ab50

15 files changed

+82
-49
lines changed

benchmarks/Deserialization/DeserializationBenchmarkBase.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
using System.ComponentModel.Design;
2-
using System.Text.Json;
32
using JetBrains.Annotations;
43
using JsonApiDotNetCore.Configuration;
54
using JsonApiDotNetCore.Middleware;
65
using JsonApiDotNetCore.Resources;
76
using JsonApiDotNetCore.Resources.Annotations;
7+
using JsonApiDotNetCore.Serialization;
88
using JsonApiDotNetCore.Serialization.JsonConverters;
99
using JsonApiDotNetCore.Serialization.Request.Adapters;
1010
using Microsoft.Extensions.Logging.Abstractions;
@@ -13,15 +13,15 @@ namespace Benchmarks.Deserialization;
1313

1414
public abstract class DeserializationBenchmarkBase
1515
{
16-
protected readonly JsonSerializerOptions SerializerReadOptions;
16+
protected readonly JsonApiSerializationContext SerializationReadContext;
1717
protected readonly DocumentAdapter DocumentAdapter;
1818

1919
protected DeserializationBenchmarkBase()
2020
{
2121
var options = new JsonApiOptions();
2222
IResourceGraph resourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add<IncomingResource, int>().Build();
2323
options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph));
24-
SerializerReadOptions = ((IJsonApiOptions)options).SerializerReadOptions;
24+
SerializationReadContext = ((IJsonApiOptions)options).SerializationReadContext;
2525

2626
var serviceContainer = new ServiceContainer();
2727
var resourceFactory = new ResourceFactory(serviceContainer);

benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase
270270
[Benchmark]
271271
public object? DeserializeOperationsRequest()
272272
{
273-
var document = JsonSerializer.Deserialize<Document>(RequestBody, SerializerReadOptions)!;
273+
Document document = JsonSerializer.Deserialize(RequestBody, SerializationReadContext.Document)!;
274274
return DocumentAdapter.Convert(document);
275275
}
276276

benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase
133133
[Benchmark]
134134
public object? DeserializeResourceRequest()
135135
{
136-
var document = JsonSerializer.Deserialize<Document>(RequestBody, SerializerReadOptions)!;
136+
Document document = JsonSerializer.Deserialize(RequestBody, SerializationReadContext.Document)!;
137137
return DocumentAdapter.Convert(document);
138138
}
139139

benchmarks/Serialization/OperationsSerializationBenchmarks.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ private static IEnumerable<OperationContainer> CreateResponseOperations(IJsonApi
116116
public string SerializeOperationsResponse()
117117
{
118118
Document responseDocument = ResponseModelAdapter.Convert(_responseOperations);
119-
return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions);
119+
return JsonSerializer.Serialize(responseDocument, SerializationWriteContext.Document);
120120
}
121121

122122
protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)

benchmarks/Serialization/ResourceSerializationBenchmarks.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ private static OutgoingResource CreateResponseResource()
107107
public string SerializeResourceResponse()
108108
{
109109
Document responseDocument = ResponseModelAdapter.Convert(ResponseResource);
110-
return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions);
110+
return JsonSerializer.Serialize(responseDocument, SerializationWriteContext.Document);
111111
}
112112

113113
protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph)

benchmarks/Serialization/SerializationBenchmarkBase.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Text.Json;
21
using System.Text.Json.Serialization;
32
using Benchmarks.Tools;
43
using JetBrains.Annotations;
@@ -8,14 +7,15 @@
87
using JsonApiDotNetCore.Queries.Internal;
98
using JsonApiDotNetCore.Resources;
109
using JsonApiDotNetCore.Resources.Annotations;
10+
using JsonApiDotNetCore.Serialization;
1111
using JsonApiDotNetCore.Serialization.Response;
1212
using Microsoft.Extensions.Logging.Abstractions;
1313

1414
namespace Benchmarks.Serialization;
1515

1616
public abstract class SerializationBenchmarkBase
1717
{
18-
protected readonly JsonSerializerOptions SerializerWriteOptions;
18+
protected readonly JsonApiSerializationContext SerializationWriteContext;
1919
protected readonly IResponseModelAdapter ResponseModelAdapter;
2020
protected readonly IResourceGraph ResourceGraph;
2121

@@ -33,7 +33,7 @@ protected SerializationBenchmarkBase()
3333
};
3434

3535
ResourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add<OutgoingResource, int>().Build();
36-
SerializerWriteOptions = ((IJsonApiOptions)options).SerializerWriteOptions;
36+
SerializationWriteContext = ((IJsonApiOptions)options).SerializationWriteContext;
3737

3838
// ReSharper disable VirtualMemberCallInConstructor
3939
JsonApiRequest request = CreateJsonApiRequest(ResourceGraph);

src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
using System.Data;
22
using System.Text.Json;
3+
using JetBrains.Annotations;
34
using JsonApiDotNetCore.Resources.Annotations;
5+
using JsonApiDotNetCore.Serialization;
46
using JsonApiDotNetCore.Serialization.Objects;
57

68
namespace JsonApiDotNetCore.Configuration;
79

810
/// <summary>
911
/// Global options that configure the behavior of JsonApiDotNetCore.
1012
/// </summary>
13+
[PublicAPI]
1114
public interface IJsonApiOptions
1215
{
1316
/// <summary>
@@ -156,13 +159,27 @@ public interface IJsonApiOptions
156159
/// </example>
157160
JsonSerializerOptions SerializerOptions { get; }
158161

162+
/// <summary>
163+
/// Gets the source-generated JSON serialization context used for deserializing request bodies. This value is based on <see cref="SerializerOptions" />
164+
/// and is intended for internal use.
165+
/// </summary>
166+
JsonApiSerializationContext SerializationReadContext { get; }
167+
159168
/// <summary>
160169
/// Gets the settings used for deserializing request bodies. This value is based on <see cref="SerializerOptions" /> and is intended for internal use.
161170
/// </summary>
171+
[Obsolete("Use SerializationReadContext.Options instead.")]
162172
JsonSerializerOptions SerializerReadOptions { get; }
163173

174+
/// <summary>
175+
/// Gets the source-generated JSON serialization context used for serializing response bodies. This value is based on <see cref="SerializerOptions" />
176+
/// and is intended for internal use.
177+
/// </summary>
178+
JsonApiSerializationContext SerializationWriteContext { get; }
179+
164180
/// <summary>
165181
/// Gets the settings used for serializing response bodies. This value is based on <see cref="SerializerOptions" /> and is intended for internal use.
166182
/// </summary>
183+
[Obsolete("Use SerializationWriteContext.Options instead.")]
167184
JsonSerializerOptions SerializerWriteOptions { get; }
168185
}

src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Text.Json;
44
using JetBrains.Annotations;
55
using JsonApiDotNetCore.Resources.Annotations;
6+
using JsonApiDotNetCore.Serialization;
67
using JsonApiDotNetCore.Serialization.JsonConverters;
78

89
namespace JsonApiDotNetCore.Configuration;
@@ -11,14 +12,20 @@ namespace JsonApiDotNetCore.Configuration;
1112
[PublicAPI]
1213
public sealed class JsonApiOptions : IJsonApiOptions
1314
{
14-
private readonly Lazy<JsonSerializerOptions> _lazySerializerWriteOptions;
15-
private readonly Lazy<JsonSerializerOptions> _lazySerializerReadOptions;
15+
private readonly Lazy<JsonApiSerializationContext> _lazySerializerReadContext;
16+
private readonly Lazy<JsonApiSerializationContext> _lazySerializerWriteContext;
1617

1718
/// <inheritdoc />
18-
JsonSerializerOptions IJsonApiOptions.SerializerReadOptions => _lazySerializerReadOptions.Value;
19+
JsonApiSerializationContext IJsonApiOptions.SerializationReadContext => _lazySerializerReadContext.Value;
1920

2021
/// <inheritdoc />
21-
JsonSerializerOptions IJsonApiOptions.SerializerWriteOptions => _lazySerializerWriteOptions.Value;
22+
JsonSerializerOptions IJsonApiOptions.SerializerReadOptions => ((IJsonApiOptions)this).SerializationReadContext.Options;
23+
24+
/// <inheritdoc />
25+
JsonApiSerializationContext IJsonApiOptions.SerializationWriteContext => _lazySerializerWriteContext.Value;
26+
27+
/// <inheritdoc />
28+
JsonSerializerOptions IJsonApiOptions.SerializerWriteOptions => ((IJsonApiOptions)this).SerializationWriteContext.Options;
2229

2330
/// <inheritdoc />
2431
public string? Namespace { get; set; }
@@ -110,16 +117,16 @@ static JsonApiOptions()
110117

111118
public JsonApiOptions()
112119
{
113-
_lazySerializerReadOptions =
114-
new Lazy<JsonSerializerOptions>(() => new JsonSerializerOptions(SerializerOptions), LazyThreadSafetyMode.ExecutionAndPublication);
120+
_lazySerializerReadContext = new Lazy<JsonApiSerializationContext>(() => new JsonApiSerializationContext(new JsonSerializerOptions(SerializerOptions)),
121+
LazyThreadSafetyMode.ExecutionAndPublication);
115122

116-
_lazySerializerWriteOptions = new Lazy<JsonSerializerOptions>(() => new JsonSerializerOptions(SerializerOptions)
123+
_lazySerializerWriteContext = new Lazy<JsonApiSerializationContext>(() => new JsonApiSerializationContext(new JsonSerializerOptions(SerializerOptions)
117124
{
118125
Converters =
119126
{
120127
new WriteOnlyDocumentConverter(),
121128
new WriteOnlyRelationshipObjectConverter()
122129
}
123-
}, LazyThreadSafetyMode.ExecutionAndPublication);
130+
}), LazyThreadSafetyMode.ExecutionAndPublication);
124131
}
125132
}

src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using JsonApiDotNetCore.Configuration;
55
using JsonApiDotNetCore.Diagnostics;
66
using JsonApiDotNetCore.Resources.Annotations;
7+
using JsonApiDotNetCore.Serialization;
78
using JsonApiDotNetCore.Serialization.Objects;
89
using Microsoft.AspNetCore.Http;
910
using Microsoft.AspNetCore.Http.Extensions;
@@ -44,7 +45,7 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin
4445

4546
using (CodeTimingSessionManager.Current.Measure("JSON:API middleware"))
4647
{
47-
if (!await ValidateIfMatchHeaderAsync(httpContext, options.SerializerWriteOptions))
48+
if (!await ValidateIfMatchHeaderAsync(httpContext, options.SerializationWriteContext))
4849
{
4950
return;
5051
}
@@ -54,8 +55,8 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin
5455

5556
if (primaryResourceType != null)
5657
{
57-
if (!await ValidateContentTypeHeaderAsync(HeaderConstants.MediaType, httpContext, options.SerializerWriteOptions) ||
58-
!await ValidateAcceptHeaderAsync(MediaType, httpContext, options.SerializerWriteOptions))
58+
if (!await ValidateContentTypeHeaderAsync(HeaderConstants.MediaType, httpContext, options.SerializationWriteContext) ||
59+
!await ValidateAcceptHeaderAsync(MediaType, httpContext, options.SerializationWriteContext))
5960
{
6061
return;
6162
}
@@ -66,8 +67,8 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin
6667
}
6768
else if (IsRouteForOperations(routeValues))
6869
{
69-
if (!await ValidateContentTypeHeaderAsync(HeaderConstants.AtomicOperationsMediaType, httpContext, options.SerializerWriteOptions) ||
70-
!await ValidateAcceptHeaderAsync(AtomicOperationsMediaType, httpContext, options.SerializerWriteOptions))
70+
if (!await ValidateContentTypeHeaderAsync(HeaderConstants.AtomicOperationsMediaType, httpContext, options.SerializationWriteContext) ||
71+
!await ValidateAcceptHeaderAsync(AtomicOperationsMediaType, httpContext, options.SerializationWriteContext))
7172
{
7273
return;
7374
}
@@ -91,11 +92,11 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin
9192
}
9293
}
9394

94-
private async Task<bool> ValidateIfMatchHeaderAsync(HttpContext httpContext, JsonSerializerOptions serializerOptions)
95+
private async Task<bool> ValidateIfMatchHeaderAsync(HttpContext httpContext, JsonApiSerializationContext serializationContext)
9596
{
9697
if (httpContext.Request.Headers.ContainsKey(HeaderNames.IfMatch))
9798
{
98-
await FlushResponseAsync(httpContext.Response, serializerOptions, new ErrorObject(HttpStatusCode.PreconditionFailed)
99+
await FlushResponseAsync(httpContext.Response, serializationContext, new ErrorObject(HttpStatusCode.PreconditionFailed)
99100
{
100101
Title = "Detection of mid-air edit collisions using ETags is not supported.",
101102
Source = new ErrorSource
@@ -120,13 +121,14 @@ private async Task<bool> ValidateIfMatchHeaderAsync(HttpContext httpContext, Jso
120121
: null;
121122
}
122123

123-
private static async Task<bool> ValidateContentTypeHeaderAsync(string allowedContentType, HttpContext httpContext, JsonSerializerOptions serializerOptions)
124+
private static async Task<bool> ValidateContentTypeHeaderAsync(string allowedContentType, HttpContext httpContext,
125+
JsonApiSerializationContext serializationContext)
124126
{
125127
string? contentType = httpContext.Request.ContentType;
126128

127129
if (contentType != null && contentType != allowedContentType)
128130
{
129-
await FlushResponseAsync(httpContext.Response, serializerOptions, new ErrorObject(HttpStatusCode.UnsupportedMediaType)
131+
await FlushResponseAsync(httpContext.Response, serializationContext, new ErrorObject(HttpStatusCode.UnsupportedMediaType)
130132
{
131133
Title = "The specified Content-Type header value is not supported.",
132134
Detail = $"Please specify '{allowedContentType}' instead of '{contentType}' for the Content-Type header value.",
@@ -143,7 +145,7 @@ private static async Task<bool> ValidateContentTypeHeaderAsync(string allowedCon
143145
}
144146

145147
private static async Task<bool> ValidateAcceptHeaderAsync(MediaTypeHeaderValue allowedMediaTypeValue, HttpContext httpContext,
146-
JsonSerializerOptions serializerOptions)
148+
JsonApiSerializationContext serializationContext)
147149
{
148150
string[] acceptHeaders = httpContext.Request.Headers.GetCommaSeparatedValues("Accept");
149151

@@ -176,7 +178,7 @@ private static async Task<bool> ValidateAcceptHeaderAsync(MediaTypeHeaderValue a
176178

177179
if (!seenCompatibleMediaType)
178180
{
179-
await FlushResponseAsync(httpContext.Response, serializerOptions, new ErrorObject(HttpStatusCode.NotAcceptable)
181+
await FlushResponseAsync(httpContext.Response, serializationContext, new ErrorObject(HttpStatusCode.NotAcceptable)
180182
{
181183
Title = "The specified Accept header value does not contain any supported media types.",
182184
Detail = $"Please include '{allowedMediaTypeValue}' in the Accept header values.",
@@ -192,7 +194,7 @@ private static async Task<bool> ValidateAcceptHeaderAsync(MediaTypeHeaderValue a
192194
return true;
193195
}
194196

195-
private static async Task FlushResponseAsync(HttpResponse httpResponse, JsonSerializerOptions serializerOptions, ErrorObject error)
197+
private static async Task FlushResponseAsync(HttpResponse httpResponse, JsonApiSerializationContext serializationContext, ErrorObject error)
196198
{
197199
httpResponse.ContentType = HeaderConstants.MediaType;
198200
httpResponse.StatusCode = (int)error.StatusCode;
@@ -202,7 +204,7 @@ private static async Task FlushResponseAsync(HttpResponse httpResponse, JsonSeri
202204
Errors = error.AsList()
203205
};
204206

205-
await JsonSerializer.SerializeAsync(httpResponse.Body, errorDocument, serializerOptions);
207+
await JsonSerializer.SerializeAsync(httpResponse.Body, errorDocument, serializationContext.Document);
206208
await httpResponse.Body.FlushAsync();
207209
}
208210

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Text.Json.Serialization;
2+
using JsonApiDotNetCore.Serialization.Objects;
3+
4+
namespace JsonApiDotNetCore.Serialization;
5+
6+
// Workaround for https://youtrack.jetbrains.com/issue/RSRP-487028
7+
partial class JsonApiSerializationContext
8+
{
9+
}
10+
11+
/// <summary>
12+
/// Provides compile-time metadata about the set of JSON:API types used in JSON serialization of request/response bodies.
13+
/// </summary>
14+
[JsonSerializable(typeof(Document))]
15+
public sealed partial class JsonApiSerializationContext : JsonSerializerContext
16+
{
17+
}

src/JsonApiDotNetCore/Serialization/JsonConverters/JsonObjectConverter.cs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,12 @@ public abstract class JsonObjectConverter<TObject> : JsonConverter<TObject>
77
{
88
protected static TValue? ReadSubTree<TValue>(ref Utf8JsonReader reader, JsonSerializerOptions options)
99
{
10-
if (typeof(TValue) != typeof(object) && options.GetConverter(typeof(TValue)) is JsonConverter<TValue> converter)
11-
{
12-
return converter.Read(ref reader, typeof(TValue), options);
13-
}
14-
1510
return JsonSerializer.Deserialize<TValue>(ref reader, options);
1611
}
1712

1813
protected static void WriteSubTree<TValue>(Utf8JsonWriter writer, TValue value, JsonSerializerOptions options)
1914
{
20-
if (typeof(TValue) != typeof(object) && options.GetConverter(typeof(TValue)) is JsonConverter<TValue> converter)
21-
{
22-
converter.Write(writer, value, options);
23-
}
24-
else
25-
{
26-
JsonSerializer.Serialize(writer, value, options);
27-
}
15+
JsonSerializer.Serialize(writer, value, options);
2816
}
2917

3018
protected static JsonException GetEndOfStreamError()

src/JsonApiDotNetCore/Serialization/Objects/Document.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using System.Text.Json.Serialization;
2+
using JetBrains.Annotations;
23

34
namespace JsonApiDotNetCore.Serialization.Objects;
45

56
/// <summary>
67
/// See https://jsonapi.org/format/1.1/#document-top-level and https://jsonapi.org/ext/atomic/#document-structure.
78
/// </summary>
9+
[PublicAPI]
810
public sealed class Document
911
{
1012
[JsonPropertyName("jsonapi")]

src/JsonApiDotNetCore/Serialization/Request/JsonApiReader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ private Document DeserializeDocument(string requestBody)
8080
using IDisposable _ =
8181
CodeTimingSessionManager.Current.Measure("JsonSerializer.Deserialize", MeasurementSettings.ExcludeJsonSerializationInPercentages);
8282

83-
var document = JsonSerializer.Deserialize<Document>(requestBody, _options.SerializerReadOptions);
83+
Document? document = JsonSerializer.Deserialize(requestBody, _options.SerializationReadContext.Document);
8484

8585
AssertHasDocument(document, requestBody);
8686

src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ private string SerializeDocument(Document document)
125125
{
126126
using IDisposable _ = CodeTimingSessionManager.Current.Measure("JsonSerializer.Serialize", MeasurementSettings.ExcludeJsonSerializationInPercentages);
127127

128-
return JsonSerializer.Serialize(document, _options.SerializerWriteOptions);
128+
return JsonSerializer.Serialize(document, _options.SerializationWriteContext.Document);
129129
}
130130

131131
private bool SetETagResponseHeader(HttpRequest request, HttpResponse response, string responseContent)

0 commit comments

Comments
 (0)