Skip to content

Commit

Permalink
[JSON source gen 3/3] Add new methods to JsonSerializer and System.Ne…
Browse files Browse the repository at this point in the history
…t.Http.Json APIs that take type metadata (dotnet#51528)

* Add new methods to JsonSerializer and System.Net.Http.Json APIs that take type metadata

* Address review feedback
  • Loading branch information
layomia authored Apr 20, 2021
1 parent 466deef commit 24966ab
Show file tree
Hide file tree
Showing 41 changed files with 2,004 additions and 322 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ namespace System.Net.Http.Json
public static partial class HttpClientJsonExtensions
{
public static System.Threading.Tasks.Task<object?> GetFromJsonAsync(this System.Net.Http.HttpClient client, string? requestUri, System.Type type, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<object?> GetFromJsonAsync(this System.Net.Http.HttpClient client, string? requestUri, System.Type type, System.Text.Json.Serialization.JsonSerializerContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<object?> GetFromJsonAsync(this System.Net.Http.HttpClient client, string? requestUri, System.Type type, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<object?> GetFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Type type, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<object?> GetFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Type type, System.Text.Json.Serialization.JsonSerializerContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<object?> GetFromJsonAsync(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Type type, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<TValue?> GetFromJsonAsync<TValue>(this System.Net.Http.HttpClient client, string? requestUri, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<TValue?> GetFromJsonAsync<TValue>(this System.Net.Http.HttpClient client, string? requestUri, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue> jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<TValue?> GetFromJsonAsync<TValue>(this System.Net.Http.HttpClient client, string? requestUri, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<TValue?> GetFromJsonAsync<TValue>(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Text.Json.JsonSerializerOptions? options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<TValue?> GetFromJsonAsync<TValue>(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue> jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<TValue?> GetFromJsonAsync<TValue>(this System.Net.Http.HttpClient client, System.Uri? requestUri, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> PostAsJsonAsync<TValue>(this System.Net.Http.HttpClient client, string? requestUri, TValue value, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> PostAsJsonAsync<TValue>(this System.Net.Http.HttpClient client, string? requestUri, TValue value, System.Threading.CancellationToken cancellationToken) { throw null; }
Expand All @@ -28,7 +32,9 @@ public static partial class HttpClientJsonExtensions
public static partial class HttpContentJsonExtensions
{
public static System.Threading.Tasks.Task<object?> ReadFromJsonAsync(this System.Net.Http.HttpContent content, System.Type type, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<object?> ReadFromJsonAsync(this System.Net.Http.HttpContent content, System.Type type, System.Text.Json.Serialization.JsonSerializerContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<T?> ReadFromJsonAsync<T>(this System.Net.Http.HttpContent content, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<T?> ReadFromJsonAsync<T>(this System.Net.Http.HttpContent content, System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public sealed partial class JsonContent : System.Net.Http.HttpContent
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -56,6 +58,50 @@ public static partial class HttpClientJsonExtensions
return GetFromJsonAsyncCore<TValue>(taskResponse, options, cancellationToken);
}

public static Task<object?> GetFromJsonAsync(this HttpClient client, string? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return GetFromJsonAsyncCore(taskResponse, type, context, cancellationToken);
}

public static Task<object?> GetFromJsonAsync(this HttpClient client, Uri? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return GetFromJsonAsyncCore(taskResponse, type, context, cancellationToken);
}

public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, string? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return GetFromJsonAsyncCore(taskResponse, jsonTypeInfo, cancellationToken);
}

public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, Uri? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return GetFromJsonAsyncCore<TValue>(taskResponse, jsonTypeInfo, cancellationToken);
}

public static Task<object?> GetFromJsonAsync(this HttpClient client, string? requestUri, Type type, CancellationToken cancellationToken = default)
=> client.GetFromJsonAsync(requestUri, type, options: null, cancellationToken);

Expand Down Expand Up @@ -91,5 +137,23 @@ public static partial class HttpClientJsonExtensions
return await response.Content!.ReadFromJsonAsync<T>(options, cancellationToken).ConfigureAwait(false);
}
}

private static async Task<object?> GetFromJsonAsyncCore(Task<HttpResponseMessage> taskResponse, Type type, JsonSerializerContext context, CancellationToken cancellationToken)
{
using (HttpResponseMessage response = await taskResponse.ConfigureAwait(false))
{
response.EnsureSuccessStatusCode();
return await response.Content!.ReadFromJsonAsync(type, context, cancellationToken).ConfigureAwait(false);
}
}

private static async Task<T?> GetFromJsonAsyncCore<T>(Task<HttpResponseMessage> taskResponse, JsonTypeInfo<T> jsonTypeInfo, CancellationToken cancellationToken)
{
using (HttpResponseMessage response = await taskResponse.ConfigureAwait(false))
{
response.EnsureSuccessStatusCode();
return await response.Content!.ReadFromJsonAsync<T>(jsonTypeInfo, cancellationToken).ConfigureAwait(false);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.IO;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -37,21 +39,61 @@ public static partial class HttpContentJsonExtensions

private static async Task<object?> ReadFromJsonAsyncCore(HttpContent content, Type type, Encoding? sourceEncoding, JsonSerializerOptions? options, CancellationToken cancellationToken)
{
Stream contentStream = await ReadHttpContentStreamAsync(content, cancellationToken).ConfigureAwait(false);
using (Stream contentStream = await GetContentStream(content, sourceEncoding, cancellationToken).ConfigureAwait(false))
{
return await JsonSerializer.DeserializeAsync(contentStream, type, options ?? JsonContent.s_defaultSerializerOptions, cancellationToken).ConfigureAwait(false);
}
}

// Wrap content stream into a transcoding stream that buffers the data transcoded from the sourceEncoding to utf-8.
if (sourceEncoding != null && sourceEncoding != Encoding.UTF8)
private static async Task<T?> ReadFromJsonAsyncCore<T>(HttpContent content, Encoding? sourceEncoding, JsonSerializerOptions? options, CancellationToken cancellationToken)
{
using (Stream contentStream = await GetContentStream(content, sourceEncoding, cancellationToken).ConfigureAwait(false))
{
contentStream = GetTranscodingStream(contentStream, sourceEncoding);
return await JsonSerializer.DeserializeAsync<T>(contentStream, options ?? JsonContent.s_defaultSerializerOptions, cancellationToken).ConfigureAwait(false);
}
}

using (contentStream)
public static Task<object?> ReadFromJsonAsync(this HttpContent content, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default)
{
if (content == null)
{
return await JsonSerializer.DeserializeAsync(contentStream, type, options ?? JsonContent.s_defaultSerializerOptions, cancellationToken).ConfigureAwait(false);
throw new ArgumentNullException(nameof(content));
}

Encoding? sourceEncoding = JsonContent.GetEncoding(content.Headers.ContentType?.CharSet);

return ReadFromJsonAsyncCore(content, type, sourceEncoding, context, cancellationToken);
}

private static async Task<T?> ReadFromJsonAsyncCore<T>(HttpContent content, Encoding? sourceEncoding, JsonSerializerOptions? options, CancellationToken cancellationToken)
public static Task<T?> ReadFromJsonAsync<T>(this HttpContent content, JsonTypeInfo<T> jsonTypeInfo, CancellationToken cancellationToken = default)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}

Encoding? sourceEncoding = JsonContent.GetEncoding(content.Headers.ContentType?.CharSet);

return ReadFromJsonAsyncCore<T>(content, sourceEncoding, jsonTypeInfo, cancellationToken);
}

private static async Task<object?> ReadFromJsonAsyncCore(HttpContent content, Type type, Encoding? sourceEncoding, JsonSerializerContext context, CancellationToken cancellationToken)
{
using (Stream contentStream = await GetContentStream(content, sourceEncoding, cancellationToken).ConfigureAwait(false))
{
return await JsonSerializer.DeserializeAsync(contentStream, type, context, cancellationToken).ConfigureAwait(false);
}
}

private static async Task<T?> ReadFromJsonAsyncCore<T>(HttpContent content, Encoding? sourceEncoding, JsonTypeInfo<T> jsonTypeInfo, CancellationToken cancellationToken)
{
using (Stream contentStream = await GetContentStream(content, sourceEncoding, cancellationToken).ConfigureAwait(false))
{
return await JsonSerializer.DeserializeAsync<T>(contentStream, jsonTypeInfo, cancellationToken).ConfigureAwait(false);
}
}

private static async Task<Stream> GetContentStream(HttpContent content, Encoding? sourceEncoding, CancellationToken cancellationToken)
{
Stream contentStream = await ReadHttpContentStreamAsync(content, cancellationToken).ConfigureAwait(false);

Expand All @@ -61,10 +103,7 @@ public static partial class HttpContentJsonExtensions
contentStream = GetTranscodingStream(contentStream, sourceEncoding);
}

using (contentStream)
{
return await JsonSerializer.DeserializeAsync<T>(contentStream, options ?? JsonContent.s_defaultSerializerOptions, cancellationToken).ConfigureAwait(false);
}
return contentStream;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading.Tasks;
using Xunit;
using System.Collections.Generic;
using System.Linq;
using System.Net.Test.Common;
using System.Text.Json;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace System.Net.Http.Json.Functional.Tests
{
public class HttpClientJsonExtensionsTests
{
[Theory]
[MemberData(nameof(ReadFromJsonTestData))]
public async Task TestGetFromJsonAsync(string json)
public async Task TestGetFromJsonAsync(string json, bool containsQuotedNumbers)
{
HttpHeaderData header = new HttpHeaderData("Content-Type", "application/json");
List<HttpHeaderData> headers = new List<HttpHeaderData> { header };
Expand All @@ -36,6 +36,21 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync(

per = await client.GetFromJsonAsync<Person>(uri.ToString());
per.Validate();

if (!containsQuotedNumbers)
{
per = (Person)await client.GetFromJsonAsync(uri, typeof(Person), JsonContext.Default);
per.Validate();

per = (Person)await client.GetFromJsonAsync(uri.ToString(), typeof(Person), JsonContext.Default);
per.Validate();

per = await client.GetFromJsonAsync<Person>(uri, JsonContext.Default.Person);
per.Validate();

per = await client.GetFromJsonAsync<Person>(uri.ToString(), JsonContext.Default.Person);
per.Validate();
}
}
},
server => server.HandleRequestAsync(content: json, headers: headers));
Expand All @@ -44,8 +59,8 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync(
public static IEnumerable<object[]> ReadFromJsonTestData()
{
Person per = Person.Create();
yield return new object[] { per.Serialize() };
yield return new object[] { per.SerializeWithNumbersAsStrings() };
yield return new object[] { per.Serialize(), false };
yield return new object[] { per.SerializeWithNumbersAsStrings(), true };
}

[Fact]
Expand All @@ -58,6 +73,8 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync(
{
await Assert.ThrowsAsync<HttpRequestException>(() => client.GetFromJsonAsync(uri, typeof(Person)));
await Assert.ThrowsAsync<HttpRequestException>(() => client.GetFromJsonAsync<Person>(uri));
await Assert.ThrowsAsync<HttpRequestException>(() => client.GetFromJsonAsync(uri, typeof(Person), JsonContext.Default));
await Assert.ThrowsAsync<HttpRequestException>(() => client.GetFromJsonAsync<Person>(uri, JsonContext.Default.Person));
}
},
server => server.HandleRequestAsync(statusCode: HttpStatusCode.InternalServerError));
Expand Down Expand Up @@ -170,6 +187,8 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync(

Person per = Assert.IsType<Person>(await client.GetFromJsonAsync((string)null, typeof(Person)));
per = Assert.IsType<Person>(await client.GetFromJsonAsync((Uri)null, typeof(Person)));
per = Assert.IsType<Person>(await client.GetFromJsonAsync((string)null, typeof(Person), JsonContext.Default));
per = Assert.IsType<Person>(await client.GetFromJsonAsync((Uri)null, typeof(Person), JsonContext.Default));

per = await client.GetFromJsonAsync<Person>((string)null);
per = await client.GetFromJsonAsync<Person>((Uri)null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;

namespace System.Net.Http.Json.Functional.Tests
{
internal partial class JsonContext : JsonSerializerContext
{
private JsonTypeInfo<int> _Int32;
public JsonTypeInfo<int> Int32
{
get
{
if (_Int32 == null)
{
JsonConverter customConverter;
if (Options.Converters.Count > 0 && (customConverter = GetRuntimeProvidedCustomConverter(typeof(int))) != null)
{
_Int32 = JsonMetadataServices.CreateValueInfo<int>(Options, customConverter);
}
else
{
_Int32 = JsonMetadataServices.CreateValueInfo<int>(Options, JsonMetadataServices.Int32Converter);
}
}

return _Int32;
}
}
}
}
Loading

0 comments on commit 24966ab

Please sign in to comment.