Skip to content

Commit

Permalink
Add JsonContent.Create overloads which accept JsonTypeInfo (#89614)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
brantburnett and eiriktsarpalis authored Jul 31, 2023
1 parent 9059941 commit 7e2eb6b
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 175 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -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 @@ -22,7 +24,6 @@ System.Net.Http.Json.JsonContent</PackageDescription>
<Compile Include="System\Net\Http\Json\HttpClientJsonExtensions.Patch.cs" />
<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\LengthLimitReadStream.cs" />
</ItemGroup>

Expand All @@ -33,7 +34,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
@@ -1,58 +1,68 @@
// 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.Diagnostics.CodeAnalysis;
using System.IO;
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
{
private readonly JsonSerializerOptions? _jsonSerializerOptions;
public Type ObjectType { get; }
private readonly JsonTypeInfo _typeInfo;
public Type ObjectType => _typeInfo.Type;
public object? Value { get; }

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
private JsonContent(
object? inputValue,
Type inputType,
MediaTypeHeaderValue? mediaType,
JsonSerializerOptions? options)
JsonTypeInfo jsonTypeInfo,
MediaTypeHeaderValue? mediaType)
{
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()));
}
Debug.Assert(jsonTypeInfo is not null);
Debug.Assert(inputValue is null || jsonTypeInfo.Type.IsAssignableFrom(inputValue.GetType()));

Value = inputValue;
ObjectType = inputType;
_typeInfo = jsonTypeInfo;
Headers.ContentType = mediaType ?? JsonHelpers.GetDefaultMediaType();
_jsonSerializerOptions = options ?? JsonHelpers.s_defaultSerializerOptions;
}

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
public static JsonContent Create<T>(T inputValue, MediaTypeHeaderValue? mediaType = null, JsonSerializerOptions? options = null)
=> Create(inputValue, typeof(T), mediaType, options);
=> Create(inputValue, GetJsonTypeInfo(typeof(T), options), mediaType);

[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);
{
ThrowHelper.ThrowIfNull(inputType);
EnsureTypeCompatibility(inputValue, inputType);

return new JsonContent(inputValue, GetJsonTypeInfo(inputType, options), mediaType);
}

public static JsonContent Create<T>(T? inputValue, JsonTypeInfo<T> jsonTypeInfo,
MediaTypeHeaderValue? mediaType = null)
{
ThrowHelper.ThrowIfNull(jsonTypeInfo);

return new JsonContent(inputValue, jsonTypeInfo, mediaType);
}

public static JsonContent Create(object? inputValue, JsonTypeInfo jsonTypeInfo,
MediaTypeHeaderValue? mediaType = null)
{
ThrowHelper.ThrowIfNull(jsonTypeInfo);
EnsureTypeCompatibility(inputValue, jsonTypeInfo.Type);

return new JsonContent(inputValue, jsonTypeInfo, mediaType);
}

protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context)
=> SerializeToStreamAsyncCore(stream, async: true, CancellationToken.None);
Expand All @@ -63,10 +73,6 @@ 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 async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, CancellationToken cancellationToken)
{
Encoding? targetEncoding = JsonHelpers.GetEncoding(this);
Expand All @@ -80,11 +86,11 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C
{
if (async)
{
await JsonSerializer.SerializeAsync(transcodingStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false);
await JsonSerializer.SerializeAsync(transcodingStream, Value, _typeInfo, cancellationToken).ConfigureAwait(false);
}
else
{
JsonSerializer.Serialize(transcodingStream, Value, ObjectType, _jsonSerializerOptions);
JsonSerializer.Serialize(transcodingStream, Value, _typeInfo);
}
}
finally
Expand All @@ -101,11 +107,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 JsonSerializer.SerializeAsync(transcodingStream, Value, _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.
Expand All @@ -117,17 +123,39 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C
{
if (async)
{
await JsonSerializer.SerializeAsync(targetStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false);
await JsonSerializer.SerializeAsync(targetStream, Value, _typeInfo, cancellationToken).ConfigureAwait(false);
}
else
{
#if NETCOREAPP
JsonSerializer.Serialize(targetStream, Value, ObjectType, _jsonSerializerOptions);
JsonSerializer.Serialize(targetStream, Value, _typeInfo);
#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
}
}
}

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
private static JsonTypeInfo GetJsonTypeInfo(Type inputType, JsonSerializerOptions? options)
{
Debug.Assert(inputType is not null);

// Ensure the options supports the call to GetTypeInfo
options ??= JsonHelpers.s_defaultSerializerOptions;
options.TypeInfoResolver ??= JsonSerializerOptions.Default.TypeInfoResolver;
options.MakeReadOnly();

return options.GetTypeInfo(inputType);
}

private static void EnsureTypeCompatibility(object? inputValue, Type inputType)
{
if (inputValue is not null && !inputType.IsAssignableFrom(inputValue.GetType()))
{
throw new ArgumentException(SR.Format(SR.SerializeWrongType, inputType, inputValue.GetType()));
}
}
}
}

This file was deleted.

This file was deleted.

Loading

0 comments on commit 7e2eb6b

Please sign in to comment.