-
-
Save martincostello/53b10574fbee2d8bc7b0f75bf0855a84 to your computer and use it in GitHub Desktop.
using System.Net; | |
using System.Reflection; | |
using System.Text.Json; | |
using System.Text.Json.Serialization; | |
namespace Refit; | |
/// <summary> | |
/// A class representing an implementation of <see cref="IHttpContentSerializer"/> that can be | |
/// used with the <c>System.Text.Json</c> source generators. This class cannot be inherited. | |
/// </summary> | |
/// <param name="jsonSerializerContext">The <see cref="JsonSerializerContext"/> to use.</param> | |
public sealed class SystemTextJsonContentSerializerForSourceGenerator(JsonSerializerContext jsonSerializerContext) : IHttpContentSerializer | |
{ | |
//// Based on https://github.com/reactiveui/refit/blob/main/Refit/SystemTextJsonContentSerializer.cs | |
private readonly JsonSerializerContext _jsonSerializerContext = jsonSerializerContext; | |
/// <inheritdoc /> | |
public HttpContent ToHttpContent<T>(T item) | |
{ | |
ArgumentNullException.ThrowIfNull(item); | |
#if NET8_0_OR_GREATER | |
var jsonTypeInfo = _jsonSerializerContext.GetTypeInfo(typeof(T)); | |
return System.Net.Http.Json.JsonContent.Create(item, jsonTypeInfo); | |
#else | |
return new JsonContent(item, typeof(T), _jsonSerializerContext); | |
#endif | |
} | |
/// <inheritdoc /> | |
public async Task<T?> FromHttpContentAsync<T>(HttpContent content, CancellationToken cancellationToken = default) | |
{ | |
ArgumentNullException.ThrowIfNull(content); | |
using var stream = await content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); | |
#if NET8_0_OR_GREATER | |
var jsonTypeInfo = _jsonSerializerContext.GetTypeInfo(typeof(T)) as JsonTypeInfo<T>; | |
return await JsonSerializer.DeserializeAsync(stream, jsonTypeInfo, cancellationToken).ConfigureAwait(false); | |
#else | |
object result = await JsonSerializer.DeserializeAsync( | |
stream, | |
typeof(T), | |
_jsonSerializerContext, | |
cancellationToken).ConfigureAwait(false); | |
return (T?)result; | |
#endif | |
} | |
/// <inheritdoc /> | |
public string? GetFieldNameForProperty(PropertyInfo propertyInfo) | |
{ | |
ArgumentNullException.ThrowIfNull(propertyInfo); | |
return propertyInfo | |
.GetCustomAttributes<JsonPropertyNameAttribute>(true) | |
.Select(x => x.Name) | |
.FirstOrDefault(); | |
} | |
#if !NET8_0_OR_GREATER | |
/// <summary> | |
/// Based on https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs. | |
/// </summary> | |
private sealed class JsonContent : HttpContent | |
{ | |
private readonly object _value; | |
private readonly Type _objectType; | |
private readonly JsonSerializerContext _serializerContext; | |
internal JsonContent(object inputValue, Type inputType, JsonSerializerContext context) | |
{ | |
_value = inputValue; | |
_objectType = inputType; | |
_serializerContext = context; | |
Headers.ContentType = new("application/json") { CharSet = "utf-8" }; | |
} | |
protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) | |
{ | |
return JsonSerializer.SerializeAsync(stream, _value, _objectType, _serializerContext, CancellationToken.None); | |
} | |
protected override bool TryComputeLength(out long length) | |
{ | |
length = 0; | |
return false; | |
} | |
} | |
#endif | |
} |
@martincostello Would you be able to give me an example of your JsonSerializerContext
?
Currently running into problems after migrating my app from Xamarin.iOS to .NET for iOS with my API calls using Refit. I've already created my own JsonSerializerContext
sub-class, but adding a JsonSerializable
attribute for every type that Refit needs to serialize/deserialize to, feels very obnoxious to me. Am I missing something?
I've stopped using Refit in my own code as it's not AoT-friendly due to its use of Reflection, but you can find examples of my JsonSerializerContext
classes with this GitHub code search link.
You'll need to declare as many types with attributes as are required for the source generator to be able to recursively go through the definition(s) to find every type as that's how the JSON source generator is designed to work as it needs to know ahead of time what you're going to need at runtime to generate the appropriate code. For example, if you had a class that had a property of every other type you (de)serialize, then you would only need to add an attribute for that type as it would by extension have to include those types to be able to handle that parent class. Similarly, if you serialize a T[]
, then you only need to add [JsonSerializabletypeof(T[])]
as by extension that includes typeof(T)
.
Thanks for this example! I tried it out with net8.0 and replaced ToHttpContent like:
And deleted the JsonContent implementation. Then used it like: