Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JsonContent.Create overloads which accept JsonTypeInfo #89614

Merged
merged 6 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public static partial class HttpContentJsonExtensions
[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.")]
public static System.Threading.Tasks.Task<T?> ReadFromJsonAsync<T>(this System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public sealed partial class JsonContent : System.Net.Http.HttpContent
public abstract partial class JsonContent : System.Net.Http.HttpContent
{
internal JsonContent() { }
public System.Type ObjectType { get { throw null; } }
Expand All @@ -156,6 +156,8 @@ internal JsonContent() { }
[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.")]
[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.")]
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; }
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; }
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; }
protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context) { throw null; }
protected override bool TryComputeLength(out long length) { throw null; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace System.Net.Http.Json
{
public sealed partial class JsonContent : System.Net.Http.HttpContent
public abstract partial class JsonContent : System.Net.Http.HttpContent
{
protected override void SerializeToStream(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { }
protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ System.Net.Http.Json.JsonContent</PackageDescription>
</PropertyGroup>

<ItemGroup>
<Compile Include="$(CommonPath)System\ThrowHelper.cs"
Link="Common\System\ThrowHelper.cs" />
<Compile Include="System\Net\Http\Json\HttpClientJsonExtensions.Get.AsyncEnumerable.cs" />
<Compile Include="System\Net\Http\Json\HttpClientJsonExtensions.cs" />
<Compile Include="System\Net\Http\Json\HttpContentJsonExtensions.AsyncEnumerable.cs" />
Expand All @@ -23,6 +25,7 @@ System.Net.Http.Json.JsonContent</PackageDescription>
<Compile Include="System\Net\Http\Json\HttpContentJsonExtensions.cs" />
<Compile Include="System\Net\Http\Json\JsonContent.cs" />
<Compile Include="System\Net\Http\Json\JsonContentOfT.cs" />
<Compile Include="System\Net\Http\Json\JsonContentUntyped.cs" />
<Compile Include="System\Net\Http\Json\LengthLimitReadStream.cs" />
</ItemGroup>

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

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public static Task<HttpResponseMessage> PatchAsJsonAsync<TValue>(this HttpClient
throw new ArgumentNullException(nameof(client));
}

JsonContent<TValue> content = new(value, jsonTypeInfo);
JsonContent content = JsonContent.Create(value, jsonTypeInfo);
return client.PatchAsync(requestUri, content, cancellationToken);
}

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

JsonContent<TValue> content = new(value, jsonTypeInfo);
JsonContent content = JsonContent.Create(value, jsonTypeInfo);
return client.PatchAsync(requestUri, content, cancellationToken);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient
throw new ArgumentNullException(nameof(client));
}

JsonContent<TValue> content = new(value, jsonTypeInfo);
JsonContent content = JsonContent.Create(value, jsonTypeInfo);
return client.PostAsync(requestUri, content, cancellationToken);
}

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

JsonContent<TValue> content = new(value, jsonTypeInfo);
JsonContent content = JsonContent.Create(value, jsonTypeInfo);
return client.PostAsync(requestUri, content, cancellationToken);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient c
throw new ArgumentNullException(nameof(client));
}

JsonContent<TValue> content = new(value, jsonTypeInfo);
JsonContent content = JsonContent.Create(value, jsonTypeInfo);
return client.PutAsync(requestUri, content, cancellationToken);
}

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

JsonContent<TValue> content = new(value, jsonTypeInfo);
JsonContent content = JsonContent.Create(value, jsonTypeInfo);
return client.PutAsync(requestUri, content, cancellationToken);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,23 @@
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Tasks;

namespace System.Net.Http.Json
{
public sealed partial class JsonContent : HttpContent
public abstract partial class JsonContent : HttpContent
{
private readonly JsonSerializerOptions? _jsonSerializerOptions;
public Type ObjectType { get; }
public object? Value { get; }
public Type ObjectType => JsonTypeInfo.Type;
public object? Value => ValueCore;

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
private JsonContent(
object? inputValue,
Type inputType,
MediaTypeHeaderValue? mediaType,
JsonSerializerOptions? options)
{
if (inputType is null)
{
throw new ArgumentNullException(nameof(inputType));
}

if (inputValue != null && !inputType.IsAssignableFrom(inputValue.GetType()))
{
throw new ArgumentException(SR.Format(SR.SerializeWrongType, inputType, inputValue.GetType()));
}
private protected abstract JsonTypeInfo JsonTypeInfo { get; }
private protected abstract object? ValueCore { get; }

Value = inputValue;
ObjectType = inputType;
private protected JsonContent(MediaTypeHeaderValue? mediaType)
{
Headers.ContentType = mediaType ?? JsonHelpers.GetDefaultMediaType();
_jsonSerializerOptions = options ?? JsonHelpers.s_defaultSerializerOptions;
}

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
Expand All @@ -52,7 +36,16 @@ public static JsonContent Create<T>(T inputValue, MediaTypeHeaderValue? mediaTyp
[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
public static JsonContent Create(object? inputValue, Type inputType, MediaTypeHeaderValue? mediaType = null, JsonSerializerOptions? options = null)
=> new JsonContent(inputValue, inputType, mediaType, options);
=> new JsonContentUntyped(inputValue, inputType, options, mediaType);

public static JsonContent Create<T>(T? inputValue, JsonTypeInfo<T> jsonTypeInfo,
MediaTypeHeaderValue? mediaType = null)
=> inputValue is not null
? new JsonContent<T>(inputValue, jsonTypeInfo, mediaType)
: new JsonContentUntyped(null, jsonTypeInfo, mediaType);

public static JsonContent Create(object? inputValue, JsonTypeInfo jsonTypeInfo, MediaTypeHeaderValue? mediaType = null)
=> new JsonContentUntyped(inputValue, jsonTypeInfo, mediaType);

protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context)
=> SerializeToStreamAsyncCore(stream, async: true, CancellationToken.None);
Expand All @@ -63,10 +56,12 @@ protected override bool TryComputeLength(out long length)
return false;
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "The ctor is annotated with RequiresUnreferencedCode.")]
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
Justification = "The ctor is annotated with RequiresDynamicCode.")]
private protected abstract Task SerializeToUtf8StreamAsync(Stream targetStream, CancellationToken cancellationToken);

#if NETCOREAPP
private protected abstract void SerializeToUtf8Stream(Stream targetStream);
#endif

private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, CancellationToken cancellationToken)
{
Encoding? targetEncoding = JsonHelpers.GetEncoding(this);
Expand All @@ -80,11 +75,11 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C
{
if (async)
{
await JsonSerializer.SerializeAsync(transcodingStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false);
await SerializeToUtf8StreamAsync(transcodingStream, cancellationToken).ConfigureAwait(false);
}
else
{
JsonSerializer.Serialize(transcodingStream, Value, ObjectType, _jsonSerializerOptions);
SerializeToUtf8Stream(transcodingStream);
}
}
finally
Expand All @@ -101,11 +96,11 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C
}
}
#else
Debug.Assert(async);
Debug.Assert(async, "HttpContent synchronous serialization is only supported since .NET 5.0");

