From 77975cada363da68838cfc87ae287e8cbfbe3d7f Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Thu, 27 Jul 2023 22:19:15 -0400 Subject: [PATCH 1/6] Add JsonContent.Create overloads which accept JsonTypeInfo --- .../ref/System.Net.Http.Json.cs | 2 + .../src/System.Net.Http.Json.csproj | 7 +- .../Json/HttpClientJsonExtensions.Patch.cs | 4 +- .../Json/HttpClientJsonExtensions.Post.cs | 4 +- .../Http/Json/HttpClientJsonExtensions.Put.cs | 4 +- .../Net/Http/Json/JsonContent.JsonTypeInfo.cs | 46 +++++++ .../Http/Json/JsonContent.JsonTypeInfoOfT.cs | 47 ++++++++ .../Net/Http/Json/JsonContent.Object.cs | 66 +++++++++++ .../src/System/Net/Http/Json/JsonContent.cs | 73 ++++++------ .../System/Net/Http/Json/JsonContentOfT.cs | 112 ------------------ .../Http/Json/JsonContentOfT.netcoreapp.cs | 18 --- .../tests/FunctionalTests/JsonContentTests.cs | 47 +++++++- 12 files changed, 254 insertions(+), 176 deletions(-) create mode 100644 src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfo.cs create mode 100644 src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfoOfT.cs create mode 100644 src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.Object.cs delete mode 100644 src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.cs delete mode 100644 src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.netcoreapp.cs diff --git a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs index efde74c2b3c49..d32b6f3f4feaf 100644 --- a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs +++ b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs @@ -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 inputValue, System.Net.Http.Headers.MediaTypeHeaderValue? mediaType = null, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } + public static System.Net.Http.Json.JsonContent Create(TValue? inputValue, System.Text.Json.Serialization.Metadata.JsonTypeInfo 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; } } diff --git a/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj b/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj index 9ed9b541f2403..f15b19085f7a5 100644 --- a/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj +++ b/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj @@ -11,6 +11,8 @@ System.Net.Http.Json.JsonContent + @@ -22,7 +24,9 @@ System.Net.Http.Json.JsonContent - + + + @@ -33,7 +37,6 @@ System.Net.Http.Json.JsonContent - diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Patch.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Patch.cs index c700474c59376..b887011c92fac 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Patch.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Patch.cs @@ -107,7 +107,7 @@ public static Task PatchAsJsonAsync(this HttpClient throw new ArgumentNullException(nameof(client)); } - JsonContent content = new(value, jsonTypeInfo); + JsonContent content = JsonContent.Create(value, jsonTypeInfo); return client.PatchAsync(requestUri, content, cancellationToken); } @@ -129,7 +129,7 @@ public static Task PatchAsJsonAsync(this HttpClient throw new ArgumentNullException(nameof(client)); } - JsonContent content = new(value, jsonTypeInfo); + JsonContent content = JsonContent.Create(value, jsonTypeInfo); return client.PatchAsync(requestUri, content, cancellationToken); } } diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Post.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Post.cs index 0ea794945dd52..ec559ef34ae95 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Post.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Post.cs @@ -54,7 +54,7 @@ public static Task PostAsJsonAsync(this HttpClient throw new ArgumentNullException(nameof(client)); } - JsonContent content = new(value, jsonTypeInfo); + JsonContent content = JsonContent.Create(value, jsonTypeInfo); return client.PostAsync(requestUri, content, cancellationToken); } @@ -65,7 +65,7 @@ public static Task PostAsJsonAsync(this HttpClient throw new ArgumentNullException(nameof(client)); } - JsonContent content = new(value, jsonTypeInfo); + JsonContent content = JsonContent.Create(value, jsonTypeInfo); return client.PostAsync(requestUri, content, cancellationToken); } } diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Put.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Put.cs index eb85ec452c9a8..5a6dab9f00dca 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Put.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.Put.cs @@ -54,7 +54,7 @@ public static Task PutAsJsonAsync(this HttpClient c throw new ArgumentNullException(nameof(client)); } - JsonContent content = new(value, jsonTypeInfo); + JsonContent content = JsonContent.Create(value, jsonTypeInfo); return client.PutAsync(requestUri, content, cancellationToken); } @@ -65,7 +65,7 @@ public static Task PutAsJsonAsync(this HttpClient c throw new ArgumentNullException(nameof(client)); } - JsonContent content = new(value, jsonTypeInfo); + JsonContent content = JsonContent.Create(value, jsonTypeInfo); return client.PutAsync(requestUri, content, cancellationToken); } } diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfo.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfo.cs new file mode 100644 index 0000000000000..cc10a15ef5b37 --- /dev/null +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfo.cs @@ -0,0 +1,46 @@ +// 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.Json; +using System.Text.Json.Serialization.Metadata; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http.Json +{ + public sealed partial class JsonContent + { + private sealed class JsonContentTypeInfoSerializer : JsonContentSerializer + { + private readonly JsonTypeInfo _typeInfo; + + public override Type ObjectType => _typeInfo.Type; + public override object? Value { get; } + + public JsonContentTypeInfoSerializer(object? inputValue, JsonTypeInfo jsonTypeInfo) + { + ThrowHelper.ThrowIfNull(jsonTypeInfo); + + _typeInfo = jsonTypeInfo; + Value = inputValue; + } + + public override Task SerializeToStreamAsync(Stream targetStream, + CancellationToken cancellationToken) + => JsonSerializer.SerializeAsync(targetStream, Value, _typeInfo, cancellationToken); + + public override void SerializeToStream(Stream targetStream) + { +#if NETCOREAPP + JsonSerializer.Serialize(targetStream, Value, _typeInfo); +#else + Debug.Fail("Synchronous serialization is only supported since .NET 5.0"); +#endif + } + } + } +} diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfoOfT.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfoOfT.cs new file mode 100644 index 0000000000000..48b7bcfe38252 --- /dev/null +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfoOfT.cs @@ -0,0 +1,47 @@ +// 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.Json; +using System.Text.Json.Serialization.Metadata; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http.Json +{ + public sealed partial class JsonContent + { + private sealed class JsonContentSerializer : JsonContentSerializer + { + private readonly JsonTypeInfo _typeInfo; + private readonly TValue _typedValue; + + public override Type ObjectType => _typeInfo.Type; + public override object? Value => _typedValue; + + public JsonContentSerializer(TValue inputValue, JsonTypeInfo jsonTypeInfo) + { + ThrowHelper.ThrowIfNull(jsonTypeInfo); + + _typeInfo = jsonTypeInfo; + _typedValue = inputValue; + } + + public override Task SerializeToStreamAsync(Stream targetStream, + CancellationToken cancellationToken) + => JsonSerializer.SerializeAsync(targetStream, _typedValue, _typeInfo, cancellationToken); + + public override void SerializeToStream(Stream targetStream) + { +#if NETCOREAPP + JsonSerializer.Serialize(targetStream, _typedValue, _typeInfo); +#else + Debug.Fail("Synchronous serialization is only supported since .NET 5.0"); +#endif + } + } + } +} diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.Object.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.Object.cs new file mode 100644 index 0000000000000..03e67ad414769 --- /dev/null +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.Object.cs @@ -0,0 +1,66 @@ +// 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.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http.Json +{ + public sealed partial class JsonContent + { + private sealed class JsonContentObjectSerializer : JsonContentSerializer + { + private readonly JsonSerializerOptions? _jsonSerializerOptions; + + public override Type ObjectType { get; } + public override object? Value { get; } + + [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] + public JsonContentObjectSerializer( + object? inputValue, + Type inputType, + JsonSerializerOptions? options) + { + ThrowHelper.ThrowIfNull(inputType); + + if (inputValue != null && !inputType.IsAssignableFrom(inputValue.GetType())) + { + throw new ArgumentException(SR.Format(SR.SerializeWrongType, inputType, inputValue.GetType())); + } + + Value = inputValue; + ObjectType = inputType; + _jsonSerializerOptions = options ?? JsonHelpers.s_defaultSerializerOptions; + } + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", + Justification = "The ctor is annotated with RequiresUnreferencedCode.")] + [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", + Justification = "The ctor is annotated with RequiresDynamicCode.")] + public override Task SerializeToStreamAsync(Stream targetStream, + CancellationToken cancellationToken) + => JsonSerializer.SerializeAsync(targetStream, Value, ObjectType, _jsonSerializerOptions, + cancellationToken); + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", + Justification = "The ctor is annotated with RequiresUnreferencedCode.")] + [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", + Justification = "The ctor is annotated with RequiresDynamicCode.")] + public override void SerializeToStream(Stream targetStream) + { +#if NETCOREAPP + JsonSerializer.Serialize(targetStream, Value, ObjectType, _jsonSerializerOptions); +#else + Debug.Fail("Synchronous serialization is only supported since .NET 5.0"); +#endif + } + } + } +} diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs index 0f5f722112e49..c62d02db917fb 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs @@ -9,6 +9,7 @@ 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; @@ -16,32 +17,17 @@ namespace System.Net.Http.Json { public sealed partial class JsonContent : HttpContent { - private readonly JsonSerializerOptions? _jsonSerializerOptions; - public Type ObjectType { get; } - public object? Value { get; } + private readonly JsonContentSerializer _serializer; - [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)); - } + public Type ObjectType => _serializer.ObjectType; + public object? Value => _serializer.Value; - if (inputValue != null && !inputType.IsAssignableFrom(inputValue.GetType())) - { - throw new ArgumentException(SR.Format(SR.SerializeWrongType, inputType, inputValue.GetType())); - } + private JsonContent(JsonContentSerializer serializer, MediaTypeHeaderValue? mediaType) + { + ThrowHelper.ThrowIfNull(serializer); - Value = inputValue; - ObjectType = inputType; + _serializer = serializer; Headers.ContentType = mediaType ?? JsonHelpers.GetDefaultMediaType(); - _jsonSerializerOptions = options ?? JsonHelpers.s_defaultSerializerOptions; } [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] @@ -52,7 +38,20 @@ public static JsonContent Create(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 JsonContent(new JsonContentObjectSerializer(inputValue, inputType, options), mediaType); + + public static JsonContent Create(TValue? inputValue, JsonTypeInfo jsonTypeInfo, + MediaTypeHeaderValue? mediaType = null) + { + JsonContentSerializer serializer = inputValue is not null + ? new JsonContentSerializer(inputValue, jsonTypeInfo) + : new JsonContentTypeInfoSerializer(null, jsonTypeInfo); + + return new JsonContent(serializer, mediaType); + } + + public static JsonContent Create(object? inputValue, JsonTypeInfo jsonTypeInfo, MediaTypeHeaderValue? mediaType = null) + => new JsonContent(new JsonContentTypeInfoSerializer(inputValue, jsonTypeInfo), mediaType); protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) => SerializeToStreamAsyncCore(stream, async: true, CancellationToken.None); @@ -63,10 +62,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); @@ -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 _serializer.SerializeToStreamAsync(transcodingStream, cancellationToken).ConfigureAwait(false); } else { - JsonSerializer.Serialize(transcodingStream, Value, ObjectType, _jsonSerializerOptions); + _serializer.SerializeToStream(transcodingStream); } } finally @@ -105,7 +100,7 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C using (TranscodingWriteStream transcodingStream = new TranscodingWriteStream(targetStream, targetEncoding)) { - await JsonSerializer.SerializeAsync(transcodingStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false); + await _serializer.SerializeToStreamAsync(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. @@ -117,17 +112,23 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C { if (async) { - await JsonSerializer.SerializeAsync(targetStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false); + await _serializer.SerializeToStreamAsync(targetStream, cancellationToken).ConfigureAwait(false); } else { -#if NETCOREAPP - JsonSerializer.Serialize(targetStream, Value, ObjectType, _jsonSerializerOptions); -#else - Debug.Fail("Synchronous serialization is only supported since .NET 5.0"); -#endif + _serializer.SerializeToStream(targetStream); } } } + + private abstract class JsonContentSerializer + { + public abstract Type ObjectType { get; } + public abstract object? Value { get; } + + public abstract Task SerializeToStreamAsync(Stream targetStream, CancellationToken cancellationToken); + + public abstract void SerializeToStream(Stream targetStream); + } } } diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.cs deleted file mode 100644 index 3e2b3467e3a3a..0000000000000 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.cs +++ /dev/null @@ -1,112 +0,0 @@ -// 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.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 : HttpContent - { - private readonly JsonTypeInfo _typeInfo; - - private readonly TValue _typedValue; - - public JsonContent(TValue inputValue, JsonTypeInfo jsonTypeInfo) - { - if (jsonTypeInfo is null) - { - throw new ArgumentNullException(nameof(jsonTypeInfo)); - } - - _typeInfo = jsonTypeInfo; - _typedValue = inputValue; - Headers.ContentType = JsonHelpers.GetDefaultMediaType(); - } - - protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) - => SerializeToStreamAsyncCore(stream, async: true, CancellationToken.None); - - protected override bool TryComputeLength(out long length) - { - length = 0; - return false; - } - - /// - /// Based on . - /// The difference is that this implementation calls overloads of that take type metadata directly. - /// This is done to avoid rooting unused, built-in s and reflection-based - /// warm-up logic (to reduce app size and be trim-friendly), post trimming. - /// - 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 - } - } - } - } -} diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.netcoreapp.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.netcoreapp.cs deleted file mode 100644 index bd6bb97da2230..0000000000000 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.netcoreapp.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace System.Net.Http.Json -{ - internal sealed partial class JsonContent - { - protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken) - => SerializeToStreamAsyncCore(stream, async: false, cancellationToken).GetAwaiter().GetResult(); - - protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken) - => SerializeToStreamAsyncCore(stream, async: true, cancellationToken); - } -} diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.cs index a23a7c803fbdd..4c1e354879d57 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.cs @@ -3,21 +3,25 @@ using System.Net.Http.Headers; using System.Net.Test.Common; -using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using System.Threading.Tasks; using Xunit; namespace System.Net.Http.Json.Functional.Tests { - public abstract class JsonContentTestsBase + public abstract partial class JsonContentTestsBase { protected abstract Task SendAsync(HttpClient client, HttpRequestMessage request); private class Foo { } private class Bar { } + [JsonSerializable(typeof(Foo))] + private partial class FooContext : JsonSerializerContext { } + [Fact] public void JsonContentObjectType() { @@ -32,6 +36,14 @@ public void JsonContentObjectType() Assert.Equal(fooType, content.ObjectType); Assert.Same(foo, content.Value); + content = JsonContent.Create(foo, FooContext.Default.Foo); + Assert.Equal(fooType, content.ObjectType); + Assert.Same(foo, content.Value); + + content = JsonContent.Create(null, FooContext.Default.Foo); + Assert.Equal(fooType, content.ObjectType); + Assert.Null(content.Value); + object fooBoxed = foo; // ObjectType is the specified type when using the .ctor. @@ -43,6 +55,16 @@ public void JsonContentObjectType() content = JsonContent.Create(fooBoxed); Assert.Equal(typeof(object), content.ObjectType); Assert.Same(fooBoxed, content.Value); + + // ObjectType is the specified type when JsonTypeInfo. + content = JsonContent.Create(fooBoxed, FooContext.Default.Foo); + Assert.Equal(fooType, content.ObjectType); + Assert.Same(fooBoxed, content.Value); + + // ObjectType is the specified type when null and JsonTypeInfo. + content = JsonContent.Create(null, FooContext.Default.Foo); + Assert.Equal(fooType, content.ObjectType); + Assert.Null(content.Value); } [Fact] @@ -67,6 +89,12 @@ public void TestJsonContentMediaType() content = JsonContent.Create(foo, mediaType: mediaType); Assert.Same(mediaType, content.Headers.ContentType); + + content = JsonContent.Create(foo, FooContext.Default.Foo, mediaType: mediaType); + Assert.Same(mediaType, content.Headers.ContentType); + + content = JsonContent.Create((object) foo, FooContext.Default.Foo, mediaType: mediaType); + Assert.Same(mediaType, content.Headers.ContentType); } [Fact] @@ -136,6 +164,14 @@ public void JsonContentMediaTypeDefaultIfNull() content = JsonContent.Create(foo, mediaType: null); Assert.Equal("application/json", content.Headers.ContentType.MediaType); Assert.Equal("utf-8", content.Headers.ContentType.CharSet); + + content = JsonContent.Create(foo, FooContext.Default.Foo, mediaType: null); + Assert.Equal("application/json", content.Headers.ContentType.MediaType); + Assert.Equal("utf-8", content.Headers.ContentType.CharSet); + + content = JsonContent.Create(null, FooContext.Default.Foo, mediaType: null); + Assert.Equal("application/json", content.Headers.ContentType.MediaType); + Assert.Equal("utf-8", content.Headers.ContentType.CharSet); } [Fact] @@ -160,6 +196,13 @@ public void JsonContentThrowsOnIncompatibleTypeAsync() } } + [Fact] + public void JsonContentTypeInfoIsNull() + { + AssertExtensions.Throws("jsonTypeInfo", () => JsonContent.Create(null, jsonTypeInfo: (JsonTypeInfo) null, mediaType: null)); + AssertExtensions.Throws("jsonTypeInfo", () => JsonContent.Create(null, jsonTypeInfo: (JsonTypeInfo) null, mediaType: null)); + } + [Fact] public async Task ValidateUtf16IsTranscodedAsync() { From d2f01c82a004dc1e8352dd0d54915439974ce7a8 Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Fri, 28 Jul 2023 08:39:01 -0400 Subject: [PATCH 2/6] Change type parameter to match other overloads --- .../System.Net.Http.Json/ref/System.Net.Http.Json.cs | 2 +- .../src/System/Net/Http/Json/JsonContent.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs index d32b6f3f4feaf..02cb67e95ccfd 100644 --- a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs +++ b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs @@ -156,7 +156,7 @@ 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 inputValue, System.Net.Http.Headers.MediaTypeHeaderValue? mediaType = null, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } - public static System.Net.Http.Json.JsonContent Create(TValue? inputValue, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Net.Http.Headers.MediaTypeHeaderValue? mediaType = null) { throw null; } + public static System.Net.Http.Json.JsonContent Create(T? inputValue, System.Text.Json.Serialization.Metadata.JsonTypeInfo 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; } diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs index c62d02db917fb..45bb51346a8b2 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs @@ -40,11 +40,11 @@ public static JsonContent Create(T inputValue, MediaTypeHeaderValue? mediaTyp public static JsonContent Create(object? inputValue, Type inputType, MediaTypeHeaderValue? mediaType = null, JsonSerializerOptions? options = null) => new JsonContent(new JsonContentObjectSerializer(inputValue, inputType, options), mediaType); - public static JsonContent Create(TValue? inputValue, JsonTypeInfo jsonTypeInfo, + public static JsonContent Create(T? inputValue, JsonTypeInfo jsonTypeInfo, MediaTypeHeaderValue? mediaType = null) { JsonContentSerializer serializer = inputValue is not null - ? new JsonContentSerializer(inputValue, jsonTypeInfo) + ? new JsonContentSerializer(inputValue, jsonTypeInfo) : new JsonContentTypeInfoSerializer(null, jsonTypeInfo); return new JsonContent(serializer, mediaType); From 96eeacebd6d039f7e978ab46af494d1cb007ac0e Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Fri, 28 Jul 2023 10:03:36 -0400 Subject: [PATCH 3/6] Feedback fixes, primarily using inheritance --- .../ref/System.Net.Http.Json.cs | 2 +- .../ref/System.Net.Http.Json.netcoreapp.cs | 2 +- .../src/System.Net.Http.Json.csproj | 5 +- .../Net/Http/Json/JsonContent.JsonTypeInfo.cs | 46 ------------ .../Http/Json/JsonContent.JsonTypeInfoOfT.cs | 47 ------------ .../Net/Http/Json/JsonContent.Object.cs | 66 ----------------- .../src/System/Net/Http/Json/JsonContent.cs | 60 +++++++--------- .../Net/Http/Json/JsonContent.netcoreapp.cs | 2 +- .../System/Net/Http/Json/JsonContentOfT.cs | 41 +++++++++++ .../Net/Http/Json/JsonContentUntyped.cs | 71 +++++++++++++++++++ 10 files changed, 144 insertions(+), 198 deletions(-) delete mode 100644 src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfo.cs delete mode 100644 src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfoOfT.cs delete mode 100644 src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.Object.cs create mode 100644 src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.cs create mode 100644 src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentUntyped.cs diff --git a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs index 02cb67e95ccfd..949b02414d7cf 100644 --- a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs +++ b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs @@ -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 ReadFromJsonAsync(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; } } diff --git a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.netcoreapp.cs b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.netcoreapp.cs index 0388fefce6a7a..01ba06f592e91 100644 --- a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.netcoreapp.cs +++ b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.netcoreapp.cs @@ -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; } diff --git a/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj b/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj index f15b19085f7a5..a81ca4ed1832f 100644 --- a/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj +++ b/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj @@ -24,9 +24,8 @@ System.Net.Http.Json.JsonContent - - - + + diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfo.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfo.cs deleted file mode 100644 index cc10a15ef5b37..0000000000000 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfo.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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.Json; -using System.Text.Json.Serialization.Metadata; -using System.Threading; -using System.Threading.Tasks; - -namespace System.Net.Http.Json -{ - public sealed partial class JsonContent - { - private sealed class JsonContentTypeInfoSerializer : JsonContentSerializer - { - private readonly JsonTypeInfo _typeInfo; - - public override Type ObjectType => _typeInfo.Type; - public override object? Value { get; } - - public JsonContentTypeInfoSerializer(object? inputValue, JsonTypeInfo jsonTypeInfo) - { - ThrowHelper.ThrowIfNull(jsonTypeInfo); - - _typeInfo = jsonTypeInfo; - Value = inputValue; - } - - public override Task SerializeToStreamAsync(Stream targetStream, - CancellationToken cancellationToken) - => JsonSerializer.SerializeAsync(targetStream, Value, _typeInfo, cancellationToken); - - public override void SerializeToStream(Stream targetStream) - { -#if NETCOREAPP - JsonSerializer.Serialize(targetStream, Value, _typeInfo); -#else - Debug.Fail("Synchronous serialization is only supported since .NET 5.0"); -#endif - } - } - } -} diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfoOfT.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfoOfT.cs deleted file mode 100644 index 48b7bcfe38252..0000000000000 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.JsonTypeInfoOfT.cs +++ /dev/null @@ -1,47 +0,0 @@ -// 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.Json; -using System.Text.Json.Serialization.Metadata; -using System.Threading; -using System.Threading.Tasks; - -namespace System.Net.Http.Json -{ - public sealed partial class JsonContent - { - private sealed class JsonContentSerializer : JsonContentSerializer - { - private readonly JsonTypeInfo _typeInfo; - private readonly TValue _typedValue; - - public override Type ObjectType => _typeInfo.Type; - public override object? Value => _typedValue; - - public JsonContentSerializer(TValue inputValue, JsonTypeInfo jsonTypeInfo) - { - ThrowHelper.ThrowIfNull(jsonTypeInfo); - - _typeInfo = jsonTypeInfo; - _typedValue = inputValue; - } - - public override Task SerializeToStreamAsync(Stream targetStream, - CancellationToken cancellationToken) - => JsonSerializer.SerializeAsync(targetStream, _typedValue, _typeInfo, cancellationToken); - - public override void SerializeToStream(Stream targetStream) - { -#if NETCOREAPP - JsonSerializer.Serialize(targetStream, _typedValue, _typeInfo); -#else - Debug.Fail("Synchronous serialization is only supported since .NET 5.0"); -#endif - } - } - } -} diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.Object.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.Object.cs deleted file mode 100644 index 03e67ad414769..0000000000000 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.Object.cs +++ /dev/null @@ -1,66 +0,0 @@ -// 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.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace System.Net.Http.Json -{ - public sealed partial class JsonContent - { - private sealed class JsonContentObjectSerializer : JsonContentSerializer - { - private readonly JsonSerializerOptions? _jsonSerializerOptions; - - public override Type ObjectType { get; } - public override object? Value { get; } - - [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] - [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] - public JsonContentObjectSerializer( - object? inputValue, - Type inputType, - JsonSerializerOptions? options) - { - ThrowHelper.ThrowIfNull(inputType); - - if (inputValue != null && !inputType.IsAssignableFrom(inputValue.GetType())) - { - throw new ArgumentException(SR.Format(SR.SerializeWrongType, inputType, inputValue.GetType())); - } - - Value = inputValue; - ObjectType = inputType; - _jsonSerializerOptions = options ?? JsonHelpers.s_defaultSerializerOptions; - } - - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "The ctor is annotated with RequiresUnreferencedCode.")] - [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", - Justification = "The ctor is annotated with RequiresDynamicCode.")] - public override Task SerializeToStreamAsync(Stream targetStream, - CancellationToken cancellationToken) - => JsonSerializer.SerializeAsync(targetStream, Value, ObjectType, _jsonSerializerOptions, - cancellationToken); - - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "The ctor is annotated with RequiresUnreferencedCode.")] - [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", - Justification = "The ctor is annotated with RequiresDynamicCode.")] - public override void SerializeToStream(Stream targetStream) - { -#if NETCOREAPP - JsonSerializer.Serialize(targetStream, Value, ObjectType, _jsonSerializerOptions); -#else - Debug.Fail("Synchronous serialization is only supported since .NET 5.0"); -#endif - } - } - } -} diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs index 45bb51346a8b2..bc2cdfb1c958c 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs @@ -15,18 +15,16 @@ namespace System.Net.Http.Json { - public sealed partial class JsonContent : HttpContent + public abstract partial class JsonContent : HttpContent { - private readonly JsonContentSerializer _serializer; + public Type ObjectType => JsonTypeInfo.Type; + public object? Value => ValueCore; - public Type ObjectType => _serializer.ObjectType; - public object? Value => _serializer.Value; + private protected abstract JsonTypeInfo JsonTypeInfo { get; } + private protected abstract object? ValueCore { get; } - private JsonContent(JsonContentSerializer serializer, MediaTypeHeaderValue? mediaType) + private protected JsonContent(MediaTypeHeaderValue? mediaType) { - ThrowHelper.ThrowIfNull(serializer); - - _serializer = serializer; Headers.ContentType = mediaType ?? JsonHelpers.GetDefaultMediaType(); } @@ -38,20 +36,16 @@ public static JsonContent Create(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(new JsonContentObjectSerializer(inputValue, inputType, options), mediaType); + => new JsonContentUntyped(inputValue, inputType, options, mediaType); public static JsonContent Create(T? inputValue, JsonTypeInfo jsonTypeInfo, MediaTypeHeaderValue? mediaType = null) - { - JsonContentSerializer serializer = inputValue is not null - ? new JsonContentSerializer(inputValue, jsonTypeInfo) - : new JsonContentTypeInfoSerializer(null, jsonTypeInfo); - - return new JsonContent(serializer, mediaType); - } + => inputValue is not null + ? new JsonContent(inputValue, jsonTypeInfo, mediaType) + : new JsonContentUntyped(null, jsonTypeInfo, mediaType); public static JsonContent Create(object? inputValue, JsonTypeInfo jsonTypeInfo, MediaTypeHeaderValue? mediaType = null) - => new JsonContent(new JsonContentTypeInfoSerializer(inputValue, jsonTypeInfo), mediaType); + => new JsonContentUntyped(inputValue, jsonTypeInfo, mediaType); protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) => SerializeToStreamAsyncCore(stream, async: true, CancellationToken.None); @@ -62,6 +56,12 @@ protected override bool TryComputeLength(out long length) return false; } + 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); @@ -75,11 +75,11 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C { if (async) { - await _serializer.SerializeToStreamAsync(transcodingStream, cancellationToken).ConfigureAwait(false); + await SerializeToUtf8StreamAsync(transcodingStream, cancellationToken).ConfigureAwait(false); } else { - _serializer.SerializeToStream(transcodingStream); + SerializeToUtf8Stream(transcodingStream); } } finally @@ -96,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 _serializer.SerializeToStreamAsync(transcodingStream, 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. @@ -112,23 +112,17 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C { if (async) { - await _serializer.SerializeToStreamAsync(targetStream, cancellationToken).ConfigureAwait(false); + await SerializeToUtf8StreamAsync(targetStream, cancellationToken).ConfigureAwait(false); } else { - _serializer.SerializeToStream(targetStream); +#if NETCOREAPP + SerializeToUtf8Stream(targetStream); +#else + Debug.Fail("HttpContent synchronous serialization is only supported since .NET 5.0"); +#endif } } } - - private abstract class JsonContentSerializer - { - public abstract Type ObjectType { get; } - public abstract object? Value { get; } - - public abstract Task SerializeToStreamAsync(Stream targetStream, CancellationToken cancellationToken); - - public abstract void SerializeToStream(Stream targetStream); - } } } diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.netcoreapp.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.netcoreapp.cs index 7520cd74ec5ae..e1f1e3fd1e962 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.netcoreapp.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.netcoreapp.cs @@ -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(); diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.cs new file mode 100644 index 0000000000000..f81041a17383b --- /dev/null +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +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 class JsonContent : JsonContent + { + private readonly JsonTypeInfo _typeInfo; + private readonly TValue _typedValue; + + private protected override JsonTypeInfo JsonTypeInfo => _typeInfo; + private protected override object? ValueCore => _typedValue; + + public JsonContent(TValue inputValue, JsonTypeInfo jsonTypeInfo, MediaTypeHeaderValue? mediaType) + : base(mediaType) + { + ThrowHelper.ThrowIfNull(jsonTypeInfo); + + _typeInfo = jsonTypeInfo; + _typedValue = inputValue; + } + + private protected override Task SerializeToUtf8StreamAsync(Stream targetStream, + CancellationToken cancellationToken) + => JsonSerializer.SerializeAsync(targetStream, _typedValue, _typeInfo, cancellationToken); + +#if NETCOREAPP + private protected override void SerializeToUtf8Stream(Stream targetStream) + { + JsonSerializer.Serialize(targetStream, _typedValue, _typeInfo); + } +#endif + } +} diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentUntyped.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentUntyped.cs new file mode 100644 index 0000000000000..fc2ed963a2e6b --- /dev/null +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentUntyped.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.IO; +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 class JsonContentUntyped : JsonContent + { + private protected override JsonTypeInfo JsonTypeInfo { get; } + private protected override object? ValueCore { get; } + + [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] + public JsonContentUntyped( + object? inputValue, + Type inputType, + JsonSerializerOptions? options, + MediaTypeHeaderValue? mediaType = null) + : this(inputValue, GetJsonTypeInfo(inputType, options), mediaType) { } + + public JsonContentUntyped( + object? inputValue, + JsonTypeInfo jsonTypeInfo, + MediaTypeHeaderValue? mediaType = null) + : base(mediaType) + { + ThrowHelper.ThrowIfNull(jsonTypeInfo); + + if (inputValue != null && !jsonTypeInfo.Type.IsAssignableFrom(inputValue.GetType())) + { + throw new ArgumentException(SR.Format(SR.SerializeWrongType, jsonTypeInfo.Type, inputValue.GetType())); + } + + JsonTypeInfo = jsonTypeInfo; + ValueCore = inputValue; + } + + private protected override Task SerializeToUtf8StreamAsync(Stream targetStream, + CancellationToken cancellationToken) + => JsonSerializer.SerializeAsync(targetStream, ValueCore, JsonTypeInfo, + cancellationToken); + +#if NETCOREAPP + private protected override void SerializeToUtf8Stream(Stream targetStream) + { + JsonSerializer.Serialize(targetStream, ValueCore, JsonTypeInfo); + } +#endif + + [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] + private static JsonTypeInfo GetJsonTypeInfo(Type inputType, JsonSerializerOptions? options) + { + ThrowHelper.ThrowIfNull(inputType); + + // Ensure the options supports the call to GetTypeInfo + options ??= JsonHelpers.s_defaultSerializerOptions; + options.TypeInfoResolver ??= JsonSerializerOptions.Default.TypeInfoResolver; + options.MakeReadOnly(); + + return options.GetTypeInfo(inputType); + } + } +} From ea587150cb8d452770bea4db3466b138eee8bb95 Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Fri, 28 Jul 2023 13:34:17 -0400 Subject: [PATCH 4/6] Remove polymorphism --- .../ref/System.Net.Http.Json.cs | 2 +- .../ref/System.Net.Http.Json.netcoreapp.cs | 2 +- .../src/System.Net.Http.Json.csproj | 2 - .../src/System/Net/Http/Json/JsonContent.cs | 67 ++++++++++------- .../Net/Http/Json/JsonContent.netcoreapp.cs | 2 +- .../System/Net/Http/Json/JsonContentOfT.cs | 41 ----------- .../Net/Http/Json/JsonContentUntyped.cs | 71 ------------------- 7 files changed, 44 insertions(+), 143 deletions(-) delete mode 100644 src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.cs delete mode 100644 src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentUntyped.cs diff --git a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs index 949b02414d7cf..02cb67e95ccfd 100644 --- a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs +++ b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.cs @@ -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 ReadFromJsonAsync(this System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } - public abstract partial class JsonContent : System.Net.Http.HttpContent + public sealed partial class JsonContent : System.Net.Http.HttpContent { internal JsonContent() { } public System.Type ObjectType { get { throw null; } } diff --git a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.netcoreapp.cs b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.netcoreapp.cs index 01ba06f592e91..0388fefce6a7a 100644 --- a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.netcoreapp.cs +++ b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.netcoreapp.cs @@ -10,7 +10,7 @@ namespace System.Net.Http.Json { - public abstract partial class JsonContent : System.Net.Http.HttpContent + public sealed 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; } diff --git a/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj b/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj index a81ca4ed1832f..08375656e0598 100644 --- a/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj +++ b/src/libraries/System.Net.Http.Json/src/System.Net.Http.Json.csproj @@ -24,8 +24,6 @@ System.Net.Http.Json.JsonContent - - diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs index bc2cdfb1c958c..2048c20ec8f60 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs @@ -15,37 +15,44 @@ namespace System.Net.Http.Json { - public abstract partial class JsonContent : HttpContent + public sealed partial class JsonContent : HttpContent { - public Type ObjectType => JsonTypeInfo.Type; - public object? Value => ValueCore; + private readonly JsonTypeInfo _typeInfo; + public Type ObjectType => _typeInfo.Type; + public object? Value { get; } - private protected abstract JsonTypeInfo JsonTypeInfo { get; } - private protected abstract object? ValueCore { get; } - - private protected JsonContent(MediaTypeHeaderValue? mediaType) + private JsonContent( + object? inputValue, + JsonTypeInfo jsonTypeInfo, + MediaTypeHeaderValue? mediaType) { + ThrowHelper.ThrowIfNull(jsonTypeInfo); + + if (inputValue != null && !jsonTypeInfo.Type.IsAssignableFrom(inputValue.GetType())) + { + throw new ArgumentException(SR.Format(SR.SerializeWrongType, jsonTypeInfo.Type, inputValue.GetType())); + } + + Value = inputValue; + _typeInfo = jsonTypeInfo; Headers.ContentType = mediaType ?? JsonHelpers.GetDefaultMediaType(); } [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] public static JsonContent Create(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 JsonContentUntyped(inputValue, inputType, options, mediaType); + => Create(inputValue, GetJsonTypeInfo(inputType, options), mediaType); - public static JsonContent Create(T? inputValue, JsonTypeInfo jsonTypeInfo, - MediaTypeHeaderValue? mediaType = null) - => inputValue is not null - ? new JsonContent(inputValue, jsonTypeInfo, mediaType) - : new JsonContentUntyped(null, jsonTypeInfo, mediaType); + public static JsonContent Create(T? inputValue, JsonTypeInfo jsonTypeInfo, MediaTypeHeaderValue? mediaType = null) + => new JsonContent(inputValue, jsonTypeInfo, mediaType); public static JsonContent Create(object? inputValue, JsonTypeInfo jsonTypeInfo, MediaTypeHeaderValue? mediaType = null) - => new JsonContentUntyped(inputValue, jsonTypeInfo, mediaType); + => new JsonContent(inputValue, jsonTypeInfo, mediaType); protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) => SerializeToStreamAsyncCore(stream, async: true, CancellationToken.None); @@ -56,12 +63,6 @@ protected override bool TryComputeLength(out long length) return false; } - 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); @@ -75,11 +76,11 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C { if (async) { - await SerializeToUtf8StreamAsync(transcodingStream, cancellationToken).ConfigureAwait(false); + await JsonSerializer.SerializeAsync(transcodingStream, Value, _typeInfo, cancellationToken).ConfigureAwait(false); } else { - SerializeToUtf8Stream(transcodingStream); + JsonSerializer.Serialize(transcodingStream, Value, _typeInfo); } } finally @@ -100,7 +101,7 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C using (TranscodingWriteStream transcodingStream = new TranscodingWriteStream(targetStream, targetEncoding)) { - await SerializeToUtf8StreamAsync(transcodingStream, 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. @@ -112,17 +113,31 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C { if (async) { - await SerializeToUtf8StreamAsync(targetStream, cancellationToken).ConfigureAwait(false); + await JsonSerializer.SerializeAsync(targetStream, Value, _typeInfo, cancellationToken).ConfigureAwait(false); } else { #if NETCOREAPP - SerializeToUtf8Stream(targetStream); + JsonSerializer.Serialize(targetStream, Value, _typeInfo); #else 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) + { + ThrowHelper.ThrowIfNull(inputType); + + // Ensure the options supports the call to GetTypeInfo + options ??= JsonHelpers.s_defaultSerializerOptions; + options.TypeInfoResolver ??= JsonSerializerOptions.Default.TypeInfoResolver; + options.MakeReadOnly(); + + return options.GetTypeInfo(inputType); + } } } diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.netcoreapp.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.netcoreapp.cs index e1f1e3fd1e962..7520cd74ec5ae 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.netcoreapp.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.netcoreapp.cs @@ -7,7 +7,7 @@ namespace System.Net.Http.Json { - public abstract partial class JsonContent + public sealed partial class JsonContent { protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken) => SerializeToStreamAsyncCore(stream, async: false, cancellationToken).GetAwaiter().GetResult(); diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.cs deleted file mode 100644 index f81041a17383b..0000000000000 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentOfT.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; -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 class JsonContent : JsonContent - { - private readonly JsonTypeInfo _typeInfo; - private readonly TValue _typedValue; - - private protected override JsonTypeInfo JsonTypeInfo => _typeInfo; - private protected override object? ValueCore => _typedValue; - - public JsonContent(TValue inputValue, JsonTypeInfo jsonTypeInfo, MediaTypeHeaderValue? mediaType) - : base(mediaType) - { - ThrowHelper.ThrowIfNull(jsonTypeInfo); - - _typeInfo = jsonTypeInfo; - _typedValue = inputValue; - } - - private protected override Task SerializeToUtf8StreamAsync(Stream targetStream, - CancellationToken cancellationToken) - => JsonSerializer.SerializeAsync(targetStream, _typedValue, _typeInfo, cancellationToken); - -#if NETCOREAPP - private protected override void SerializeToUtf8Stream(Stream targetStream) - { - JsonSerializer.Serialize(targetStream, _typedValue, _typeInfo); - } -#endif - } -} diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentUntyped.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentUntyped.cs deleted file mode 100644 index fc2ed963a2e6b..0000000000000 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContentUntyped.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; -using System.IO; -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 class JsonContentUntyped : JsonContent - { - private protected override JsonTypeInfo JsonTypeInfo { get; } - private protected override object? ValueCore { get; } - - [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] - [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] - public JsonContentUntyped( - object? inputValue, - Type inputType, - JsonSerializerOptions? options, - MediaTypeHeaderValue? mediaType = null) - : this(inputValue, GetJsonTypeInfo(inputType, options), mediaType) { } - - public JsonContentUntyped( - object? inputValue, - JsonTypeInfo jsonTypeInfo, - MediaTypeHeaderValue? mediaType = null) - : base(mediaType) - { - ThrowHelper.ThrowIfNull(jsonTypeInfo); - - if (inputValue != null && !jsonTypeInfo.Type.IsAssignableFrom(inputValue.GetType())) - { - throw new ArgumentException(SR.Format(SR.SerializeWrongType, jsonTypeInfo.Type, inputValue.GetType())); - } - - JsonTypeInfo = jsonTypeInfo; - ValueCore = inputValue; - } - - private protected override Task SerializeToUtf8StreamAsync(Stream targetStream, - CancellationToken cancellationToken) - => JsonSerializer.SerializeAsync(targetStream, ValueCore, JsonTypeInfo, - cancellationToken); - -#if NETCOREAPP - private protected override void SerializeToUtf8Stream(Stream targetStream) - { - JsonSerializer.Serialize(targetStream, ValueCore, JsonTypeInfo); - } -#endif - - [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] - [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] - private static JsonTypeInfo GetJsonTypeInfo(Type inputType, JsonSerializerOptions? options) - { - ThrowHelper.ThrowIfNull(inputType); - - // Ensure the options supports the call to GetTypeInfo - options ??= JsonHelpers.s_defaultSerializerOptions; - options.TypeInfoResolver ??= JsonSerializerOptions.Default.TypeInfoResolver; - options.MakeReadOnly(); - - return options.GetTypeInfo(inputType); - } - } -} From 7ff614f43268e73e07b0dd2464cc37c0500b2905 Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Mon, 31 Jul 2023 10:21:29 -0400 Subject: [PATCH 5/6] Address constructor feedback --- .../src/System/Net/Http/Json/JsonContent.cs | 45 +++++++++++++------ .../tests/FunctionalTests/JsonContentTests.cs | 12 ++++- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs index 2048c20ec8f60..17be2035d9275 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs @@ -1,9 +1,7 @@ // 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; @@ -26,12 +24,7 @@ private JsonContent( JsonTypeInfo jsonTypeInfo, MediaTypeHeaderValue? mediaType) { - ThrowHelper.ThrowIfNull(jsonTypeInfo); - - if (inputValue != null && !jsonTypeInfo.Type.IsAssignableFrom(inputValue.GetType())) - { - throw new ArgumentException(SR.Format(SR.SerializeWrongType, jsonTypeInfo.Type, inputValue.GetType())); - } + Debug.Assert(jsonTypeInfo is not null); Value = inputValue; _typeInfo = jsonTypeInfo; @@ -46,13 +39,29 @@ public static JsonContent Create(T inputValue, MediaTypeHeaderValue? mediaTyp [RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] public static JsonContent Create(object? inputValue, Type inputType, MediaTypeHeaderValue? mediaType = null, JsonSerializerOptions? options = null) - => Create(inputValue, GetJsonTypeInfo(inputType, options), mediaType); + { + ThrowHelper.ThrowIfNull(inputType); + EnsureTypeCompatibility(inputValue, inputType); + + return new JsonContent(inputValue, GetJsonTypeInfo(inputType, options), mediaType); + } - public static JsonContent Create(T? inputValue, JsonTypeInfo jsonTypeInfo, MediaTypeHeaderValue? mediaType = null) - => new JsonContent(inputValue, jsonTypeInfo, mediaType); + public static JsonContent Create(T? inputValue, JsonTypeInfo jsonTypeInfo, + MediaTypeHeaderValue? mediaType = null) + { + ThrowHelper.ThrowIfNull(jsonTypeInfo); - public static JsonContent Create(object? inputValue, JsonTypeInfo jsonTypeInfo, MediaTypeHeaderValue? mediaType = null) - => new JsonContent(inputValue, jsonTypeInfo, mediaType); + 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); @@ -130,7 +139,7 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, C [RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)] private static JsonTypeInfo GetJsonTypeInfo(Type inputType, JsonSerializerOptions? options) { - ThrowHelper.ThrowIfNull(inputType); + Debug.Assert(inputType is not null); // Ensure the options supports the call to GetTypeInfo options ??= JsonHelpers.s_defaultSerializerOptions; @@ -139,5 +148,13 @@ private static JsonTypeInfo GetJsonTypeInfo(Type inputType, JsonSerializerOption 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())); + } + } } } diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.cs index 4c1e354879d57..5e87891e53131 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.cs @@ -20,6 +20,7 @@ private class Foo { } private class Bar { } [JsonSerializable(typeof(Foo))] + [JsonSerializable(typeof(Bar))] private partial class FooContext : JsonSerializerContext { } [Fact] @@ -185,14 +186,21 @@ public void JsonContentThrowsOnIncompatibleTypeAsync() { var foo = new Foo(); Type typeOfBar = typeof(Bar); + string strTypeOfBar = typeOfBar.ToString(); + // Validate for reflection Exception ex = Assert.Throws(() => JsonContent.Create(foo, typeOfBar)); - - string strTypeOfBar = typeOfBar.ToString(); Assert.Contains(strTypeOfBar, ex.Message); string afterInputTypeMessage = ex.Message.Split(strTypeOfBar.ToCharArray())[1]; Assert.Contains(afterInputTypeMessage, ex.Message); + + // Validate for weakly-typed JsonTypeInfo + ex = Assert.Throws(() => JsonContent.Create((object) foo, FooContext.Default.Bar)); + Assert.Contains(strTypeOfBar, ex.Message); + + afterInputTypeMessage = ex.Message.Split(strTypeOfBar.ToCharArray())[1]; + Assert.Contains(afterInputTypeMessage, ex.Message); } } From 70508833a401bf572bd3bd3e99a2a263645462f7 Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Mon, 31 Jul 2023 10:40:44 -0400 Subject: [PATCH 6/6] Update src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs Co-authored-by: Eirik Tsarpalis --- .../System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs index 17be2035d9275..08573d5ffbbcd 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs @@ -25,6 +25,7 @@ private JsonContent( MediaTypeHeaderValue? mediaType) { Debug.Assert(jsonTypeInfo is not null); + Debug.Assert(inputValue is null || jsonTypeInfo.Type.IsAssignableFrom(inputValue.GetType())); Value = inputValue; _typeInfo = jsonTypeInfo;