Skip to content

Commit 7e2eb6b

Browse files
Add JsonContent.Create overloads which accept JsonTypeInfo (#89614)
* Add JsonContent.Create overloads which accept JsonTypeInfo * Change type parameter to match other overloads * Feedback fixes, primarily using inheritance * Remove polymorphism * Address constructor feedback * Update src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs Co-authored-by: Eirik Tsarpalis <eirik.tsarpalis@gmail.com> --------- Co-authored-by: Eirik Tsarpalis <eirik.tsarpalis@gmail.com>
1 parent 9059941 commit 7e2eb6b

9 files changed

+126
-175
lines changed

src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs

+2
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ internal JsonContent() { }
156156
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
157157
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.")]
158158
public static System.Net.Http.Json.JsonContent Create<T>(T inputValue, System.Net.Http.Headers.MediaTypeHeaderValue? mediaType = null, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
159+
public static System.Net.Http.Json.JsonContent Create<T>(T? inputValue, System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> jsonTypeInfo, System.Net.Http.Headers.MediaTypeHeaderValue? mediaType = null) { throw null; }
160+
public static System.Net.Http.Json.JsonContent Create(object? inputValue, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Net.Http.Headers.MediaTypeHeaderValue? mediaType = null) { throw null; }
159161
protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context) { throw null; }
160162
protected override bool TryComputeLength(out long length) { throw null; }
161163
}

src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ System.Net.Http.Json.JsonContent</PackageDescription>
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14+
<Compile Include="$(CommonPath)System\ThrowHelper.cs"
15+
Link="Common\System\ThrowHelper.cs" />
1416
<Compile Include="System\Net\Http\Json\HttpClientJsonExtensions.Get.AsyncEnumerable.cs" />
1517
<Compile Include="System\Net\Http\Json\HttpClientJsonExtensions.cs" />
1618
<Compile Include="System\Net\Http\Json\HttpContentJsonExtensions.AsyncEnumerable.cs" />
@@ -22,7 +24,6 @@ System.Net.Http.Json.JsonContent</PackageDescription>
2224
<Compile Include="System\Net\Http\Json\HttpClientJsonExtensions.Patch.cs" />
2325
<Compile Include="System\Net\Http\Json\HttpContentJsonExtensions.cs" />
2426
<Compile Include="System\Net\Http\Json\JsonContent.cs" />
25-
<Compile Include="System\Net\Http\Json\JsonContentOfT.cs" />
2627
<Compile Include="System\Net\Http\Json\LengthLimitReadStream.cs" />
2728
</ItemGroup>
2829

@@ -33,7 +34,6 @@ System.Net.Http.Json.JsonContent</PackageDescription>
3334
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
3435
<Compile Include="System\Net\Http\Json\HttpContentJsonExtensions.netcoreapp.cs" />
3536
<Compile Include="System\Net\Http\Json\JsonContent.netcoreapp.cs" />
36-
<Compile Include="System\Net\Http\Json\JsonContentOfT.netcoreapp.cs" />
3737
</ItemGroup>
3838

3939
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">

src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Patch.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public static Task<HttpResponseMessage> PatchAsJsonAsync<TValue>(this HttpClient
107107
throw new ArgumentNullException(nameof(client));
108108
}
109109

110-
JsonContent<TValue> content = new(value, jsonTypeInfo);
110+
JsonContent content = JsonContent.Create(value, jsonTypeInfo);
111111
return client.PatchAsync(requestUri, content, cancellationToken);
112112
}
113113

@@ -129,7 +129,7 @@ public static Task<HttpResponseMessage> PatchAsJsonAsync<TValue>(this HttpClient
129129
throw new ArgumentNullException(nameof(client));
130130
}
131131

132-
JsonContent<TValue> content = new(value, jsonTypeInfo);
132+
JsonContent content = JsonContent.Create(value, jsonTypeInfo);
133133
return client.PatchAsync(requestUri, content, cancellationToken);
134134
}
135135
}

src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Post.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient
5454
throw new ArgumentNullException(nameof(client));
5555
}
5656

57-
JsonContent<TValue> content = new(value, jsonTypeInfo);
57+
JsonContent content = JsonContent.Create(value, jsonTypeInfo);
5858
return client.PostAsync(requestUri, content, cancellationToken);
5959
}
6060

@@ -65,7 +65,7 @@ public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient
6565
throw new ArgumentNullException(nameof(client));
6666
}
6767

68-
JsonContent<TValue> content = new(value, jsonTypeInfo);
68+
JsonContent content = JsonContent.Create(value, jsonTypeInfo);
6969
return client.PostAsync(requestUri, content, cancellationToken);
7070
}
7171
}

src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Put.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient c
5454
throw new ArgumentNullException(nameof(client));
5555
}
5656

57-
JsonContent<TValue> content = new(value, jsonTypeInfo);
57+
JsonContent content = JsonContent.Create(value, jsonTypeInfo);
5858
return client.PutAsync(requestUri, content, cancellationToken);
5959
}
6060

@@ -65,7 +65,7 @@ public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient c
6565
throw new ArgumentNullException(nameof(client));
6666
}
6767

68-
JsonContent<TValue> content = new(value, jsonTypeInfo);
68+
JsonContent content = JsonContent.Create(value, jsonTypeInfo);
6969
return client.PutAsync(requestUri, content, cancellationToken);
7070
}
7171
}
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,68 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
#if !NETCOREAPP
54
using System.Diagnostics;
6-
#endif
75
using System.Diagnostics.CodeAnalysis;
86
using System.IO;
97
using System.Net.Http.Headers;
108
using System.Text;
119
using System.Text.Json;
10+
using System.Text.Json.Serialization.Metadata;
1211
using System.Threading;
1312
using System.Threading.Tasks;
1413

1514
namespace System.Net.Http.Json
1615
{
1716
public sealed partial class JsonContent : HttpContent
1817
{
19-
private readonly JsonSerializerOptions? _jsonSerializerOptions;
20-
public Type ObjectType { get; }
18+
private readonly JsonTypeInfo _typeInfo;
19+
public Type ObjectType => _typeInfo.Type;
2120
public object? Value { get; }
2221

23-
[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
24-
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
2522
private JsonContent(
2623
object? inputValue,
27-
Type inputType,
28-
MediaTypeHeaderValue? mediaType,
29-
JsonSerializerOptions? options)
24+
JsonTypeInfo jsonTypeInfo,
25+
MediaTypeHeaderValue? mediaType)
3026
{
31-
if (inputType is null)
32-
{
33-
throw new ArgumentNullException(nameof(inputType));
34-
}
35-
36-
if (inputValue != null && !inputType.IsAssignableFrom(inputValue.GetType()))
37-
{
38-
throw new ArgumentException(SR.Format(SR.SerializeWrongType, inputType, inputValue.GetType()));
39-
}
27+
Debug.Assert(jsonTypeInfo is not null);
28+
Debug.Assert(inputValue is null || jsonTypeInfo.Type.IsAssignableFrom(inputValue.GetType()));
4029

4130
Value = inputValue;
42-
ObjectType = inputType;
31+
_typeInfo = jsonTypeInfo;
4332
Headers.ContentType = mediaType ?? JsonHelpers.GetDefaultMediaType();
44-
_jsonSerializerOptions = options ?? JsonHelpers.s_defaultSerializerOptions;
4533
}
4634

4735
[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
4836
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
4937
public static JsonContent Create<T>(T inputValue, MediaTypeHeaderValue? mediaType = null, JsonSerializerOptions? options = null)
50-
=> Create(inputValue, typeof(T), mediaType, options);
38+
=> Create(inputValue, GetJsonTypeInfo(typeof(T), options), mediaType);
5139

5240
[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
5341
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
5442
public static JsonContent Create(object? inputValue, Type inputType, MediaTypeHeaderValue? mediaType = null, JsonSerializerOptions? options = null)
55-
=> new JsonContent(inputValue, inputType, mediaType, options);
43+
{
44+
ThrowHelper.ThrowIfNull(inputType);
45+
EnsureTypeCompatibility(inputValue, inputType);
46+
47+
return new JsonContent(inputValue, GetJsonTypeInfo(inputType, options), mediaType);
48+
}
49+
50+
public static JsonContent Create<T>(T? inputValue, JsonTypeInfo<T> jsonTypeInfo,
51+
MediaTypeHeaderValue? mediaType = null)
52+
{
53+
ThrowHelper.ThrowIfNull(jsonTypeInfo);
54+
55+
return new JsonContent(inputValue, jsonTypeInfo, mediaType);
56+
}
57+
58+
public static JsonContent Create(object? inputValue, JsonTypeInfo jsonTypeInfo,
59+
MediaTypeHeaderValue? mediaType = null)
60+
{
61+
ThrowHelper.ThrowIfNull(jsonTypeInfo);
62+
EnsureTypeCompatibility(inputValue, jsonTypeInfo.Type);
63+
64+
return new JsonContent(inputValue, jsonTypeInfo, mediaType);
65+
}
5666

5767
protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context)
5868
=> SerializeToStreamAsyncCore(stream, async: true, CancellationToken.None);
@@ -63,10 +73,6 @@ protected override bool TryComputeLength(out long length)
6373
return false;
6474
}
6575

66-
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
67-
Justification = "The ctor is annotated with RequiresUnreferencedCode.")]
68-
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
69-
Justification = "The ctor is annotated with RequiresDynamicCode.")]
7076
private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, CancellationToken cancellationToken)
7177
{
7278
Encoding? targetEncoding = JsonHelpers.GetEncoding(this);
@@ -80,11 +86,11 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C
8086
{
8187
if (async)
8288
{
83-
await JsonSerializer.SerializeAsync(transcodingStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false);
89+
await JsonSerializer.SerializeAsync(transcodingStream, Value, _typeInfo, cancellationToken).ConfigureAwait(false);
8490
}
8591
else
8692
{
87-
JsonSerializer.Serialize(transcodingStream, Value, ObjectType, _jsonSerializerOptions);
93+
JsonSerializer.Serialize(transcodingStream, Value, _typeInfo);
8894
}
8995
}
9096
finally
@@ -101,11 +107,11 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C
101107
}
102108
}
103109
#else
104-
Debug.Assert(async);
110+
Debug.Assert(async, "HttpContent synchronous serialization is only supported since .NET 5.0");
105111

106112
using (TranscodingWriteStream transcodingStream = new TranscodingWriteStream(targetStream, targetEncoding))
107113
{
108-
await JsonSerializer.SerializeAsync(transcodingStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false);
114+
await JsonSerializer.SerializeAsync(transcodingStream, Value, _typeInfo, cancellationToken).ConfigureAwait(false);
109115
// The transcoding streams use Encoders and Decoders that have internal buffers. We need to flush these
110116
// when there is no more data to be written. Stream.FlushAsync isn't suitable since it's
111117
// acceptable to Flush a Stream (multiple times) prior to completion.
@@ -117,17 +123,39 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C
117123
{
118124
if (async)
119125
{
120-
await JsonSerializer.SerializeAsync(targetStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false);
126+
await JsonSerializer.SerializeAsync(targetStream, Value, _typeInfo, cancellationToken).ConfigureAwait(false);
121127
}
122128
else
123129
{
124130
#if NETCOREAPP
125-
JsonSerializer.Serialize(targetStream, Value, ObjectType, _jsonSerializerOptions);
131+
JsonSerializer.Serialize(targetStream, Value, _typeInfo);
126132
#else
127-
Debug.Fail("Synchronous serialization is only supported since .NET 5.0");
133+
Debug.Fail("HttpContent synchronous serialization is only supported since .NET 5.0");
128134
#endif
129135
}
130136
}
131137
}
138+
139+
[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
140+
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
141+
private static JsonTypeInfo GetJsonTypeInfo(Type inputType, JsonSerializerOptions? options)
142+
{
143+
Debug.Assert(inputType is not null);
144+
145+
// Ensure the options supports the call to GetTypeInfo
146+
options ??= JsonHelpers.s_defaultSerializerOptions;
147+
options.TypeInfoResolver ??= JsonSerializerOptions.Default.TypeInfoResolver;
148+
options.MakeReadOnly();
149+
150+
return options.GetTypeInfo(inputType);
151+
}
152+
153+
private static void EnsureTypeCompatibility(object? inputValue, Type inputType)
154+
{
155+
if (inputValue is not null && !inputType.IsAssignableFrom(inputValue.GetType()))
156+
{
157+
throw new ArgumentException(SR.Format(SR.SerializeWrongType, inputType, inputValue.GetType()));
158+
}
159+
}
132160
}
133161
}

src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.cs

-112
This file was deleted.

src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.netcoreapp.cs

-18
This file was deleted.

0 commit comments

Comments
 (0)