using (TranscodingWriteStream transcodingStream = new TranscodingWriteStream(targetStream, targetEncoding))
{
await JsonSerializer.SerializeAsync(transcodingStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false);
await SerializeToUtf8StreamAsync(transcodingStream, cancellationToken).ConfigureAwait(false);
// The transcoding streams use Encoders and Decoders that have internal buffers. We need to flush these
// when there is no more data to be written. Stream.FlushAsync isn't suitable since it's
// acceptable to Flush a Stream (multiple times) prior to completion.
Expand All @@ -117,14 +112,14 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C
{
if (async)
{
await JsonSerializer.SerializeAsync(targetStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false);
await SerializeToUtf8StreamAsync(targetStream, cancellationToken).ConfigureAwait(false);
}
else
{
#if NETCOREAPP
JsonSerializer.Serialize(targetStream, Value, ObjectType, _jsonSerializerOptions);
SerializeToUtf8Stream(targetStream);
#else
Debug.Fail("Synchronous serialization is only supported since .NET 5.0");
Debug.Fail("HttpContent synchronous serialization is only supported since .NET 5.0");
#endif
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace System.Net.Http.Json
{
public sealed partial class JsonContent
public abstract partial class JsonContent
{
protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken)
=> SerializeToStreamAsyncCore(stream, async: false, cancellationToken).GetAwaiter().GetResult();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,112 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NETCOREAPP
using System.Diagnostics;
#endif
using System.IO;
using System.Text;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Tasks;

namespace System.Net.Http.Json
{
internal sealed partial class JsonContent<TValue> : HttpContent
internal sealed class JsonContent<TValue> : JsonContent
{
private readonly JsonTypeInfo<TValue> _typeInfo;

private readonly TValue _typedValue;

public JsonContent(TValue inputValue, JsonTypeInfo<TValue> jsonTypeInfo)
private protected override JsonTypeInfo JsonTypeInfo => _typeInfo;
private protected override object? ValueCore => _typedValue;

public JsonContent(TValue inputValue, JsonTypeInfo<TValue> jsonTypeInfo, MediaTypeHeaderValue? mediaType)
: base(mediaType)
{
if (jsonTypeInfo is null)
{
throw new ArgumentNullException(nameof(jsonTypeInfo));
}
ThrowHelper.ThrowIfNull(jsonTypeInfo);

_typeInfo = jsonTypeInfo;
_typedValue = inputValue;
Headers.ContentType = JsonHelpers.GetDefaultMediaType();
}

protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context)
=> SerializeToStreamAsyncCore(stream, async: true, CancellationToken.None);
private protected override Task SerializeToUtf8StreamAsync(Stream targetStream,
CancellationToken cancellationToken)
=> JsonSerializer.SerializeAsync(targetStream, _typedValue, _typeInfo, cancellationToken);

protected override bool TryComputeLength(out long length)
#if NETCOREAPP
private protected override void SerializeToUtf8Stream(Stream targetStream)
{
length = 0;
return false;
JsonSerializer.Serialize(targetStream, _typedValue, _typeInfo);
}

/// <summary>
/// Based on <see cref="JsonContent.SerializeToStreamAsyncCore(Stream, bool, CancellationToken)"/>.
/// The difference is that this implementation calls overloads of <see cref="JsonSerializer"/> that take type metadata directly.
/// This is done to avoid rooting unused, built-in <see cref="System.Text.Json.Serialization.JsonConverter"/>s and reflection-based
/// warm-up logic (to reduce app size and be trim-friendly), post trimming.
/// </summary>
private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, CancellationToken cancellationToken)
{
Encoding? targetEncoding = JsonHelpers.GetEncoding(this);

// Wrap provided stream into a transcoding stream that buffers the data transcoded from utf-8 to the targetEncoding.
if (targetEncoding != null && targetEncoding != Encoding.UTF8)
{
#if NETCOREAPP
Stream transcodingStream = Encoding.CreateTranscodingStream(targetStream, targetEncoding, Encoding.UTF8, leaveOpen: true);
try
{
if (async)
{
await JsonSerializer.SerializeAsync(transcodingStream, _typedValue, _typeInfo, cancellationToken).ConfigureAwait(false);
}
else
{
JsonSerializer.Serialize(transcodingStream, _typedValue, _typeInfo);
}
}
finally
{
// Dispose/DisposeAsync will flush any partial write buffers. In practice our partial write
// buffers should be empty as we expect JsonSerializer to emit only well-formed UTF-8 data.
if (async)
{
await transcodingStream.DisposeAsync().ConfigureAwait(false);
}
else
{
transcodingStream.Dispose();
}
}
#else
Debug.Assert(async);

using (TranscodingWriteStream transcodingStream = new TranscodingWriteStream(targetStream, targetEncoding))
{
await JsonSerializer.SerializeAsync(transcodingStream, _typedValue, _typeInfo, cancellationToken).ConfigureAwait(false);
// The transcoding streams use Encoders and Decoders that have internal buffers. We need to flush these
// when there is no more data to be written. Stream.FlushAsync isn't suitable since it's
// acceptable to Flush a Stream (multiple times) prior to completion.
await transcodingStream.FinalWriteAsync(cancellationToken).ConfigureAwait(false);
}
#endif
}
else
{
if (async)
{
await JsonSerializer.SerializeAsync(targetStream, _typedValue, _typeInfo, cancellationToken).ConfigureAwait(false);
}
else
{
#if NETCOREAPP
JsonSerializer.Serialize(targetStream, _typedValue, _typeInfo);
#else
Debug.Fail("Synchronous serialization is only supported since .NET 5.0");
#endif
}
}
}
}
}
Loading