Skip to content
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

Nullable ref types (and some schema things) #75

Merged
merged 17 commits into from Feb 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
75 changes: 45 additions & 30 deletions Json.More/EnumStringConverter.cs
Expand Up @@ -33,12 +33,32 @@ namespace Json.More
public class EnumStringConverter<T> : JsonConverter<T>
where T : Enum
{
private static Dictionary<string, T> _readValues;
private static Dictionary<T, string> _writeValues;
private static Func<T, T, T> _aggregator;
private static Dictionary<string, T>? _readValues;
private static Dictionary<T, string>? _writeValues;
private static Func<T, T, T>? _aggregator;
// ReSharper disable once StaticMemberInGenericType
private static readonly object _lock = new object();

private static Dictionary<string, T> ReadValues
{
get
{
EnsureMap();
return _readValues!;
}
}

private static Dictionary<T, string> WriteValues
{
get
{
EnsureMap();
return _writeValues!;
}
}

private static Func<T, T, T> Aggregator => _aggregator ??= BuildAggregator();

/// <summary>Reads and converts the JSON to type <typeparamref name="T" />.</summary>
/// <param name="reader">The reader.</param>
/// <param name="typeToConvert">The type to convert.</param>
Expand All @@ -60,22 +80,22 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial
{
str = reader.GetString();

if (!_readValues.TryGetValue(str, out var immediate))
if (!ReadValues.TryGetValue(str, out var immediate))
throw new JsonException($"Could not find appropriate value for {str} in type {typeToConvert.Name}");

values.Add(immediate);
reader.Read();
}
reader.Read(); // waste the end array

return ToCombined(values);
return values.Aggregate(Aggregator);
}
throw new JsonException("Expected string");
}

str = reader.GetString();

return _readValues.TryGetValue(str, out var value)
return ReadValues.TryGetValue(str, out var value)
? value
: throw new JsonException($"Could not find appropriate value for {str} in type {typeToConvert.Name}");
}
Expand All @@ -88,43 +108,38 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions
{
EnsureMap();

if (typeof(T).GetCustomAttribute<FlagsAttribute>() != null && !_writeValues.ContainsKey(value))
if (typeof(T).GetCustomAttribute<FlagsAttribute>() != null && !WriteValues.ContainsKey(value))
{
writer.WriteStartArray();
foreach (var name in _writeValues.Keys)
foreach (var name in WriteValues.Keys)
{
if (value.HasFlag(name))
writer.WriteStringValue(_writeValues[name]);
writer.WriteStringValue(WriteValues[name]);
}
writer.WriteEndArray();
return;
}

writer.WriteStringValue(_writeValues[value]);
writer.WriteStringValue(WriteValues[value]);
}

private static T ToCombined(IEnumerable<T> list)
private static Func<T, T, T> BuildAggregator()
{
if (_aggregator == null)
{
var underlyingType = Enum.GetUnderlyingType(typeof(T));
var currentParameter = Expression.Parameter(typeof(T), "current");
var nextParameter = Expression.Parameter(typeof(T), "next");

_aggregator = Expression.Lambda<Func<T, T, T>>(
Expression.Convert(
Expression.Or(
Expression.Convert(currentParameter, underlyingType),
Expression.Convert(nextParameter, underlyingType)
),
typeof(T)
var underlyingType = Enum.GetUnderlyingType(typeof(T));
var currentParameter = Expression.Parameter(typeof(T), "current");
var nextParameter = Expression.Parameter(typeof(T), "next");

return Expression.Lambda<Func<T, T, T>>(
Expression.Convert(
Expression.Or(
Expression.Convert(currentParameter, underlyingType),
Expression.Convert(nextParameter, underlyingType)
),
currentParameter,
nextParameter
).Compile();
}

return list.Aggregate(_aggregator);
typeof(T)
),
currentParameter,
nextParameter
).Compile();
}

private static void EnsureMap()
Expand Down
6 changes: 4 additions & 2 deletions Json.More/Json.More.csproj
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Json.More.Net</PackageId>
<Authors>Greg Dennis</Authors>
<Version>1.3.0</Version>
<Version>1.4.0</Version>
<Description>Provides extended functionality for the System.Text.Json namespace.</Description>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageProjectUrl>https://github.com/gregsdennis/json-everything</PackageProjectUrl>
Expand All @@ -14,7 +14,7 @@
<PackageReleaseNotes>https://gregsdennis.github.io/json-everything/release-notes/json-more.html</PackageReleaseNotes>
<LangVersion>latest</LangVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.3.0.0</FileVersion>
<FileVersion>1.4.0.0</FileVersion>
<DocumentationFile>Json.More.xml</DocumentationFile>
<PackageIcon>json-logo-256.png</PackageIcon>
<IncludeSymbols>true</IncludeSymbols>
Expand All @@ -23,6 +23,7 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../json-everything.snk</AssemblyOriginatorKeyFile>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
Expand All @@ -31,6 +32,7 @@
</ItemGroup>

<ItemGroup>
<None Include="..\docs_source\release-notes\json-more.md" Link="json-more.md" />
<None Include="..\LICENSE">
<Pack>True</Pack>
<PackagePath></PackagePath>
Expand Down
10 changes: 9 additions & 1 deletion Json.More/Json.More.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Json.More/JsonDocumentExtensions.cs
Expand Up @@ -25,7 +25,7 @@ public static bool IsEquivalentTo(this JsonDocument a, JsonDocument b)
/// <param name="value">The value to convert.</param>
/// <param name="options">(optional) JSON serialization options.</param>
/// <returns>A <see cref="JsonDocument"/> representing the vale.</returns>
public static JsonDocument ToJsonDocument<T>(this T value, JsonSerializerOptions options = null)
public static JsonDocument ToJsonDocument<T>(this T value, JsonSerializerOptions? options = null)
{
return JsonDocument.Parse(JsonSerializer.Serialize(value, options));
}
Expand Down
76 changes: 74 additions & 2 deletions Json.More/JsonElementExtensions.cs
Expand Up @@ -52,6 +52,78 @@ public static bool IsEquivalentTo(this JsonElement a, JsonElement b)
}
}

