-
-
Notifications
You must be signed in to change notification settings - Fork 453
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migrate to system.text.json #2713
Conversation
# Conflicts: # lib/PuppeteerSharp/BrowserData/Firefox.cs # lib/PuppeteerSharp/Helpers/EnumHelper.cs
…koded/puppeteer-sharp into migrate-to-system.text.json
…koded/puppeteer-sharp into migrate-to-system.text.json
…koded/puppeteer-sharp into migrate-to-system.text.json
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't look at the uses of attributes on types
return JsonSerializer.Deserialize<TestExpectation[]>(fileContent, new JsonSerializerOptions() | ||
{ | ||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, | ||
Converters = | ||
{ | ||
new JsonStringEnumMemberConverter(), | ||
}, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: statically cache JsonSerializerOptions
due to CA1869
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" /> | ||
<PackageReference Include="Microsoft.Extensions.Primitives" Version="8.0.0" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems we can remove these.
They are both transitively included by PuppeteerSharp.csproj
's dependency on Microsoft.Extensions.Logging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
public static TEnum FromValueString<TEnum>(string value) | ||
where TEnum : struct, Enum | ||
{ | ||
var enumValues = _stringToEnumCache.GetOrAdd(typeof(TEnum), type => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems the two methods using _stringToEnumCache
have slightly different delegates to GetOrAdd
Perhaps out-of-scope for this PR, but we could use the trick from https://mariusgundersen.net/type-dictionary-trick/ to avoid constructing the ConcurrentDictionary
and the lookup into it.
internal static class EnumHelper
{
public static TEnum ToEnum<TEnum>(this string value)
where TEnum : struct, Enum => StringToEnum<TEnum>.Cache[value];
public static string ToValueString<TEnum>(this TEnum value)
where TEnum : struct, Enum => EnumToString<TEnum>.Cache[value];
public static TEnum FromValueString<TEnum>(string value)
where TEnum : struct, Enum
{
if (StringToEnum<TEnum>.Cache.TryGetValue(value, out var enumValue))
{
return enumValue;
}
var defaultEnumAttribute = typeof(TEnum).GetCustomAttribute<DefaultEnumValueAttribute>();
if (defaultEnumAttribute != null)
{
return (TEnum)(object)defaultEnumAttribute.Value;
}
throw new ArgumentException($"Unknown value '{value}' for enum {typeof(TEnum).Name}");
}
private static class StringToEnum<TEnum>
where TEnum : struct, Enum
{
public static readonly IReadOnlyDictionary<string, TEnum> Cache = Compute();
private static Dictionary<string, TEnum> Compute()
{
var names = Enum.GetNames(typeof(TEnum));
var values = (TEnum[])Enum.GetValues(typeof(TEnum));
var dictionary = new Dictionary<string, TEnum>(StringComparer.OrdinalIgnoreCase);
for (var i = 0; i < names.Length; i++)
{
dictionary.Add(names[i], values[i]);
var memberValue = typeof(TEnum).GetField(names[i]).GetCustomAttribute<EnumMemberAttribute>()?.Value;
if (memberValue != null)
{
dictionary[memberValue] = values[i];
}
}
return dictionary;
}
}
private static class EnumToString<TEnum>
where TEnum : struct, Enum
{
public static readonly IReadOnlyDictionary<TEnum, string> Cache = Compute();
private static Dictionary<TEnum, string> Compute()
{
var names = Enum.GetNames(typeof(TEnum));
var dictionary = new Dictionary<TEnum, string>();
foreach (var t in names)
{
var field = typeof(TEnum).GetField(t);
var valueName = field.GetCustomAttribute<EnumMemberAttribute>()?.Value ?? t;
var fieldValue = (TEnum)field.GetValue(null);
dictionary[fieldValue] = valueName;
}
return dictionary;
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Follow up PR :)
TypeInfoResolver = JsonSerializer.IsReflectionEnabledByDefault | ||
? new DefaultJsonTypeInfoResolver() | ||
: SystemTextJsonSerializationContext.Default, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we always want to use SystemTextJsonSerializationContext.Default
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that with the conditional build that makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that with this change, I would be forcing people to use declared values in the Context.
In fact, I think that if we want Evaluate
calls to work on AOT, you might need to use basic types or provide an extended context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I confirm this. If the consumer doesn't enforce this. There is no need to do this.
If the consumer does enable this. They will also have to provide a Context for any extra types they use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine with me 👍
As a follow-up later, we could perhaps change it such that all types that don't rely on user types, are (de)serialized using a source-generated JSON serializer when using the .NET8 target.
…ext.json # Conflicts: # lib/PuppeteerSharp/PuppeteerSharp.csproj
Co-authored-by: Jonas Nyrup <[email protected]>
…koded/puppeteer-sharp into migrate-to-system.text.json
Co-authored-by: campersau <[email protected]>
/// <summary> | ||
/// We need this class for AOT. | ||
/// </summary> | ||
[JsonSourceGenerationOptions(WriteIndented = true)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to write indented JSON?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not at all :)
…koded/puppeteer-sharp into migrate-to-system.text.json
This reverts commit 0d94b1b.
…koded/puppeteer-sharp into migrate-to-system.text.json
@jnyrup some updates: We can't set the JsonTypeInfoResolver by default. That would force the user to do the same thing. I added a demo in the console app. Thoughts? |
I don't have that much experience with AOT and If I have two different types |
You could pass the result a |
Description
Work in progress. Early review is welcome