Skip to content

Commit

Permalink
Merge pull request #75 from gregsdennis/nullable-ref-types
Browse files Browse the repository at this point in the history
Nullable ref types (and some schema things)
  • Loading branch information
gregsdennis committed Feb 24, 2021
2 parents eea9ccc + 7899b40 commit 431def1
Show file tree
Hide file tree
Showing 162 changed files with 1,081 additions and 498 deletions.
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

0 comments on commit 431def1

Please sign in to comment.