// source: https://stackoverflow.com/a/60592310/878701, modified for netstandard2.0
// license: https://creativecommons.org/licenses/by-sa/4.0/
/// <summary>
/// Generate a consistent JSON-value-based hash code for the element.
/// </summary>
/// <param name="element">The element.</param>
/// <param name="maxHashDepth">Maximum depth to calculate. Default is -1 which utilizes the entire structure without limitation.</param>
/// <returns>The hash code.</returns>
/// <remarks>
/// See the following for discussion on why the default implementation is insufficient:
///
/// - https://github.com/gregsdennis/json-everything/issues/76
/// - https://github.com/dotnet/runtime/issues/33388
/// </remarks>
public static int GetEquivalenceHashCode(this JsonElement element, int maxHashDepth = -1)
{
static void Add(ref int current, object? newValue)
{
unchecked
{
current = current * 397 ^ (newValue?.GetHashCode() ?? 0);
}
}

void ComputeHashCode(JsonElement obj, ref int current, int depth)
{
Add(ref current, obj.ValueKind);

switch (obj.ValueKind)
{
case JsonValueKind.Null:
case JsonValueKind.True:
case JsonValueKind.False:
case JsonValueKind.Undefined:
break;

case JsonValueKind.Number:
Add(ref current, obj.GetRawText());
break;

case JsonValueKind.String:
Add(ref current, obj.GetString());
break;

case JsonValueKind.Array:
if (depth != maxHashDepth)
foreach (var item in obj.EnumerateArray())
ComputeHashCode(item, ref current, depth + 1);
else
Add(ref current, obj.GetArrayLength());
break;

case JsonValueKind.Object:
foreach (var property in obj.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal))
{
Add(ref current, property.Name);
if (depth != maxHashDepth)
ComputeHashCode(property.Value, ref current, depth + 1);
}
break;

default:
throw new JsonException($"Unknown JsonValueKind {obj.ValueKind}");
}
}

var hash = 0;
ComputeHashCode(element, ref hash, 0);
return hash;

}

/// <summary>
/// Just a shortcut for calling `JsonSerializer.Serialize()` because `.ToString()` doesn't do what you might expect.
/// </summary>
Expand Down Expand Up @@ -150,12 +222,12 @@ public static JsonElement AsJsonElement(this float value)
}

/// <summary>
/// Converts a <see cref="string"/> to a <see cref="JsonElement"/>.
/// Converts a <see cref="string"/> to a <see cref="JsonElement"/>. Can also be used to get a `null` element.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <returns>A <see cref="JsonElement"/> representing the value.</returns>
/// <remarks>This is a workaround for lack of native support in the System.Text.Json namespace.</remarks>
public static JsonElement AsJsonElement(this string value)
public static JsonElement AsJsonElement(this string? value)
{
var doc = JsonDocument.Parse(JsonSerializer.Serialize(value));
return doc.RootElement;
Expand Down
4 changes: 2 additions & 2 deletions JsonLogic/JsonElementExtensions.cs
Expand Up @@ -22,7 +22,7 @@ public static bool IsTruthy(this JsonElement element)
};
}

