Commit: c157a01
Parent: 5ec4220

Parse ingredients

Mårten Åsberg committed on 2026-06-26 at 18:53
Directory.Build.props +1 -1
diff --git a/Directory.Build.props b/Directory.Build.props
index 11aba5b..066a318 100644
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
Directory.Packages.props +3 -1
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 829b2c8..ed20ced 100644
@@ -8,9 +8,11 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</GlobalPackageReference>
<PackageVersion Include="FsCheck" Version="3.3.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
<PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.8.0" />
<PackageVersion Include="Microsoft.Testing.Extensions.TrxReport" Version="2.2.3" />
<PackageVersion Include="MSTest" Version="4.2.3" />
<PackageVersion Include="Shouldly" Version="4.3.0" />
</ItemGroup>
</Project>
</Project>
\ No newline at end of file
global.json +3 -0
diff --git a/global.json b/global.json
index 2439bbe..6730e43 100644
@@ -3,5 +3,8 @@
"version": "11.0.100-preview.5.26302.115",
"allowPrerelease": true,
"rollForward": "disable"
},
"test": {
"runner": "Microsoft.Testing.Platform"
}
}
src/CompilerServices/CompilerFeatureRequiredAttribute.cs +0 -37
diff --git a/src/CompilerServices/CompilerFeatureRequiredAttribute.cs b/src/CompilerServices/CompilerFeatureRequiredAttribute.cs
deleted file mode 100644
index d7d21f6..0000000
@@ -1,37 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Runtime.CompilerServices
{
/// <summary>
/// Indicates that compiler support for a particular feature is required for the location where this attribute is applied.
/// </summary>
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
public sealed class CompilerFeatureRequiredAttribute : Attribute
{
public CompilerFeatureRequiredAttribute(string featureName)
{
FeatureName = featureName;
}
/// <summary>
/// The name of the compiler feature.
/// </summary>
public string FeatureName { get; }
/// <summary>
/// If true, the compiler can choose to allow access to the location where this attribute is applied if it does not understand <see cref="FeatureName"/>.
/// </summary>
public bool IsOptional { get; init; }
/// <summary>
/// The <see cref="FeatureName"/> used for the ref structs C# feature.
/// </summary>
public const string RefStructs = nameof(RefStructs);
/// <summary>
/// The <see cref="FeatureName"/> used for the required members C# feature.
/// </summary>
public const string RequiredMembers = nameof(RequiredMembers);
}
}
src/CompilerServices/IsExternalInit.cs +0 -14
diff --git a/src/CompilerServices/IsExternalInit.cs b/src/CompilerServices/IsExternalInit.cs
deleted file mode 100644
index 56b012e..0000000
@@ -1,14 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
namespace System.Runtime.CompilerServices
{
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class IsExternalInit { }
}
src/CompilerServices/RequiredMemberAttribute.cs +0 -16
diff --git a/src/CompilerServices/RequiredMemberAttribute.cs b/src/CompilerServices/RequiredMemberAttribute.cs
deleted file mode 100644
index 8fc5eea..0000000
@@ -1,16 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
namespace System.Runtime.CompilerServices
{
/// <summary>Specifies that a type has required members or that a member is required.</summary>
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property,
AllowMultiple = false,
Inherited = false
)]
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class RequiredMemberAttribute : Attribute { }
}
src/CompilerServices/packages.lock.json +1 -15
diff --git a/src/CompilerServices/packages.lock.json b/src/CompilerServices/packages.lock.json
index fabeb49..28ce62a 100644
@@ -1,26 +1,12 @@
{
"version": 2,
"dependencies": {
".NETStandard,Version=v2.0": {
"net10.0": {
"CSharpier.MsBuild": {
"type": "Direct",
"requested": "[1.3.0, )",
"resolved": "1.3.0",
"contentHash": "jzzLpmz7F6LyBh5FTZy8/4dEYVcAG9VjBPKryZ82LIoUuupcBkCSXzaeS/4oYcp/rBC8yVLcDstJ8CIv9n2IFw=="
},
"NETStandard.Library": {
"type": "Direct",
"requested": "[2.0.3, )",
"resolved": "2.0.3",
"contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0"
}
},
"Microsoft.NETCore.Platforms": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
}
}
}
src/JsonLdRecipeParser/Amount.cs +97 -2
diff --git a/src/JsonLdRecipeParser/Amount.cs b/src/JsonLdRecipeParser/Amount.cs
index abccf8b..c1106ed 100644
@@ -1,9 +1,104 @@
using System;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.RegularExpressions;
namespace JsonLdRecipeParser;
public class Amount
public partial record Amount
{
private static readonly SearchValues<char> DecimalSeparatorCharacters = SearchValues.Create(",.\u066b");
private static readonly SearchValues<char> WhitespaceCharacters = SearchValues.Create(" \t");
[GeneratedRegex(@"^\s*(?<a>\d+(?:[,.\u066b]\d+)?)(?:\s*-\s*(?<b>\d+(?:[,.\u066b]\d+)?))?")]
private static partial Regex Pattern { get; }
public required AmountValue Value { get; set; }
public required string Unit { get; set; }
public string? Unit { get; set; }
public static bool TryParse(string text, [NotNullWhen(true)] out Amount? amount)
{
var span = text.AsSpan();
return TryParse(ref span, out amount);
}
public static bool TryParse(ref ReadOnlySpan<char> text, [NotNullWhen(true)] out Amount? amount)
{
if (!TryParseValue(ref text, out var value))
{
amount = default;
return false;
}
_ = TryParseUnit(ref text, out var unit);
amount = new() { Value = value, Unit = unit };
return true;
}
private static bool TryParseValue(ref ReadOnlySpan<char> text, [NotNullWhen(true)] out AmountValue value)
{
var match = Pattern.Match(text.ToString());
if (!match.Success)
{
value = default;
return false;
}
var aText = match.Groups["a"].ValueSpan;
var bText = match.Groups["b"].ValueSpan;
if (!TryParseNumber(aText, out var a))
{
value = default;
return false;
}
if (bText.IsEmpty)
{
value = new ExactAmountValue(a);
text = text[(match.Index + match.Length)..];
return true;
}
if (TryParseNumber(bText, out var b))
{
value = new RangeAmountValue(a, b);
text = text[(match.Index + match.Length)..];
return true;
}
value = default;
return false;
}
private static bool TryParseNumber(ReadOnlySpan<char> input, out double value)
{
Span<char> text = stackalloc char[input.Length];
input.ReplaceAny(text, DecimalSeparatorCharacters, '.');
return double.TryParse(text, CultureInfo.InvariantCulture, out value);
}
private static bool TryParseUnit(ref ReadOnlySpan<char> text, [NotNullWhen(true)] out string? unit)
{
var start = text.IndexOfAnyExcept(WhitespaceCharacters);
if (start == -1)
{
unit = default;
return false;
}
text = text[start..];
var end = text.IndexOfAny(WhitespaceCharacters);
if (end == -1)
{
end = text.Length;
}
unit = text[..end].ToString();
text = text[end..];
return true;
}
}
public closed record AmountValue;
src/JsonLdRecipeParser/Ingredient.cs +49 -1
diff --git a/src/JsonLdRecipeParser/Ingredient.cs b/src/JsonLdRecipeParser/Ingredient.cs
index 9ea0dfa..e0bf3b4 100644
@@ -1,7 +1,55 @@
using System;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
namespace JsonLdRecipeParser;
public class Ingredient
public record Ingredient
{
private static readonly SearchValues<char> WhitespaceCharacters = SearchValues.Create(" \t");
public required string Name { get; set; }
public Amount? Amount { get; set; }
public static bool TryParse(string text, [NotNullWhen(true)] out Ingredient? ingredient)
{
var span = text.AsSpan();
_ = Amount.TryParse(ref span, out var amount);
if (TryParseIngredient(ref span, out var name))
{
ingredient = new() { Name = name, Amount = amount };
return true;
}
if (amount?.Unit is string unitName)
{
ingredient = new() { Name = unitName, Amount = amount with { Unit = null } };
return true;
}
ingredient = default;
return false;
}
private static bool TryParseIngredient(ref ReadOnlySpan<char> text, [NotNullWhen(true)] out string? name)
{
var start = text.IndexOfAnyExcept(WhitespaceCharacters);
if (start == -1)
{
name = default;
return false;
}
text = text[start..];
var end = text.IndexOfAny(WhitespaceCharacters);
if (end == -1)
{
end = text.Length;
}
name = text[..end].ToString();
text = text[end..];
return true;
}
}
src/JsonLdRecipeParser/packages.lock.json +1 -15
diff --git a/src/JsonLdRecipeParser/packages.lock.json b/src/JsonLdRecipeParser/packages.lock.json
index f3d98d7..243053e 100644
@@ -1,27 +1,13 @@
{
"version": 2,
"dependencies": {
".NETStandard,Version=v2.0": {
"net10.0": {
"CSharpier.MsBuild": {
"type": "Direct",
"requested": "[1.3.0, )",
"resolved": "1.3.0",
"contentHash": "jzzLpmz7F6LyBh5FTZy8/4dEYVcAG9VjBPKryZ82LIoUuupcBkCSXzaeS/4oYcp/rBC8yVLcDstJ8CIv9n2IFw=="
},
"NETStandard.Library": {
"type": "Direct",
"requested": "[2.0.3, )",
"resolved": "2.0.3",
"contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0"
}
},
"Microsoft.NETCore.Platforms": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
},
"System.Runtime.CompilerServices": {
"type": "Project"
}
test/JsonLdRecipeParser.Tests/AmountTests/TryParseShould.cs +210 -0
diff --git a/test/JsonLdRecipeParser.Tests/AmountTests/TryParseShould.cs b/test/JsonLdRecipeParser.Tests/AmountTests/TryParseShould.cs
new file mode 100644
index 0000000..b749da4
@@ -0,0 +1,210 @@
using System;
using System.Globalization;
using System.Linq;
using FsCheck;
using FsCheck.Fluent;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
namespace JsonLdRecipeParser.Tests.AmountTests;
[TestClass]
public class TryParseShould
{
[TestMethod]
public void ReturnFalseForEmptyStrings()
{
// Arrange
var text = "";
// Act
var success = Amount.TryParse(text, out _);
// Assert
success.ShouldBeFalse();
}
[TestMethod]
[DataRow(" ")]
[DataRow("\t")]
public void ReturnFalseForWhiteSpaceStrings(string input)
{
// Arrange
var text = input;
// Act
var success = Amount.TryParse(text, out _);
// Assert
success.ShouldBeFalse();
}
[TestMethod]
public void ReturnUnitlessAmountForIntegerString()
{
// Arrange
var text = "100";
// Act
var success = Amount.TryParse(text, out var amount);
// Assert
success.ShouldBeTrue();
amount.ShouldNotBeNull();
amount.Value.ShouldBe(new ExactAmountValue(100.0d));
amount.Unit.ShouldBeNull();
}
[TestMethod]
public void ReturnUnitlessAmountForRealStringWithPeriodDecimalSeparator()
{
// Arrange
var text = "2.5";
// Act
var success = Amount.TryParse(text, out var amount);
// Assert
success.ShouldBeTrue();
amount.ShouldNotBeNull();
amount.Value.ShouldBe(new ExactAmountValue(2.5d));
amount.Unit.ShouldBeNull();
}
[TestMethod]
public void ReturnUnitlessAmountForRealStringWithCommaDecimalSeparator()
{
// Arrange
var text = "2,5";
// Act
var success = Amount.TryParse(text, out var amount);
// Assert
success.ShouldBeTrue();
amount.ShouldNotBeNull();
amount.Value.ShouldBe(new ExactAmountValue(2.5d));
amount.Unit.ShouldBeNull();
}
[TestMethod]
public void ReturnUnitlessAmountForIntegerStringWithTrailingWhitespace()
{
// Arrange
var text = "100 ";
// Act
var success = Amount.TryParse(text, out var amount);
// Assert
success.ShouldBeTrue();
amount.ShouldNotBeNull();
amount.Value.ShouldBe(new ExactAmountValue(100.0d));
amount.Unit.ShouldBeNull();
}
[TestMethod]
public void ReturnUnitlessAmountForAnyRealStringAndAnyCulture() =>
Prop.ForAll(
Gen.Choose(0, 1_000_000_000).Select(i => double.Round(i / 1000.0d, 3)).ToArbitrary(),
Gen.Elements(CultureInfo.GetCultures(CultureTypes.AllCultures)).ToArbitrary(),
(number, cultureInfo) =>
{
// Arrange
var text = number.ToString(cultureInfo);
// Act
var success = Amount.TryParse(text, out var amount);
// Assert
success.ShouldBeTrue();
amount.ShouldNotBeNull();
var exactAmountValue = amount.Value.ShouldBeOfType<ExactAmountValue>();
exactAmountValue.Value.ShouldBe(number);
amount.Unit.ShouldBeNull();
}
)
.QuickCheckThrowOnFailure();
[TestMethod]
public void ReturnUnitlessRangeAmountForIntegerRange()
{
// Arrange
var text = "100-150";
// Act
var success = Amount.TryParse(text, out var amount);
// Assert
success.ShouldBeTrue();
amount.ShouldNotBeNull();
var range = amount.Value.ShouldBeOfType<RangeAmountValue>();
range.From.ShouldBe(100.0d);
range.To.ShouldBe(150.0d);
}
[TestMethod]
public void ReturnUnitlessRangeAmountForDecimalRange()
{
// Arrange
var text = "2,5-4.5";
// Act
var success = Amount.TryParse(text, out var amount);
// Assert
success.ShouldBeTrue();
amount.ShouldNotBeNull();
var range = amount.Value.ShouldBeOfType<RangeAmountValue>();
range.From.ShouldBe(2.5d);
range.To.ShouldBe(4.5d);
}
[TestMethod]
public void ReturnUnitWithWhitespace()
{
// Arrange
var text = "10 dl";
// Act
var success = Amount.TryParse(text, out var amount);
// Assert
success.ShouldBeTrue();
amount.ShouldNotBeNull();
amount.Value.ShouldBe(new ExactAmountValue(10.0d));
amount.Unit.ShouldBe("dl");
}
[TestMethod]
public void ReturnUnitWithoutWhitespace()
{
// Arrange
var text = "250g";
// Act
var success = Amount.TryParse(text, out var amount);
// Assert
success.ShouldBeTrue();
amount.ShouldNotBeNull();
amount.Value.ShouldBe(new ExactAmountValue(250.0d));
amount.Unit.ShouldBe("g");
}
[TestMethod]
public void ReturnUnitWithTrailingWhitespace()
{
// Arrange
var text = "250g ";
// Act
var success = Amount.TryParse(text, out var amount);
// Assert
success.ShouldBeTrue();
amount.ShouldNotBeNull();
amount.Value.ShouldBe(new ExactAmountValue(250.0d));
amount.Unit.ShouldBe("g");
}
}
test/JsonLdRecipeParser.Tests/IngredientTests/TryParseShould.cs +87 -0
diff --git a/test/JsonLdRecipeParser.Tests/IngredientTests/TryParseShould.cs b/test/JsonLdRecipeParser.Tests/IngredientTests/TryParseShould.cs
new file mode 100644
index 0000000..7fd7452
@@ -0,0 +1,87 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
namespace JsonLdRecipeParser.Tests.IngredientTests;
[TestClass]
public class TryParseShould
{
[TestMethod]
public void ReturnFalseForEmptyStrings()
{
// Arrange
var text = "";
// Act
var success = Ingredient.TryParse(text, out _);
// Assert
success.ShouldBeFalse();
}
[TestMethod]
[DataRow(" ")]
[DataRow("\t")]
public void ReturnFalseForWhiteSpaceStrings(string input)
{
// Arrange
var text = input;
// Act
var success = Ingredient.TryParse(text, out _);
// Assert
success.ShouldBeFalse();
}
[TestMethod]
public void ReturnIngredientNameWithoutAmount()
{
// Arrange
var text = "sugar";
// Act
var success = Ingredient.TryParse(text, out var ingredient);
// Assert
success.ShouldBeTrue();
ingredient.ShouldNotBeNull();
ingredient.Name.ShouldBe("sugar");
}
[TestMethod]
public void ReturnIngredientNameAmountWithUnit()
{
// Arrange
var text = "12g sugar";
// Act
var success = Ingredient.TryParse(text, out var ingredient);
// Assert
success.ShouldBeTrue();
ingredient.ShouldNotBeNull();
ingredient.Amount.ShouldNotBeNull();
ingredient.Amount.Value.ShouldBe(new ExactAmountValue(12.0d));
ingredient.Amount.Unit.ShouldBe("g");
ingredient.Name.ShouldBe("sugar");
}
[TestMethod]
public void ReturnIngredientNameAndAmountWithoutUnit()
{
// Arrange
var text = "1 citron";
// Act
var success = Ingredient.TryParse(text, out var ingredient);
// Assert
success.ShouldBeTrue();
ingredient.ShouldNotBeNull();
ingredient.Amount.ShouldNotBeNull();
ingredient.Amount.Value.ShouldBe(new ExactAmountValue(1.0d));
ingredient.Amount.Unit.ShouldBeNull();
ingredient.Name.ShouldBe("citron");
}
}
test/JsonLdRecipeParser.Tests/JsonLdRecipeParser.Tests.csproj +2 -1
diff --git a/test/JsonLdRecipeParser.Tests/JsonLdRecipeParser.Tests.csproj b/test/JsonLdRecipeParser.Tests/JsonLdRecipeParser.Tests.csproj
index 1c33584..2c7743b 100644
@@ -2,7 +2,6 @@
<PropertyGroup>
<RootNamespace>JsonLdRecipeParser.Tests</RootNamespace>
<AssemblyName>JsonLdRecipeParser.Tests</AssemblyName>
<TargetFramework>net11.0</TargetFramework>
<EnableMSTestRunner>true</EnableMSTestRunner>
<OutputType>Exe</OutputType>
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
@@ -12,9 +11,11 @@
<ProjectReference Include="..\..\src\JsonLdRecipeParser\JsonLdRecipeParser.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FsCheck" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" />
<PackageReference Include="Microsoft.Testing.Extensions.TrxReport" />
<PackageReference Include="MSTest" />
<PackageReference Include="Shouldly" />
</ItemGroup>
</Project>
test/JsonLdRecipeParser.Tests/packages.lock.json +52 -1
diff --git a/test/JsonLdRecipeParser.Tests/packages.lock.json b/test/JsonLdRecipeParser.Tests/packages.lock.json
index e200380..b257be5 100644
@@ -1,13 +1,22 @@
{
"version": 2,
"dependencies": {
"net11.0": {
"net10.0": {
"CSharpier.MsBuild": {
"type": "Direct",
"requested": "[1.3.0, )",
"resolved": "1.3.0",
"contentHash": "jzzLpmz7F6LyBh5FTZy8/4dEYVcAG9VjBPKryZ82LIoUuupcBkCSXzaeS/4oYcp/rBC8yVLcDstJ8CIv9n2IFw=="
},
"FsCheck": {
"type": "Direct",
"requested": "[3.3.3, )",
"resolved": "3.3.3",
"contentHash": "FtJSNzekCwoVTJPLZwK2In1cr9PhIo6WAX/RSM7BL/jQERgnO0111+hdcN14pcog9BByJmqehBrMbjjKpHZULg==",
"dependencies": {
"FSharp.Core": "5.0.2"
}
},
"Microsoft.NET.Test.Sdk": {
"type": "Direct",
"requested": "[18.7.0, )",
@@ -52,6 +61,35 @@
"Microsoft.Testing.Extensions.TrxReport": "2.2.3"
}
},
"Shouldly": {
"type": "Direct",
"requested": "[4.3.0, )",
"resolved": "4.3.0",
"contentHash": "sDetrWXrl6YXZ4HeLsdBoNk3uIa7K+V4uvIJ+cqdRa5DrFxeTED7VkjoxCuU1kJWpUuBDZz2QXFzSxBtVXLwRQ==",
"dependencies": {
"DiffEngine": "11.3.0",
"EmptyFiles": "4.4.0"
}
},
"DiffEngine": {
"type": "Transitive",
"resolved": "11.3.0",
"contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==",
"dependencies": {
"EmptyFiles": "4.4.0",
"System.Management": "6.0.1"
}
},
"EmptyFiles": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw=="
},
"FSharp.Core": {
"type": "Transitive",
"resolved": "5.0.2",
"contentHash": "DpcajqENgb3VXaHw1FKfVS+TWwEzck1PCajqLwBAVrtd1lfxd7T8JISVZBdV1mX9+2g48+tIgpNaVJWObdMrBw=="
},
"Microsoft.ApplicationInsights": {
"type": "Transitive",
"resolved": "2.23.0",
@@ -155,6 +193,19 @@
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"System.CodeDom": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA=="
},
"System.Management": {
"type": "Transitive",
"resolved": "6.0.1",
"contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==",
"dependencies": {
"System.CodeDom": "6.0.0"
}
},
"jsonldrecipeparser": {
"type": "Project",
"dependencies": {