public static string Stringify(this JsonElement element)
public static string? Stringify(this JsonElement element)
{
return element.ValueKind switch
{
Expand Down Expand Up @@ -58,7 +58,7 @@ public static IEnumerable<JsonElement> Flatten(this JsonElement root)
// Ported from: https://github.com/marvindv/jsonlogic_rs/blob/b2ad93af575f19c6b220a6a54d599e104e72a630/src/operators/logic.rs#L33
public static bool LooseEquals(this JsonElement a, JsonElement b)
{
string CoerceArrayToString(JsonElement array) => string.Join(",", array.EnumerateArray().Select(e => e.ToJsonString()));
static string CoerceArrayToString(JsonElement array) => string.Join(",", array.EnumerateArray().Select(e => e.ToJsonString()));

return (a.ValueKind, b.ValueKind) switch
{
Expand Down
8 changes: 5 additions & 3 deletions JsonLogic/JsonLogic.csproj
Expand Up @@ -14,24 +14,26 @@
<PackageTags>json logic json-logic jsonlogic</PackageTags>
<PackageReleaseNotes>https://gregsdennis.github.io/json-everything/release-notes/json-logic.html</PackageReleaseNotes>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.2.0</Version>
<Version>1.2.1</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.2.0.0</FileVersion>
<FileVersion>1.2.1.0</FileVersion>
<DocumentationFile>JsonLogic.xml</DocumentationFile>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../json-everything.snk</AssemblyOriginatorKeyFile>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Text.Json" Version="4.7.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>

<ItemGroup>
<None Include="..\docs_source\release-notes\json-logic.md" Link="json-logic.md" />
<None Include="..\LICENSE">
<Pack>True</Pack>
<PackagePath></PackagePath>
Expand Down
8 changes: 4 additions & 4 deletions JsonLogic/Rule.cs
Expand Up @@ -34,7 +34,7 @@ public abstract class Rule
/// Casts a `string` value to a <see cref="LiteralRule"/>. Can also be used to create a `null` JSON literal.
/// </summary>
/// <param name="value">The value.</param>
public static implicit operator Rule(string value) => new LiteralRule(value);
public static implicit operator Rule(string? value) => new LiteralRule(value);
/// <summary>
/// Casts a `bool` value to a <see cref="LiteralRule"/>.
/// </summary>
Expand Down Expand Up @@ -86,7 +86,7 @@ public override Rule Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSer

var ruleType = RuleRegistry.GetRule(ruleInfo.Key);

var value = ruleInfo.Value ?? new ArgumentCollection((Rule) null);
var value = ruleInfo.Value ?? new ArgumentCollection((Rule?) null);

return (Rule) Activator.CreateInstance(ruleType,
value.Cast<object>()
Expand All @@ -107,9 +107,9 @@ public override void Write(Utf8JsonWriter writer, Rule value, JsonSerializerOpti
[JsonConverter(typeof(ArgumentCollectionConverter))]
internal class ArgumentCollection : List<Rule>
{
public ArgumentCollection(Rule single)
public ArgumentCollection(Rule? single)
{
Add(single);
Add(single!);
}

public ArgumentCollection(IEnumerable<Rule> components)
Expand Down
2 changes: 1 addition & 1 deletion JsonLogic/RuleRegistry.cs
Expand Up @@ -22,7 +22,7 @@ public static class RuleRegistry
/// </summary>
/// <param name="identifier">The identifier.</param>
/// <returns>The <see cref="Type"/> of the rule.</returns>
public static Type GetRule(string identifier)
public static Type? GetRule(string identifier)
{
return _rules.TryGetValue(identifier, out var t) ? t : null;
}
Expand Down
6 changes: 3 additions & 3 deletions JsonLogic/Rules/IfRule.cs
Expand Up @@ -22,7 +22,7 @@ public override JsonElement Apply(JsonElement data)
switch (_components.Count)
{
case 0:
return ((string) null).AsJsonElement();
return ((string?) null).AsJsonElement();
case 1:
return _components[0].Apply(data);
case 2:
Expand All @@ -31,7 +31,7 @@ public override JsonElement Apply(JsonElement data)

return condition
? thenResult.Apply(data)
: ((string) null).AsJsonElement();
: ((string?) null).AsJsonElement();
default:
var currentCondition = _components[0];
var currentTrueResult = _components[1];
Expand All @@ -44,7 +44,7 @@ public override JsonElement Apply(JsonElement data)
if (condition)
return currentTrueResult.Apply(data);

if (elseIndex == _components.Count) return ((string) null).AsJsonElement();
if (elseIndex == _components.Count) return ((string?) null).AsJsonElement();

currentCondition = _components[elseIndex++];

Expand Down