diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d671e00f..e7d1df69 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,7 @@ jobs: with: dotnet-version: | 3.1.x - 5.0.x + 6.0.x global-json-file: "./global.json" - name: "Dotnet Tool Restore" run: dotnet tool restore diff --git a/Benchmarks/Schema.NET.Benchmarks/Program.cs b/Benchmarks/Schema.NET.Benchmarks/Program.cs index 40c898e2..35ab8664 100644 --- a/Benchmarks/Schema.NET.Benchmarks/Program.cs +++ b/Benchmarks/Schema.NET.Benchmarks/Program.cs @@ -4,5 +4,6 @@ namespace Schema.NET.Benchmarks; public class Program { - private static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); + private static void Main(string[] args) => + BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); } diff --git a/Benchmarks/Schema.NET.Benchmarks/Schema.NET.Benchmarks.csproj b/Benchmarks/Schema.NET.Benchmarks/Schema.NET.Benchmarks.csproj index c5cada5b..9dcf6924 100644 --- a/Benchmarks/Schema.NET.Benchmarks/Schema.NET.Benchmarks.csproj +++ b/Benchmarks/Schema.NET.Benchmarks/Schema.NET.Benchmarks.csproj @@ -2,7 +2,7 @@ Exe - net6.0;net5.0;net472 + net7.0;net6.0;net472 false diff --git a/Directory.Build.props b/Directory.Build.props index 16e9e0fe..2bc0caae 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - latest + preview true AllEnabledByDefault latest diff --git a/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs b/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs index d25e3f72..dc6074c1 100644 --- a/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs +++ b/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs @@ -13,6 +13,8 @@ namespace Schema.NET; /// public class DateTimeToIso8601DateValuesJsonConverter : ValuesJsonConverter { + private const string DateFormat = "yyyy-MM-dd"; + /// /// Writes the object retrieved from when one is found. /// @@ -38,7 +40,7 @@ public override void WriteObject(Utf8JsonWriter writer, object? value, JsonSeria if (value is DateTime dateTimeType) { - writer.WriteStringValue(dateTimeType.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + writer.WriteStringValue(dateTimeType.ToString(DateFormat, CultureInfo.InvariantCulture)); } else { diff --git a/Source/Common/FastActivator.cs b/Source/Common/FastActivator.cs index e25ffa80..86323569 100644 --- a/Source/Common/FastActivator.cs +++ b/Source/Common/FastActivator.cs @@ -35,10 +35,10 @@ internal static class FastActivator } private static Func CreateConstructorDelegate(ConstructorInfo constructor) => Expression.Lambda>( - Expression.Convert( - Expression.New(constructor, ConstructorParameter.SingleParameter), - typeof(object)), - ConstructorParameter.SingleParameter).Compile(); + Expression.Convert( + Expression.New(constructor, ConstructorParameter.SingleParameter), + typeof(object)), + ConstructorParameter.SingleParameter).Compile(); private static ConstructorInfo? GetConstructorInfo(Type objectType, Type parameter1) { diff --git a/Source/Common/HashCode.cs b/Source/Common/HashCode.cs index 830e7a5d..562d1b6d 100644 --- a/Source/Common/HashCode.cs +++ b/Source/Common/HashCode.cs @@ -114,7 +114,7 @@ public override bool Equals(object? obj) [EditorBrowsable(EditorBrowsableState.Never)] public override int GetHashCode() => #pragma warning disable CA1065 // Do not raise exceptions in unexpected locations - throw new NotSupportedException("Implicitly convert this struct to an int to get the hash code."); + throw new NotSupportedException("Implicitly convert this struct to an int to get the hash code."); #pragma warning restore CA1065 // Do not raise exceptions in unexpected locations private static int CombineHashCodes(int h1, int h2) diff --git a/Source/Common/PropertyValueSpecification.Partial.cs b/Source/Common/PropertyValueSpecification.Partial.cs index 06baed76..5fc15563 100644 --- a/Source/Common/PropertyValueSpecification.Partial.cs +++ b/Source/Common/PropertyValueSpecification.Partial.cs @@ -1,6 +1,5 @@ namespace Schema.NET; -using System.Collections.Generic; using System.Linq; using System.Text; @@ -9,6 +8,13 @@ namespace Schema.NET; /// public partial class PropertyValueSpecification { + private const string MaxLengthPropertyName = "maxlength="; + private const string MinLengthPropertyName = "minlength="; + private const string NamePropertyName = "name="; + private const string PatternPropertyName = "pattern="; + private const string RequiredPropertyName = "required"; + private const char Space = ' '; + /// /// Returns a that represents the short hand representation of this instance. /// See https://schema.org/docs/actions.html#part-3. @@ -22,35 +28,35 @@ public override string ToString() if (this.ValueMaxLength.First() is double maxLength) { - stringBuilder.Append("maxlength="); + stringBuilder.Append(MaxLengthPropertyName); stringBuilder.Append(maxLength); } if (this.ValueMinLength.First() is double minLength) { AppendSpace(stringBuilder); - stringBuilder.Append("minlength="); + stringBuilder.Append(MinLengthPropertyName); stringBuilder.Append(minLength); } if (this.ValueName.First() is string name) { AppendSpace(stringBuilder); - stringBuilder.Append("name="); + stringBuilder.Append(NamePropertyName); stringBuilder.Append(name); } if (this.ValuePattern.First() is string pattern) { AppendSpace(stringBuilder); - stringBuilder.Append("pattern="); + stringBuilder.Append(PatternPropertyName); stringBuilder.Append(pattern); } if (this.ValueRequired.First() is true) { AppendSpace(stringBuilder); - stringBuilder.Append("required"); + stringBuilder.Append(RequiredPropertyName); } return stringBuilder.ToString(); @@ -60,7 +66,7 @@ private static void AppendSpace(StringBuilder stringBuilder) { if (stringBuilder.Length > 0) { - stringBuilder.Append(' '); + stringBuilder.Append(Space); } } } diff --git a/Source/Common/SchemaEnumJsonConverter{T}.cs b/Source/Common/SchemaEnumJsonConverter{T}.cs index b0f6ffed..76744f91 100644 --- a/Source/Common/SchemaEnumJsonConverter{T}.cs +++ b/Source/Common/SchemaEnumJsonConverter{T}.cs @@ -10,7 +10,7 @@ namespace Schema.NET; /// /// Converts a Schema enumeration to and from JSON. /// -/// The enum type to convert +/// The enumeration type to convert. public class SchemaEnumJsonConverter : JsonConverter where T : struct, Enum { @@ -41,11 +41,21 @@ public SchemaEnumJsonConverter() /// The enumeration value. public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(typeToConvert); + ArgumentNullException.ThrowIfNull(options); +#else if (typeToConvert is null) { throw new ArgumentNullException(nameof(typeToConvert)); } + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } +#endif + var valueString = reader.GetString(); if (EnumHelper.TryParseEnumFromSchemaUri(typeToConvert, valueString, out var result)) { @@ -63,11 +73,21 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial /// The JSON serializer options. public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); + ArgumentNullException.ThrowIfNull(options); +#else if (writer is null) { throw new ArgumentNullException(nameof(writer)); } + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } +#endif + writer.WriteStringValue(this.valueNameMap[value]); } } diff --git a/Source/Common/SchemaSerializer.cs b/Source/Common/SchemaSerializer.cs index 0de0e983..7e1563f8 100644 --- a/Source/Common/SchemaSerializer.cs +++ b/Source/Common/SchemaSerializer.cs @@ -1,8 +1,8 @@ namespace Schema.NET; using System; -using System.Collections.Generic; using System.Text; +using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; @@ -32,7 +32,7 @@ static SchemaSerializer() { AllowTrailingCommas = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, - Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, }; HtmlEscapedSerializationSettings = new JsonSerializerOptions diff --git a/Source/Common/Values{T1,T2,T3,T4,T5,T6,T7}.cs b/Source/Common/Values{T1,T2,T3,T4,T5,T6,T7}.cs index 0e37b367..a84fdf3d 100644 --- a/Source/Common/Values{T1,T2,T3,T4,T5,T6,T7}.cs +++ b/Source/Common/Values{T1,T2,T3,T4,T5,T6,T7}.cs @@ -209,43 +209,36 @@ public Values(IEnumerable items) if (item is T7 itemT7) { items7 ??= new List(); - items7.Add(itemT7); } else if (item is T6 itemT6) { items6 ??= new List(); - items6.Add(itemT6); } else if (item is T5 itemT5) { items5 ??= new List(); - items5.Add(itemT5); } else if (item is T4 itemT4) { items4 ??= new List(); - items4.Add(itemT4); } else if (item is T3 itemT3) { items3 ??= new List(); - items3.Add(itemT3); } else if (item is T2 itemT2) { items2 ??= new List(); - items2.Add(itemT2); } else if (item is T1 itemT1) { items1 ??= new List(); - items1.Add(itemT1); } } diff --git a/Source/Common/Values{T1,T2,T3,T4,T5,T6}.cs b/Source/Common/Values{T1,T2,T3,T4,T5,T6}.cs index 08d61b36..537943f8 100644 --- a/Source/Common/Values{T1,T2,T3,T4,T5,T6}.cs +++ b/Source/Common/Values{T1,T2,T3,T4,T5,T6}.cs @@ -173,37 +173,31 @@ public Values(IEnumerable items) if (item is T6 itemT6) { items6 ??= new List(); - items6.Add(itemT6); } else if (item is T5 itemT5) { items5 ??= new List(); - items5.Add(itemT5); } else if (item is T4 itemT4) { items4 ??= new List(); - items4.Add(itemT4); } else if (item is T3 itemT3) { items3 ??= new List(); - items3.Add(itemT3); } else if (item is T2 itemT2) { items2 ??= new List(); - items2.Add(itemT2); } else if (item is T1 itemT1) { items1 ??= new List(); - items1.Add(itemT1); } } diff --git a/Source/Common/Values{T1,T2,T3,T4}.cs b/Source/Common/Values{T1,T2,T3,T4}.cs index 6947a2d6..b275502e 100644 --- a/Source/Common/Values{T1,T2,T3,T4}.cs +++ b/Source/Common/Values{T1,T2,T3,T4}.cs @@ -113,25 +113,21 @@ public Values(IEnumerable items) if (item is T4 itemT4) { items4 ??= new List(); - items4.Add(itemT4); } else if (item is T3 itemT3) { items3 ??= new List(); - items3.Add(itemT3); } else if (item is T2 itemT2) { items2 ??= new List(); - items2.Add(itemT2); } else if (item is T1 itemT1) { items1 ??= new List(); - items1.Add(itemT1); } } diff --git a/Source/Common/Values{T1,T2,T3}.cs b/Source/Common/Values{T1,T2,T3}.cs index fcce9575..e688fa48 100644 --- a/Source/Common/Values{T1,T2,T3}.cs +++ b/Source/Common/Values{T1,T2,T3}.cs @@ -89,19 +89,16 @@ public Values(IEnumerable items) if (item is T3 itemT3) { items3 ??= new List(); - items3.Add(itemT3); } else if (item is T2 itemT2) { items2 ??= new List(); - items2.Add(itemT2); } else if (item is T1 itemT1) { items1 ??= new List(); - items1.Add(itemT1); } } diff --git a/Source/Common/Values{T1,T2}.cs b/Source/Common/Values{T1,T2}.cs index 1c6de3f1..28c08cae 100644 --- a/Source/Common/Values{T1,T2}.cs +++ b/Source/Common/Values{T1,T2}.cs @@ -69,13 +69,11 @@ public Values(IEnumerable items) if (item is T2 itemT2) { items2 ??= new List(); - items2.Add(itemT2); } else if (item is T1 itemT1) { items1 ??= new List(); - items1.Add(itemT1); } } @@ -117,8 +115,6 @@ public Values(IEnumerable items) /// public OneOrMany Value2 { get; } -#pragma warning disable CA1002 // Do not expose generic lists -#pragma warning disable CA2225 // Operator overloads have named alternates /// /// Performs an implicit conversion from to . /// @@ -228,8 +224,6 @@ public Values(IEnumerable items) /// The result of the conversion. /// public static implicit operator List(Values values) => values.Value2.ToList(); -#pragma warning restore CA2225 // Operator overloads have named alternates -#pragma warning restore CA1002 // Do not expose generic lists /// /// Implements the operator ==. diff --git a/Source/Schema.NET.Pending/Schema.NET.Pending.csproj b/Source/Schema.NET.Pending/Schema.NET.Pending.csproj index 28eeea85..e5842a69 100644 --- a/Source/Schema.NET.Pending/Schema.NET.Pending.csproj +++ b/Source/Schema.NET.Pending/Schema.NET.Pending.csproj @@ -1,7 +1,7 @@ - net6.0;net5.0;netcoreapp3.1;netstandard2.0;net472;net461 + net7.0;net6.0;netcoreapp3.1;netstandard2.0;net472;net461 True $(BaseIntermediateOutputPath)\GeneratedFiles True diff --git a/Source/Schema.NET/Schema.NET.csproj b/Source/Schema.NET/Schema.NET.csproj index 5e716a45..ba18a1cc 100644 --- a/Source/Schema.NET/Schema.NET.csproj +++ b/Source/Schema.NET/Schema.NET.csproj @@ -1,7 +1,7 @@ - net6.0;net5.0;netcoreapp3.1;netstandard2.0;net472;net461 + net7.0;net6.0;netcoreapp3.1;netstandard2.0;net472;net461 True $(BaseIntermediateOutputPath)\GeneratedFiles False diff --git a/Tests/Schema.NET.Test/ContextJsonConverterTest.cs b/Tests/Schema.NET.Test/ContextJsonConverterTest.cs index 8a1d790e..b27f9a3b 100644 --- a/Tests/Schema.NET.Test/ContextJsonConverterTest.cs +++ b/Tests/Schema.NET.Test/ContextJsonConverterTest.cs @@ -7,7 +7,10 @@ public class ContextJsonConverterTest [Fact] public void ReadJson_StringContext_ContextHasName() { - var json = "{\"@context\":\"foo\",\"@type\":\"Thing\"}"; + var json = /*lang=json,strict*/ + """ + {"@context":"foo","@type":"Thing"} + """; var thing = SchemaSerializer.DeserializeObject(json); @@ -19,7 +22,10 @@ public void ReadJson_StringContext_ContextHasName() [Fact] public void ReadJson_ObjectContextWithName_ContextHasName() { - var json = "{\"@context\":{\"name\":\"foo\"},\"@type\":\"Thing\"}"; + var json = /*lang=json,strict*/ + """ + {"@context":{"name":"foo"},"@type":"Thing"} + """; var thing = SchemaSerializer.DeserializeObject(json); @@ -31,7 +37,10 @@ public void ReadJson_ObjectContextWithName_ContextHasName() [Fact] public void ReadJson_ObjectContextWithNameAndLanguage_ContextHasNameAndLanguage() { - var json = "{\"@context\":{\"name\":\"foo\",\"@language\":\"en\"},\"@type\":\"Thing\"}"; + var json = /*lang=json,strict*/ + """ + {"@context":{"name":"foo","@language":"en"},"@type":"Thing"} + """; var thing = SchemaSerializer.DeserializeObject(json); @@ -43,7 +52,10 @@ public void ReadJson_ObjectContextWithNameAndLanguage_ContextHasNameAndLanguage( [Fact] public void ReadJson_ArrayContextWithNameAndLanguage_ContextHasNameAndLanguage() { - var json = "{\"@context\":[\"foo\",{\"@language\":\"en\"}],\"@type\":\"Thing\"}"; + var json = /*lang=json,strict*/ + """ + {"@context":["foo",{"@language":"en"}],"@type":"Thing"} + """; var thing = SchemaSerializer.DeserializeObject(json); @@ -57,7 +69,11 @@ public void WriteJson_StringContext_ContextHasName() { var json = new Thing().ToString(); - Assert.Equal("{\"@context\":\"https://schema.org\",\"@type\":\"Thing\"}", json); + var expectedJson = /*lang=json,strict*/ + """ + {"@context":"https://schema.org","@type":"Thing"} + """; + Assert.Equal(expectedJson, json); } [Fact] @@ -68,6 +84,10 @@ public void WriteJson_ObjectContextWithLanguage_ContextHasName() var json = thing.ToString(); - Assert.Equal("{\"@context\":{\"name\":\"https://schema.org\",\"@language\":\"en\"},\"@type\":\"Thing\"}", json); + var expectedJson = /*lang=json,strict*/ + """ + {"@context":{"name":"https://schema.org","@language":"en"},"@type":"Thing"} + """; + Assert.Equal(expectedJson, json); } } diff --git a/Tests/Schema.NET.Test/DateValuesJsonConverterTest.cs b/Tests/Schema.NET.Test/DateValuesJsonConverterTest.cs index e4bd67d4..0953c404 100644 --- a/Tests/Schema.NET.Test/DateValuesJsonConverterTest.cs +++ b/Tests/Schema.NET.Test/DateValuesJsonConverterTest.cs @@ -11,14 +11,19 @@ public void WriteJson_DateTime_ISO8601_Date() { var value = new OneOrMany(new DateTime(2000, 1, 1, 12, 34, 56)); var json = SerializeObject(value); - Assert.Equal("{\"Property\":\"2000-01-01\"}", json); + + var expectedJson = /*lang=json,strict*/ + """ + {"Property":"2000-01-01"} + """; + Assert.Equal(expectedJson, json); } private static string SerializeObject(T value) - where T : struct, IValues - => SchemaSerializer.SerializeObject(new TestModel { Property = value }); + where T : struct, IValues => + SchemaSerializer.SerializeObject(new TestModel { Property = value }); - private class TestModel + private sealed class TestModel where T : struct, IValues { [JsonConverter(typeof(DateTimeToIso8601DateValuesJsonConverter))] diff --git a/Tests/Schema.NET.Test/DurationValuesJsonConverterTest.cs b/Tests/Schema.NET.Test/DurationValuesJsonConverterTest.cs index d59af67c..7829ef00 100644 --- a/Tests/Schema.NET.Test/DurationValuesJsonConverterTest.cs +++ b/Tests/Schema.NET.Test/DurationValuesJsonConverterTest.cs @@ -11,14 +11,19 @@ public void WriteJson_TimeSpan_ISO8601_Duration() { var value = new OneOrMany(new TimeSpan(12, 34, 56)); var json = SerializeObject(value); - Assert.Equal("{\"Property\":\"PT12H34M56S\"}", json); + + const string expected = /*lang=json,strict*/ + """ + {"Property":"PT12H34M56S"} + """; + Assert.Equal(expected, json); } private static string SerializeObject(T value) - where T : struct, IValues - => SchemaSerializer.SerializeObject(new TestModel { Property = value }); + where T : struct, IValues => + SchemaSerializer.SerializeObject(new TestModel { Property = value }); - private class TestModel + private sealed class TestModel where T : struct, IValues { [JsonConverter(typeof(TimeSpanToISO8601DurationValuesJsonConverter))] diff --git a/Tests/Schema.NET.Test/Examples/BookTest.cs b/Tests/Schema.NET.Test/Examples/BookTest.cs index 6487e0b7..8703fe21 100644 --- a/Tests/Schema.NET.Test/Examples/BookTest.cs +++ b/Tests/Schema.NET.Test/Examples/BookTest.cs @@ -80,76 +80,82 @@ public class BookTest }, }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"Book\"," + - "\"@id\":\"https://example.com/book/1\"," + - "\"name\":\"The Catcher in the Rye\"," + - "\"url\":\"https://www.barnesandnoble.com/store/info/offer/JDSalinger\"," + - "\"author\":{" + - "\"@type\":\"Person\"," + - "\"name\":\"J.D. Salinger\"" + - "}," + - "\"workExample\":[" + - "{" + - "\"@type\":\"Book\"," + - "\"potentialAction\":{" + - "\"@type\":\"ReadAction\"," + - "\"target\":{" + - "\"@type\":\"EntryPoint\"," + - "\"actionPlatform\":[" + - "\"https://schema.org/DesktopWebPlatform\"," + - "\"https://schema.org/IOSPlatform\"," + - "\"https://schema.org/AndroidPlatform\"" + - "]," + - "\"urlTemplate\":\"https://www.barnesandnoble.com/store/info/offer/0316769487?purchase=true\"" + - "}," + - "\"expectsAcceptanceOf\":{" + - "\"@type\":\"Offer\"," + - "\"availability\":\"https://schema.org/InStock\"," + - "\"eligibleRegion\":{" + - "\"@type\":\"Country\"," + - "\"name\":\"US\"" + - "}," + - "\"price\":6.99," + - "\"priceCurrency\":\"USD\"" + - "}" + - "}," + - "\"bookEdition\":\"2nd Edition\"," + - "\"bookFormat\":\"https://schema.org/Hardcover\"," + - "\"isbn\":\"031676948\"" + - "}," + - "{" + - "\"@type\":\"Book\"," + - "\"potentialAction\":{" + - "\"@type\":\"ReadAction\"," + - "\"target\":{" + - "\"@type\":\"EntryPoint\"," + - "\"actionPlatform\":[" + - "\"https://schema.org/DesktopWebPlatform\"," + - "\"https://schema.org/IOSPlatform\"," + - "\"https://schema.org/AndroidPlatform\"" + - "]," + - "\"urlTemplate\":\"https://www.barnesandnoble.com/store/info/offer/031676947?purchase=true\"" + - "}," + - "\"expectsAcceptanceOf\":{" + - "\"@type\":\"Offer\"," + - "\"availability\":\"https://schema.org/InStock\"," + - "\"eligibleRegion\":{" + - "\"@type\":\"Country\"," + - "\"name\":\"UK\"" + - "}," + - "\"price\":1.99," + - "\"priceCurrency\":\"USD\"" + - "}" + - "}," + - "\"bookEdition\":\"1st Edition\"," + - "\"bookFormat\":\"https://schema.org/EBook\"," + - "\"isbn\":\"031676947\"" + - "}" + - "]" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "Book", + "@id": "https://example.com/book/1", + "name": "The Catcher in the Rye", + "url": "https://www.barnesandnoble.com/store/info/offer/JDSalinger", + "author": { + "@type": "Person", + "name": "J.D. Salinger" + }, + "workExample": [ + { + "@type": "Book", + "potentialAction": { + "@type": "ReadAction", + "target": { + "@type": "EntryPoint", + "actionPlatform": [ + "https://schema.org/DesktopWebPlatform", + "https://schema.org/IOSPlatform", + "https://schema.org/AndroidPlatform" + ], + "urlTemplate": "https://www.barnesandnoble.com/store/info/offer/0316769487?purchase=true" + }, + "expectsAcceptanceOf": { + "@type": "Offer", + "availability": "https://schema.org/InStock", + "eligibleRegion": { + "@type": "Country", + "name": "US" + }, + "price": 6.99, + "priceCurrency": "USD" + } + }, + "bookEdition": "2nd Edition", + "bookFormat": "https://schema.org/Hardcover", + "isbn": "031676948" + }, + { + "@type": "Book", + "potentialAction": { + "@type": "ReadAction", + "target": { + "@type": "EntryPoint", + "actionPlatform": [ + "https://schema.org/DesktopWebPlatform", + "https://schema.org/IOSPlatform", + "https://schema.org/AndroidPlatform" + ], + "urlTemplate": "https://www.barnesandnoble.com/store/info/offer/031676947?purchase=true" + }, + "expectsAcceptanceOf": { + "@type": "Offer", + "availability": "https://schema.org/InStock", + "eligibleRegion": { + "@type": "Country", + "name": "UK" + }, + "price": 1.99, + "priceCurrency": "USD" + } + }, + "bookEdition": "1st Edition", + "bookFormat": "https://schema.org/EBook", + "isbn": "031676947" + } + ] + } + """; + + [Fact] + public void ToString_BookGoogleStructuredData_ReturnsExpectedJsonLd() => + Assert.Equal(this.json.MinifyJson(), this.book.ToString()); [Fact] public void Deserializing_BookJsonLd_ReturnsMatchingBook() @@ -190,19 +196,21 @@ public void Deserializing_BookJsonLd_ReturnsBook() [Fact] public void Deserializing_HasPersonAsAuthor_OrganizationIsNullAndHasPerson() { - var json = - "{" + - "\"@context\" : \"https://schema.org\"," + - "\"@type\" : \"Book\"," + - "\"author\" : [" + - "{" + - "\"@type\" : \"Person\"," + - "\"name\" : \"NameOfPerson1\"," + - "}," + - "]," + - "\"typicalAgeRange\" : \"14\"," + - "\"isbn\" : \"3333\"" + - "}"; + var json = /*lang=json*/ + """ + { + "@context": "https://schema.org", + "@type": "Book", + "author": [ + { + "@type": "Person", + "name": "NameOfPerson1", + }, + ], + "typicalAgeRange": "14", + "isbn": "3333" + } + """; var book = SchemaSerializer.DeserializeObject(json)!; Assert.Empty(book.Author.Value1); diff --git a/Tests/Schema.NET.Test/Examples/BreadcrumbListTest.cs b/Tests/Schema.NET.Test/Examples/BreadcrumbListTest.cs index 2380c44c..db916b7f 100644 --- a/Tests/Schema.NET.Test/Examples/BreadcrumbListTest.cs +++ b/Tests/Schema.NET.Test/Examples/BreadcrumbListTest.cs @@ -34,33 +34,39 @@ public class BreadcrumbListTest }, }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"BreadcrumbList\"," + - "\"itemListElement\":[" + - "{" + - "\"@type\":\"ListItem\"," + - "\"item\":{" + // Required - "\"@type\":\"Book\"," + - "\"@id\":\"https://example.com/books\"," + // Required - "\"name\":\"Books\"," + // Required - "\"image\":\"https://example.com/images/icon-book.png\"" + // Optional - "}," + - "\"position\":1" + // Required - "}," + - "{" + - "\"@type\":\"ListItem\"," + - "\"item\":{" + - "\"@type\":\"Person\"," + - "\"@id\":\"https://example.com/books/authors\"," + // Required - "\"name\":\"Authors\"," + - "\"image\":\"https://example.com/images/icon-author.png\"" + - "}," + - "\"position\":2" + - "}" + - "]" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "BreadcrumbList", + "itemListElement": [ + { + "@type": "ListItem", + "item": { + "@type": "Book", + "@id": "https://example.com/books", + "name": "Books", + "image": "https://example.com/images/icon-book.png" + }, + "position": 1 + }, + { + "@type": "ListItem", + "item": { + "@type": "Person", + "@id": "https://example.com/books/authors", + "name": "Authors", + "image": "https://example.com/images/icon-author.png" + }, + "position": 2 + } + ] + } + """; + + [Fact] + public void ToString_BreadcrumbListGoogleStructuredData_ReturnsExpectedJsonLd() => + Assert.Equal(this.json.MinifyJson(), this.breadcrumbList.ToString()); [Fact] public void Deserializing_BreadcrumbListJsonLd_ReturnsMatchingBreadcrumbList() diff --git a/Tests/Schema.NET.Test/Examples/CarTest.cs b/Tests/Schema.NET.Test/Examples/CarTest.cs index 8105d031..7d7a6098 100644 --- a/Tests/Schema.NET.Test/Examples/CarTest.cs +++ b/Tests/Schema.NET.Test/Examples/CarTest.cs @@ -34,32 +34,38 @@ public class CarTest VehicleModelDate = 2019, }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"Car\"," + - "\"name\":\"Volvo XC90\"," + - "\"description\":\"The XC90 is pure reflection of luxury that embodies Swedish design heritage. See everything this luxury SUV has to offer.\"," + - "\"image\":\"https://www.example.com/volvo_xc90.jpg\"," + - "\"brand\":{" + - "\"@type\":\"Brand\"," + - "\"name\":\"Volvo\"" + - "}," + - "\"offers\":{" + - "\"@type\":\"Offer\"," + - "\"availability\":\"https://schema.org/InStock\"," + - "\"itemCondition\":\"https://schema.org/NewCondition\"," + - "\"price\":47200.0," + - "\"priceCurrency\":\"USD\"," + - "\"priceValidUntil\":\"2020-11-05\"," + - "\"seller\":{" + - "\"@type\":\"Organization\"," + - "\"name\":\"Volvo Dealer\"" + - "}" + - "}," + - "\"modelDate\":2019," + - "\"vehicleModelDate\":2019" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "Car", + "name": "Volvo XC90", + "description": "The XC90 is pure reflection of luxury that embodies Swedish design heritage. See everything this luxury SUV has to offer.", + "image": "https://www.example.com/volvo_xc90.jpg", + "brand": { + "@type": "Brand", + "name": "Volvo" + }, + "offers": { + "@type": "Offer", + "availability": "https://schema.org/InStock", + "itemCondition": "https://schema.org/NewCondition", + "price": 47200, + "priceCurrency": "USD", + "priceValidUntil": "2020-11-05", + "seller": { + "@type": "Organization", + "name": "Volvo Dealer" + } + }, + "modelDate": 2019, + "vehicleModelDate": 2019 + } + """; + + [Fact] + public void ToString_CarGoogleStructuredData_ReturnsExpectedJsonLd() => + Assert.Equal(this.json.MinifyJson(), this.car.ToString()); [Fact] public void Deserializing_CarJsonLd_ReturnsCar() diff --git a/Tests/Schema.NET.Test/Examples/ClaimReviewTest.cs b/Tests/Schema.NET.Test/Examples/ClaimReviewTest.cs index 2033265a..a3699c71 100644 --- a/Tests/Schema.NET.Test/Examples/ClaimReviewTest.cs +++ b/Tests/Schema.NET.Test/Examples/ClaimReviewTest.cs @@ -33,34 +33,40 @@ public class ClaimReviewTest }, }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"ClaimReview\"," + - "\"url\":\"https://example.com/news/science/worldisflat.html\"," + - "\"author\":{" + - "\"@type\":\"Organization\"," + - "\"name\":\"Example.com science watch\"" + - "}," + - "\"datePublished\":\"2016-06-22\"," + - "\"itemReviewed\":{" + - "\"@type\":\"CreativeWork\"," + - "\"author\":{" + - "\"@type\":\"Organization\"," + - "\"name\":\"Square World Society\"," + - "\"sameAs\":\"https://example.flatworlders.com/we-know-that-the-world-is-flat\"" + - "}," + - "\"datePublished\":\"2016-06-20\"" + - "}," + - "\"reviewRating\":{" + - "\"@type\":\"Rating\"," + - "\"alternateName\":\"False\"," + - "\"bestRating\":5.0," + - "\"ratingValue\":1.0," + - "\"worstRating\":1.0" + - "}," + - "\"claimReviewed\":\"The world is flat\"" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "ClaimReview", + "url": "https://example.com/news/science/worldisflat.html", + "author": { + "@type": "Organization", + "name": "Example.com science watch" + }, + "datePublished": "2016-06-22", + "itemReviewed": { + "@type": "CreativeWork", + "author": { + "@type": "Organization", + "name": "Square World Society", + "sameAs": "https://example.flatworlders.com/we-know-that-the-world-is-flat" + }, + "datePublished": "2016-06-20" + }, + "reviewRating": { + "@type": "Rating", + "alternateName": "False", + "bestRating": 5, + "ratingValue": 1, + "worstRating": 1 + }, + "claimReviewed": "The world is flat" + } + """; + + [Fact] + public void ToString_ClaimReviewGoogleStructuredData_ReturnsExpectedJsonLd() => + Assert.Equal(this.json.MinifyJson(), this.claimReview.ToString()); [Fact] public void Deserializing_ClaimReviewJsonLd_ReturnsClaimReview() diff --git a/Tests/Schema.NET.Test/Examples/CourseTest.cs b/Tests/Schema.NET.Test/Examples/CourseTest.cs index b5386c45..2e6dfcea 100644 --- a/Tests/Schema.NET.Test/Examples/CourseTest.cs +++ b/Tests/Schema.NET.Test/Examples/CourseTest.cs @@ -17,18 +17,24 @@ public class CourseTest }, }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"Course\"," + - "\"name\":\"Introduction to Computer Science and Programming\"," + - "\"description\":\"Introductory CS course laying out the basics.\"," + - "\"provider\":{" + - "\"@type\":\"Organization\"," + - "\"name\":\"University of Technology - Eureka\"," + - "\"sameAs\":\"https://www.ut-eureka.edu\"" + - "}" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "Course", + "name": "Introduction to Computer Science and Programming", + "description": "Introductory CS course laying out the basics.", + "provider": { + "@type": "Organization", + "name": "University of Technology - Eureka", + "sameAs": "https://www.ut-eureka.edu" + } + } + """; + + [Fact] + public void ToString_CourseGoogleStructuredData_ReturnsExpectedJsonLd() => + Assert.Equal(this.json.MinifyJson(), this.course.ToString()); [Fact] public void Deserializing_CourseJsonLd_ReturnsCourse() diff --git a/Tests/Schema.NET.Test/Examples/EventTest.cs b/Tests/Schema.NET.Test/Examples/EventTest.cs index a4b01d35..97181470 100644 --- a/Tests/Schema.NET.Test/Examples/EventTest.cs +++ b/Tests/Schema.NET.Test/Examples/EventTest.cs @@ -41,7 +41,7 @@ public class EventTest PriceCurrency = "USD", // Recommended Availability = ItemAvailability.InStock, // Recommended ValidFrom = new DateTimeOffset(2017, 1, 20, 16, 20, 0, TimeSpan.FromHours(-8)), // Recommended - Category = NullString, + Category = NullString!, }, new Offer { @@ -55,52 +55,55 @@ public class EventTest Performer = new Person() // Recommended { Name = "Andy Lagunoff", // Recommended - Telephone = NullString, // Should be ignored + Telephone = NullString!, // Should be ignored }, }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"Event\"," + - "\"name\":\"Jan Lieberman Concert Series: Journey in Jazz\"," + - "\"description\":\"Join us for an afternoon of Jazz with Santa Clara resident and pianist Andy Lagunoff. Complimentary food and beverages will be served.\"," + - "\"image\":\"https://www.example.com/event_image/12345\"," + - "\"endDate\":\"2017-04-24T23:00:00-08:00\"," + - "\"location\":{" + - "\"@type\":\"Place\"," + - "\"name\":\"Santa Clara City Library, Central Park Library\"," + - "\"address\":{" + - "\"@type\":\"PostalAddress\"," + - "\"addressCountry\":\"US\"," + - "\"addressLocality\":\"Santa Clara\"," + - "\"addressRegion\":\"CA\"," + - "\"postalCode\":\"95051\"," + - "\"streetAddress\":\"2635 Homestead Rd\"" + - "}" + - "}," + - "\"offers\":[" + - "{" + - "\"@type\":\"Offer\"," + - "\"url\":\"https://www.example.com/event_offer/12345_201803180430\"," + - "\"availability\":\"https://schema.org/InStock\"," + - "\"price\":30.0," + - "\"priceCurrency\":\"USD\"," + - "\"validFrom\":\"2017-01-20T16:20:00-08:00\"" + - "},{" + - "\"@type\":\"Offer\"," + - "\"url\":\"https://www.example.com/event_offer/12345_201803180430\"," + - "\"price\":30.0," + - "\"priceCurrency\":\"USD\"," + - "\"validFrom\":\"2017-01-20T16:20:00-08:00\"" + - "}" + - "]," + - "\"performer\":{" + - "\"@type\":\"Person\"," + - "\"name\":\"Andy Lagunoff\"" + - "}," + - "\"startDate\":\"2017-04-24T19:30:00-08:00\"" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "Event", + "name": "Jan Lieberman Concert Series: Journey in Jazz", + "description": "Join us for an afternoon of Jazz with Santa Clara resident and pianist Andy Lagunoff. Complimentary food and beverages will be served.", + "image": "https://www.example.com/event_image/12345", + "endDate": "2017-04-24T23:00:00-08:00", + "location": { + "@type": "Place", + "name": "Santa Clara City Library, Central Park Library", + "address": { + "@type": "PostalAddress", + "addressCountry": "US", + "addressLocality": "Santa Clara", + "addressRegion": "CA", + "postalCode": "95051", + "streetAddress": "2635 Homestead Rd" + } + }, + "offers": [ + { + "@type": "Offer", + "url": "https://www.example.com/event_offer/12345_201803180430", + "availability": "https://schema.org/InStock", + "price": 30.0, + "priceCurrency": "USD", + "validFrom": "2017-01-20T16:20:00-08:00" + }, + { + "@type": "Offer", + "url": "https://www.example.com/event_offer/12345_201803180430", + "price": 30.0, + "priceCurrency": "USD", + "validFrom": "2017-01-20T16:20:00-08:00" + } + ], + "performer": { + "@type": "Person", + "name": "Andy Lagunoff" + }, + "startDate": "2017-04-24T19:30:00-08:00" + } + """; [Fact] public void Deserializing_EventJsonLd_ReturnsEvent() diff --git a/Tests/Schema.NET.Test/Examples/ItemListTest.cs b/Tests/Schema.NET.Test/Examples/ItemListTest.cs index 6b180d23..b5dd5150 100644 --- a/Tests/Schema.NET.Test/Examples/ItemListTest.cs +++ b/Tests/Schema.NET.Test/Examples/ItemListTest.cs @@ -30,29 +30,31 @@ public class ItemListTest }, }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"ItemList\"," + - "\"itemListElement\":[" + - "{" + - "\"@type\":\"ListItem\"," + - "\"item\":{" + - "\"@type\":\"Recipe\"," + - "\"name\":\"Recipe 1\"" + - "}," + - "\"position\":1" + - "}," + - "{" + - "\"@type\":\"ListItem\"," + - "\"item\":{" + - "\"@type\":\"Recipe\"," + - "\"name\":\"Recipe 2\"" + - "}," + - "\"position\":2" + - "}" + - "]" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "ItemList", + "itemListElement": [ + { + "@type": "ListItem", + "item": { + "@type": "Recipe", + "name": "Recipe 1" + }, + "position": 1 + }, + { + "@type": "ListItem", + "item": { + "@type": "Recipe", + "name": "Recipe 2" + }, + "position": 2 + } + ] + } + """; [Fact] public void Deserializing_ItemListJsonLd_ReturnsMatchingItemList() @@ -82,27 +84,29 @@ public void ToString_CarouselSummaryPageSearchBoxGoogleStructuredData_ReturnsExp }, }, }; - var expectedJson = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"ItemList\"," + - "\"itemListElement\":[" + - "{" + - "\"@type\":\"ListItem\"," + - "\"url\":\"https://example.com/articles/1\"," + - "\"position\":1" + - "}," + - "{" + - "\"@type\":\"ListItem\"," + - "\"url\":\"https://example.com/articles/2\"," + - "\"position\":2" + - "}" + - "]" + - "}"; + var expectedJson = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "ItemList", + "itemListElement": [ + { + "@type": "ListItem", + "url": "https://example.com/articles/1", + "position": 1 + }, + { + "@type": "ListItem", + "url": "https://example.com/articles/2", + "position": 2 + } + ] + } + """; var json = itemList.ToString(); - Assert.Equal(expectedJson, json); + Assert.Equal(expectedJson.MinifyJson(), json); } // https://developers.google.com/search/docs/guides/mark-up-listings @@ -132,61 +136,65 @@ public void ToString_CarouselAllInOnePageSearchBoxGoogleStructuredData_ReturnsEx }, }, }; - var expectedJson = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"ItemList\"," + - "\"itemListElement\":[" + - "{" + - "\"@type\":\"ListItem\"," + - "\"item\":{" + - "\"@type\":\"Recipe\"," + - "\"name\":\"Recipe 1\"" + - "}," + - "\"position\":1" + - "}," + - "{" + - "\"@type\":\"ListItem\"," + - "\"item\":{" + - "\"@type\":\"Recipe\"," + - "\"name\":\"Recipe 2\"" + - "}," + - "\"position\":2" + - "}" + - "]" + - "}"; + var expectedJson = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "ItemList", + "itemListElement": [ + { + "@type": "ListItem", + "item": { + "@type": "Recipe", + "name": "Recipe 1" + }, + "position": 1 + }, + { + "@type": "ListItem", + "item": { + "@type": "Recipe", + "name": "Recipe 2" + }, + "position": 2 + } + ] + } + """; var json = itemList.ToString(); - Assert.Equal(expectedJson, json); + Assert.Equal(expectedJson.MinifyJson(), json); } [Fact] public void Deserializing_ItemListJsonLd_ReturnsItemList() { - var json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"ItemList\"," + - "\"itemListElement\":[" + - "{" + - "\"@type\":\"ListItem\"," + - "\"item\":{" + - "\"@type\":\"Recipe\"," + - "\"name\":\"Recipe 1\"" + - "}," + - "\"position\":1" + - "}," + - "{" + - "\"@type\":\"ListItem\"," + - "\"item\":{" + - "\"@type\":\"Recipe\"," + - "\"name\":\"Recipe 2\"" + - "}," + - "\"position\":2" + - "}" + - "]" + - "}"; + var json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "ItemList", + "itemListElement": [ + { + "@type": "ListItem", + "item": { + "@type": "Recipe", + "name": "Recipe 1" + }, + "position": 1 + }, + { + "@type": "ListItem", + "item": { + "@type": "Recipe", + "name": "Recipe 2" + }, + "position": 2 + } + ] + } + """; var itemList = SchemaSerializer.DeserializeObject(json)!; Assert.Equal("ItemList", itemList.Type); diff --git a/Tests/Schema.NET.Test/Examples/JobPostingTest.cs b/Tests/Schema.NET.Test/Examples/JobPostingTest.cs index 1812baad..633718aa 100644 --- a/Tests/Schema.NET.Test/Examples/JobPostingTest.cs +++ b/Tests/Schema.NET.Test/Examples/JobPostingTest.cs @@ -45,50 +45,52 @@ public class JobPostingTest }, }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"JobPosting\"," + - "\"title\":\"Fitter and Turner\"," + - "\"description\":\"

Widget assembly role for pressing wheel assemblies.

Educational Requirements: Completed level 2 ISTA Machinist Apprenticeship.

Required Experience: At least 3 years in a machinist role.

\"," + - "\"identifier\":{" + - "\"@type\":\"PropertyValue\"," + - "\"name\":\"MagsRUs Wheel Company\"," + - "\"value\":\"1234567\"" + - "}," + - "\"baseSalary\":{" + - "\"@type\":\"MonetaryAmount\"," + - "\"currency\":\"USD\"," + - "\"value\":{" + - "\"@type\":\"QuantitativeValue\"," + - "\"unitText\":\"HOUR\"," + - "\"value\":40" + - "}" + - "}," + - "\"datePosted\":\"2017-01-18\"," + - "\"employmentType\":\"CONTRACTOR\"," + - "\"hiringOrganization\":{" + - "\"@type\":\"Organization\"," + - "\"name\":\"MagsRUs Wheel Company\"," + - "\"sameAs\":\"https://www.magsruswheelcompany.com\"" + - "}," + - "\"jobLocation\":{" + - "\"@type\":\"Place\"," + - "\"address\":{" + - "\"@type\":\"PostalAddress\"," + - "\"addressCountry\":\"US\"," + - "\"addressLocality\":\"Detroit\"," + - "\"addressRegion\":\"MI\"," + - "\"postalCode\":\"48201\"," + - "\"streetAddress\":\"555 Clancy St\"" + - "}" + - "}," + - "\"validThrough\":\"2017-03-18T00:00:00+00:00\"" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "JobPosting", + "title": "Fitter and Turner", + "description": "

Widget assembly role for pressing wheel assemblies.

Educational Requirements: Completed level 2 ISTA Machinist Apprenticeship.

Required Experience: At least 3 years in a machinist role.

", + "identifier": { + "@type": "PropertyValue", + "name": "MagsRUs Wheel Company", + "value": "1234567" + }, + "baseSalary": { + "@type": "MonetaryAmount", + "currency": "USD", + "value": { + "@type": "QuantitativeValue", + "unitText": "HOUR", + "value": 40 + } + }, + "datePosted": "2017-01-18", + "employmentType": "CONTRACTOR", + "hiringOrganization": { + "@type": "Organization", + "name": "MagsRUs Wheel Company", + "sameAs": "https://www.magsruswheelcompany.com" + }, + "jobLocation": { + "@type": "Place", + "address": { + "@type": "PostalAddress", + "addressCountry": "US", + "addressLocality": "Detroit", + "addressRegion": "MI", + "postalCode": "48201", + "streetAddress": "555 Clancy St" + } + }, + "validThrough":"2017-03-18T00:00:00+00:00" + } + """; [Fact] public void ToString_JobPostingGoogleStructuredData_ReturnsExpectedJsonLd() => - Assert.Equal(this.json, this.jobPosting.ToString()); + Assert.Equal(this.json.MinifyJson(), this.jobPosting.ToString()); [Fact] public void Deserializing_JobPostingJsonLd_ReturnsJobPosting() diff --git a/Tests/Schema.NET.Test/Examples/MixedTypesTest.cs b/Tests/Schema.NET.Test/Examples/MixedTypesTest.cs index 6a14a79b..aa76153d 100644 --- a/Tests/Schema.NET.Test/Examples/MixedTypesTest.cs +++ b/Tests/Schema.NET.Test/Examples/MixedTypesTest.cs @@ -23,22 +23,24 @@ public class MixedTypesTest }, }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"Book\"," + - "\"@id\":\"https://example.com/book/1\"," + - "\"author\":[" + - "{" + - "\"@type\":\"Organization\"," + - "\"name\":\"Penguin\"" + - "}," + - "{" + - "\"@type\":\"Person\"," + - "\"name\":\"J.D. Salinger\"" + - "}" + - "]" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "Book", + "@id": "https://example.com/book/1", + "author": [ + { + "@type": "Organization", + "name": "Penguin" + }, + { + "@type": "Person", + "name": "J.D. Salinger" + } + ] + } + """; [Fact] public void Deserializing_WithCollectionOfMixedTypes_ReturnsMatchingObject() diff --git a/Tests/Schema.NET.Test/Examples/MusicAlbumTest.cs b/Tests/Schema.NET.Test/Examples/MusicAlbumTest.cs index 10de9fd3..fc01f509 100644 --- a/Tests/Schema.NET.Test/Examples/MusicAlbumTest.cs +++ b/Tests/Schema.NET.Test/Examples/MusicAlbumTest.cs @@ -49,56 +49,55 @@ public class MusicAlbumTest }, }; - private readonly string json = - "{" + - "\"@context\": \"https://schema.org\"," + - "\"@type\": \"MusicAlbum\"," + - "\"name\": \"Hail to the Thief\"," + - "\"identifier\": \"1oW3v5Har9mvXnGk0x4fHm\"," + - "\"image\": [{" + - "\"$type\": \"Schema.NET.ImageObject, Schema.NET\"," + - "\"@context\": \"https://schema.org\"," + - "\"@type\": \"ImageObject\"," + - "\"contentUrl\": \"https://i.scdn.co/image/5ded47fd3d05325dd0faaf4619481e1f25a21ec7\"" + - "}, {" + - "\"$type\": \"Schema.NET.ImageObject, Schema.NET\"," + - "\"@context\": \"https://schema.org\"," + - "\"@type\": \"ImageObject\"," + - "\"contentUrl\": \"https://is4-ssl.mzstatic.com/image/thumb/Music69/v4/cc/1c/90/cc1c9039-c3ba-4256-e251-1687df46cb0a/cover.jpg/1400x1400bb.jpeg\"" + - "}" + - "]," + - "\"sameAs\": \"https://music.apple.com/us/album/hail-to-the-thief/1097863576\"," + - "\"url\": \"https://open.spotify.com/album/1oW3v5Har9mvXnGk0x4fHm\"," + - "\"aggregateRating\": {" + - "\"@context\": \"https://schema.org\"," + - "\"@type\": \"AggregateRating\"," + - "\"bestRating\": 100," + - "\"ratingValue\": 60," + - "\"worstRating\": 0" + - "}," + - "\"datePublished\": \"2003-05-26\"," + - "\"offers\": {" + - "\"@context\": \"https://schema.org\"," + - "\"@type\": \"Offer\"," + - "\"gtin12\": \"634904078560\"" + - "}," + - "\"numTracks\": 14," + - "\"albumRelease\": {" + - "\"@context\": \"https://schema.org\"," + - "\"@type\": \"MusicRelease\"," + - "\"recordLabel\": {" + - "\"@context\": \"https://schema.org\"," + - "\"@type\": \"Organization\"," + - "\"name\": \"XL Recordings\"" + - "}" + - "}," + - "\"byArtist\": {" + - "\"@context\": \"https://schema.org\"," + - "\"@type\": \"MusicGroup\"," + - "\"name\": \"Radiohead\"," + - "\"identifier\": \"4Z8W4fKeB5YxbusRsdQVPb\"" + - "}" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "MusicAlbum", + "name": "Hail to the Thief", + "identifier": "1oW3v5Har9mvXnGk0x4fHm", + "image": [ + { + "@type": "ImageObject", + "contentUrl": "https://i.scdn.co/image/5ded47fd3d05325dd0faaf4619481e1f25a21ec7" + }, + { + "@type": "ImageObject", + "contentUrl": "https://is4-ssl.mzstatic.com/image/thumb/Music69/v4/cc/1c/90/cc1c9039-c3ba-4256-e251-1687df46cb0a/cover.jpg/1400x1400bb.jpeg" + } + ], + "sameAs": "https://music.apple.com/us/album/hail-to-the-thief/1097863576", + "url": "https://open.spotify.com/album/1oW3v5Har9mvXnGk0x4fHm", + "aggregateRating": { + "@type": "AggregateRating", + "bestRating": 100, + "ratingValue": 60, + "worstRating": 0 + }, + "datePublished": "2003-05-26", + "offers": { + "@type": "Offer", + "gtin12": "634904078560" + }, + "numTracks": 14, + "albumRelease": { + "@type": "MusicRelease", + "recordLabel": { + "@type": "Organization", + "name": "XL Recordings" + } + }, + "byArtist": { + "@type": "MusicGroup", + "name": "Radiohead", + "identifier": "4Z8W4fKeB5YxbusRsdQVPb" + } + } + """; + + [Fact] + public void ToString_MusicAlbumGoogleStructuredData_ReturnsExpectedJsonLd() => + Assert.Equal(this.json.MinifyJson(), this.musicAlbum.ToString()); [Fact] public void Deserializing_MusicAlbumJsonLd_ReturnsMusicAlbum() diff --git a/Tests/Schema.NET.Test/Examples/MusicEventTest.cs b/Tests/Schema.NET.Test/Examples/MusicEventTest.cs index f68dff72..bc2794dd 100644 --- a/Tests/Schema.NET.Test/Examples/MusicEventTest.cs +++ b/Tests/Schema.NET.Test/Examples/MusicEventTest.cs @@ -45,51 +45,48 @@ public class MusicEventTest StartDate = DateTimeOffset.Parse("2019-06-23T03:00:00-07:00", CultureInfo.InvariantCulture), // Recommended }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"MusicEvent\"," + - "\"name\": \"Arash & Tohi\"," + - "\"identifier\": \"vv1AaZAQ8Gkds-P77\"," + - "\"url\": \"https://www.ticketmaster.com/arash-tohi-hollywood-california-06-22-2019/event/09005690F1865104\"," + - "\"location\": {" + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"MusicVenue\"," + - "\"name\": \"Dolby Theatre\"," + - "\"identifier\": \"KovZpZAdtaEA\"" + - "}," + - "\"offers\": {" + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"Offer\"," + - "\"url\": \"https://www.ticketmaster.com/arash-tohi-hollywood-california-06-22-2019/event/09005690F1865104\"," + - "\"availabilityEnds\": \"2019-06-23T01:00:00-07:00\"," + - "\"availabilityStarts\": \"2019-04-24T21:00:00-07:00\"," + - "\"priceSpecification\": {" + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"PriceSpecification\"," + - "\"maxPrice\": 295," + - "\"minPrice\": 80," + - "\"priceCurrency\": \"USD\"" + - "}" + - "}," + - "\"performer\": [" + - "{" + - "\"$type\": \"Schema.NET.MusicGroup, Schema.NET\"," + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"MusicGroup\"," + - "\"name\": \"Arash\"," + - "\"identifier\": \"K8vZ917ukD7\"" + - "}," + - "{" + - "\"$type\": \"Schema.NET.MusicGroup, Schema.NET\"," + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"MusicGroup\"," + - "\"name\": \"Tohi\"," + - "\"identifier\": \"K8vZ917bA70\"" + - "}" + - "]," + - "\"startDate\": \"2019-06-23T03:00:00-07:00\"" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "MusicEvent", + "name": "Arash & Tohi", + "identifier": "vv1AaZAQ8Gkds-P77", + "url": "https://www.ticketmaster.com/arash-tohi-hollywood-california-06-22-2019/event/09005690F1865104", + "location": { + "@type": "MusicVenue", + "name": "Dolby Theatre", + "identifier": "KovZpZAdtaEA" + }, + "offers": { + "@type": "Offer", + "url": "https://www.ticketmaster.com/arash-tohi-hollywood-california-06-22-2019/event/09005690F1865104", + "availabilityEnds": "2019-06-23T01:00:00-07:00", + "availabilityStarts": "2019-04-24T21:00:00-07:00", + "priceSpecification": { + "@type": "PriceSpecification", + "maxPrice": 295, + "minPrice": 80, + "priceCurrency": "USD" + } + }, + "performer": [ + { + "$type": "Schema.NET.MusicGroup, Schema.NET", + "@type": "MusicGroup", + "name": "Arash", + "identifier": "K8vZ917ukD7" + }, + { + "$type": "Schema.NET.MusicGroup, Schema.NET", + "@type": "MusicGroup", + "name": "Tohi", + "identifier": "K8vZ917bA70" + } + ], + "startDate": "2019-06-23T03:00:00-07:00" + } + """; [Fact] public void Deserializing_MusicEventJsonLd_ReturnsMusicEvent() diff --git a/Tests/Schema.NET.Test/Examples/MusicGroupTest.cs b/Tests/Schema.NET.Test/Examples/MusicGroupTest.cs index 12f3f112..7c96851b 100644 --- a/Tests/Schema.NET.Test/Examples/MusicGroupTest.cs +++ b/Tests/Schema.NET.Test/Examples/MusicGroupTest.cs @@ -17,20 +17,25 @@ public class MusicGroupTest Url = new Uri("https://open.spotify.com/artist/4Z8W4fKeB5YxbusRsdQVPb"), // Recommended }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"MusicGroup\"," + - "\"name\": \"Radiohead\"," + - "\"identifier\": \"4Z8W4fKeB5YxbusRsdQVPb\"," + - "\"image\": {" + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"ImageObject\"," + - "\"contentUrl\": \"https://i.scdn.co/image/afcd616e1ef2d2786f47b3b4a8a6aeea24a72adc\"" + - "}," + - "\"sameAs\": \"https://music.apple.com/us/artist/radiohead/657515\"," + - "\"url\": \"https://open.spotify.com/artist/4Z8W4fKeB5YxbusRsdQVPb\"" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "MusicGroup", + "name": "Radiohead", + "identifier": "4Z8W4fKeB5YxbusRsdQVPb", + "image": { + "@type": "ImageObject", + "contentUrl": "https://i.scdn.co/image/afcd616e1ef2d2786f47b3b4a8a6aeea24a72adc" + }, + "sameAs": "https://music.apple.com/us/artist/radiohead/657515", + "url": "https://open.spotify.com/artist/4Z8W4fKeB5YxbusRsdQVPb" + } + """; + + [Fact] + public void ToString_MusicGroupGoogleStructuredData_ReturnsExpectedJsonLd() => + Assert.Equal(this.json.MinifyJson(), this.musicGroup.ToString()); [Fact] public void Deserializing_MusicGroupJsonLd_ReturnsMusicGroup() diff --git a/Tests/Schema.NET.Test/Examples/MusicRecordingTest.cs b/Tests/Schema.NET.Test/Examples/MusicRecordingTest.cs index fefb52cf..fc0e3f0c 100644 --- a/Tests/Schema.NET.Test/Examples/MusicRecordingTest.cs +++ b/Tests/Schema.NET.Test/Examples/MusicRecordingTest.cs @@ -32,37 +32,40 @@ public class MusicRecordingTest IsrcCode = "GBAYE0300801", // Recommended }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"MusicRecording\"," + - "\"name\": \"2 + 2 = 5\"," + - "\"identifier\": \"37kUGdEJJ7NaMl5LFW4EA4\"," + - "\"image\": {" + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"ImageObject\"," + - "\"contentUrl\": \"https://is4-ssl.mzstatic.com/image/thumb/Music69/v4/cc/1c/90/cc1c9039-c3ba-4256-e251-1687df46cb0a/cover.jpg/1400x1400bb.jpeg\"" + - "}," + - "\"sameAs\": \"https://music.apple.com/us/album/2-2-5/1097863576?i=1097863810\"," + - "\"url\": \"https://open.spotify.com/track/37kUGdEJJ7NaMl5LFW4EA4\"," + - "\"datePublished\": \"2003-05-26\"," + - "\"isFamilyFriendly\": true," + - "\"position\": \"1.1\"," + - "\"byArtist\": {" + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"MusicGroup\"," + - "\"name\": \"Radiohead\"," + - "\"identifier\": \"4Z8W4fKeB5YxbusRsdQVPb\"" + - "}," + - "\"duration\": \"PT3M19.36S\"," + - "\"inAlbum\": {" + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"MusicAlbum\"," + - "\"name\": \"Hail To the Thief\"," + - "\"identifier\": \"1oW3v5Har9mvXnGk0x4fHm\"" + - "}," + - "\"isrcCode\": \"GBAYE0300801\"" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "MusicRecording", + "name": "2 + 2 = 5", + "identifier": "37kUGdEJJ7NaMl5LFW4EA4", + "image": { + "@type": "ImageObject", + "contentUrl": "https://is4-ssl.mzstatic.com/image/thumb/Music69/v4/cc/1c/90/cc1c9039-c3ba-4256-e251-1687df46cb0a/cover.jpg/1400x1400bb.jpeg" + }, + "sameAs": "https://music.apple.com/us/album/2-2-5/1097863576?i=1097863810", + "url": "https://open.spotify.com/track/37kUGdEJJ7NaMl5LFW4EA4", + "datePublished": "2003-05-26", + "isFamilyFriendly": true, + "position": "1.1", + "byArtist": { + "@type": "MusicGroup", + "name": "Radiohead", + "identifier": "4Z8W4fKeB5YxbusRsdQVPb" + }, + "duration": "PT3M19.36S", + "inAlbum": { + "@type": "MusicAlbum", + "name": "Hail To the Thief", + "identifier": "1oW3v5Har9mvXnGk0x4fHm" + }, + "isrcCode": "GBAYE0300801" + } + """; + + [Fact] + public void ToString_MusicRecordingGoogleStructuredData_ReturnsExpectedJsonLd() => + Assert.Equal(this.json.MinifyJson(), this.musicRecording.ToString()); [Fact] public void Deserializing_MusicRecordingJsonLd_ReturnsMusicRecording() diff --git a/Tests/Schema.NET.Test/Examples/MusicVenueTest.cs b/Tests/Schema.NET.Test/Examples/MusicVenueTest.cs index 09edf7ad..294426fd 100644 --- a/Tests/Schema.NET.Test/Examples/MusicVenueTest.cs +++ b/Tests/Schema.NET.Test/Examples/MusicVenueTest.cs @@ -37,40 +37,43 @@ public class MusicVenueTest Telephone = "(323) 308-6300", // Recommended }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"MusicVenue\"," + - "\"name\": \"Dolby Theatre\"," + - "\"description\": \"Host to a range of prestigious events including movie premieres, concerts & the Oscars https://www.dolbytheatre.com\"," + - "\"identifier\": \"KovZpZAdtaEA\"," + - "\"image\": {" + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"ImageObject\"," + - "\"contentUrl\": \"https://s1.ticketm.net/dam/v/e6b/380fa861-3f46-44a9-a514-21553f7fbe6b_379601_SOURCE.jpg\"" + - "}," + - "\"sameAs\": [" + - "\"https://www.twitter.com/dolbytheatre\"," + - "\"https://www.dolbytheatre.com\"" + - "]," + - "\"url\": \"https://foursquare.com/dolbytheatre\"," + - "\"address\": {" + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"PostalAddress\"," + - "\"addressCountry\": \"US\"," + - "\"addressLocality\": \"Los Angeles\"," + - "\"addressRegion\": \"CA\"," + - "\"postalCode\": \"90028\"," + - "\"streetAddress\": \"6801 Hollywood Blvd\"" + - "}," + - "\"geo\": {" + - "\"@context\":\"https://schema.org\"," + - "\"@type\": \"GeoCoordinates\"," + - "\"latitude\": \"34.1018929033898\"," + - "\"longitude\": \"-118.340147939959\"" + - "}," + - "\"telephone\": \"(323) 308-6300\"" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "MusicVenue", + "name": "Dolby Theatre", + "description": "Host to a range of prestigious events including movie premieres, concerts & the Oscars https://www.dolbytheatre.com", + "identifier": "KovZpZAdtaEA", + "image": { + "@type": "ImageObject", + "contentUrl": "https://s1.ticketm.net/dam/v/e6b/380fa861-3f46-44a9-a514-21553f7fbe6b_379601_SOURCE.jpg" + }, + "sameAs": [ + "https://www.twitter.com/dolbytheatre", + "https://www.dolbytheatre.com" + ], + "url": "https://foursquare.com/dolbytheatre", + "address": { + "@type": "PostalAddress", + "addressCountry": "US", + "addressLocality": "Los Angeles", + "addressRegion": "CA", + "postalCode": "90028", + "streetAddress": "6801 Hollywood Blvd" + }, + "geo": { + "@type": "GeoCoordinates", + "latitude": "34.1018929033898", + "longitude": "-118.340147939959" + }, + "telephone": "(323) 308-6300" + } + """; + + [Fact] + public void ToString_MusicVenueGoogleStructuredData_ReturnsExpectedJsonLd() => + Assert.Equal(this.json.MinifyJson(), this.musicVenue.ToString()); [Fact] public void Deserializing_MusicVenueJsonLd_ReturnsMusicVenue() diff --git a/Tests/Schema.NET.Test/Examples/NewsArticleTest.cs b/Tests/Schema.NET.Test/Examples/NewsArticleTest.cs index 2572b836..0241df4d 100644 --- a/Tests/Schema.NET.Test/Examples/NewsArticleTest.cs +++ b/Tests/Schema.NET.Test/Examples/NewsArticleTest.cs @@ -34,40 +34,42 @@ public class NewsArticleTest Description = "A most wonderful article", // Ignored }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"NewsArticle\"," + - "\"description\":\"A most wonderful article\"," + - "\"image\":{" + - "\"@type\":\"ImageObject\"," + - "\"url\":\"https://google.com/thumbnail1.jpg\"," + - "\"height\":800," + - "\"width\":800" + - "}," + - "\"mainEntityOfPage\":\"https://google.com/article\"," + - "\"author\":{" + - "\"@type\":\"Person\"," + - "\"name\":\"John Doe\"" + - "}," + - "\"dateModified\":\"2015-02-05T09:20:00+00:00\"," + - "\"datePublished\":\"2015-02-05\"," + - "\"headline\":\"Article headline\"," + - "\"publisher\":{" + - "\"@type\":\"Organization\"," + - "\"name\":\"Google\"," + - "\"logo\":{" + - "\"@type\":\"ImageObject\"," + - "\"url\":\"https://google.com/logo.jpg\"," + - "\"height\":60," + - "\"width\":600" + - "}" + - "}" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "NewsArticle", + "description": "A most wonderful article", + "image": { + "@type": "ImageObject", + "url": "https://google.com/thumbnail1.jpg", + "height": 800, + "width": 800 + }, + "mainEntityOfPage": "https://google.com/article", + "author": { + "@type": "Person", + "name": "John Doe" + }, + "dateModified": "2015-02-05T09:20:00+00:00", + "datePublished": "2015-02-05", + "headline": "Article headline", + "publisher": { + "@type": "Organization", + "name": "Google", + "logo": { + "@type": "ImageObject", + "url": "https://google.com/logo.jpg", + "height": 60, + "width": 600 + } + } + } + """; [Fact] public void ToString_ArticleGoogleStructuredData_ReturnsExpectedJsonLd() => - Assert.Equal(this.json, this.article.ToString()); + Assert.Equal(this.json.MinifyJson(), this.article.ToString()); [Fact] public void Deserializing_NewsArticleJsonLd_ReturnsNewsArticle() diff --git a/Tests/Schema.NET.Test/Examples/OrganizationTest.cs b/Tests/Schema.NET.Test/Examples/OrganizationTest.cs index adf5e16c..00499df6 100644 --- a/Tests/Schema.NET.Test/Examples/OrganizationTest.cs +++ b/Tests/Schema.NET.Test/Examples/OrganizationTest.cs @@ -21,25 +21,27 @@ public class OrganizationTest Logo = new Uri("https://example.com/logo.png"), }; - private readonly string json = - @"{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"Organization\"," + - "\"url\":\"https://example.com\"," + - "\"areaServed\":\"GB\"," + - "\"contactPoint\":{" + - "\"@type\":\"ContactPoint\"," + - "\"availableLanguage\":\"English\"," + - "\"contactOption\":\"https://schema.org/TollFree\"," + - "\"contactType\":\"customer service\"," + - "\"telephone\":\"+1-401-555-1212\"" + - "}," + - "\"logo\":\"https://example.com/logo.png\"" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "Organization", + "url": "https://example.com", + "areaServed": "GB", + "contactPoint": { + "@type": "ContactPoint", + "availableLanguage": "English", + "contactOption": "https://schema.org/TollFree", + "contactType": "customer service", + "telephone": "+1-401-555-1212" + }, + "logo": "https://example.com/logo.png" + } + """; [Fact] public void ToString_CorporateContactsGoogleStructuredData_ReturnsExpectedJsonLd() => - Assert.Equal(this.json, this.organization.ToString()); + Assert.Equal(this.json.MinifyJson(), this.organization.ToString()); [Fact] public void Deserializing_OrganizationJsonLd_ReturnsOrganization() diff --git a/Tests/Schema.NET.Test/Examples/PersonTest.cs b/Tests/Schema.NET.Test/Examples/PersonTest.cs index 053f9fd6..1fd89b0b 100644 --- a/Tests/Schema.NET.Test/Examples/PersonTest.cs +++ b/Tests/Schema.NET.Test/Examples/PersonTest.cs @@ -20,23 +20,25 @@ public class PersonTest Url = new Uri("https://example.com"), // Required }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"Person\"," + - "\"name\":\"Name\"," + - "\"sameAs\":[" + - "\"https://www.facebook.com/your-profile\"," + - "\"https://instagram.com/yourProfile\"," + - "\"https://www.linkedin.com/in/yourprofile\"," + - "\"https://plus.google.com/your_profile\"" + - "]," + - "\"url\":\"https://example.com\"" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "Person", + "name": "Name", + "sameAs": [ + "https://www.facebook.com/your-profile", + "https://instagram.com/yourProfile", + "https://www.linkedin.com/in/yourprofile", + "https://plus.google.com/your_profile" + ], + "url": "https://example.com" + } + """; [Fact] public void ToString_SiteNameGoogleStructuredData_ReturnsExpectedJsonLd() => - Assert.Equal(this.json, this.person.ToString()); + Assert.Equal(this.json.MinifyJson(), this.person.ToString()); [Fact] public void Deserializing_PersonJsonLd_ReturnsPerson() diff --git a/Tests/Schema.NET.Test/Examples/ProductTest.cs b/Tests/Schema.NET.Test/Examples/ProductTest.cs index 1c01b4a7..3ce9de63 100644 --- a/Tests/Schema.NET.Test/Examples/ProductTest.cs +++ b/Tests/Schema.NET.Test/Examples/ProductTest.cs @@ -40,40 +40,42 @@ public class ProductTest }, }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"Product\"," + - "\"name\":\"Executive Anvil\"," + - "\"description\":\"Sleeker than ACME's Classic Anvil, the Executive Anvil is perfect for the business traveller looking for something to drop from a height.\"," + - "\"image\":\"https://www.example.com/anvil_executive.jpg\"," + - "\"aggregateRating\":{" + - "\"@type\":\"AggregateRating\"," + - "\"ratingValue\":4.4," + - "\"reviewCount\":89" + - "}," + - "\"brand\":{" + - "\"@type\":\"Brand\"," + - "\"name\":\"ACME\"" + - "}," + - "\"mpn\":\"925872\"," + - "\"offers\":{" + - "\"@type\":\"Offer\"," + - "\"availability\":\"https://schema.org/InStock\"," + - "\"itemCondition\":\"https://schema.org/UsedCondition\"," + - "\"price\":119.99," + - "\"priceCurrency\":\"USD\"," + - "\"priceValidUntil\":\"2020-11-05\"," + - "\"seller\":{" + - "\"@type\":\"Organization\"," + - "\"name\":\"Executive Objects\"" + - "}" + - "}" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "Product", + "name": "Executive Anvil", + "description": "Sleeker than ACME's Classic Anvil, the Executive Anvil is perfect for the business traveller looking for something to drop from a height.", + "image": "https://www.example.com/anvil_executive.jpg", + "aggregateRating": { + "@type": "AggregateRating", + "ratingValue": 4.4, + "reviewCount": 89 + }, + "brand": { + "@type": "Brand", + "name": "ACME" + }, + "mpn": "925872", + "offers": { + "@type": "Offer", + "availability": "https://schema.org/InStock", + "itemCondition": "https://schema.org/UsedCondition", + "price": 119.99, + "priceCurrency": "USD", + "priceValidUntil": "2020-11-05", + "seller": { + "@type": "Organization", + "name": "Executive Objects" + } + } + } + """; [Fact] public void ToString_ProductGoogleStructuredData_ReturnsExpectedJsonLd() => - Assert.Equal(this.json, this.product.ToString()); + Assert.Equal(this.json.MinifyJson(), this.product.ToString()); [Fact] public void Deserializing_ProductJsonLd_ReturnsProduct() diff --git a/Tests/Schema.NET.Test/Examples/RecipeTest.cs b/Tests/Schema.NET.Test/Examples/RecipeTest.cs index 0b566d1e..a7e174d4 100644 --- a/Tests/Schema.NET.Test/Examples/RecipeTest.cs +++ b/Tests/Schema.NET.Test/Examples/RecipeTest.cs @@ -44,50 +44,54 @@ public class RecipeTest }, }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"Recipe\"," + - "\"name\":\"Grandma's Holiday Apple Pie\"," + - "\"description\":\"This is my grandmother's apple pie recipe. I like to add a dash of nutmeg.\"," + - "\"image\":\"https://example.com/image.jpg\"," + - "\"aggregateRating\":{" + - "\"@type\":\"AggregateRating\"," + - "\"ratingValue\":4," + - "\"reviewCount\":35" + - "}," + - "\"author\":{" + - "\"@type\":\"Person\"," + - "\"name\":\"Carol Smith\"" + - "}," + - "\"datePublished\":\"2009-11-05\"," + - "\"prepTime\":\"PT30M\"," + - "\"totalTime\":\"PT1H30M\"," + - "\"cookTime\":\"PT1H\"," + - "\"nutrition\":{" + - "\"@type\":\"NutritionInformation\"," + - "\"calories\":\"250 cal\"," + - "\"fatContent\":\"12 g\"," + - "\"servingSize\":\"1 medium slice\"" + - "}," + - "\"recipeIngredient\":[" + - "\"Thinly-sliced apples:6 cups\"," + - "\"White sugar:3/4 cup\"" + - "]," + - "\"recipeInstructions\":[{" + - "\"@type\":\"HowToStep\"," + - "\"text\":\"1. Cut and peel apples...\"" + - "},{" + - "\"@type\":\"HowToStep\"," + - "\"text\":\"2. Put in pie shell...\"" + - "}" + - "]," + - "\"recipeYield\":\"1 9 inch pie (8 servings)\"" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "Recipe", + "name": "Grandma's Holiday Apple Pie", + "description": "This is my grandmother's apple pie recipe. I like to add a dash of nutmeg.", + "image": "https://example.com/image.jpg", + "aggregateRating": { + "@type": "AggregateRating", + "ratingValue": 4, + "reviewCount": 35 + }, + "author": { + "@type": "Person", + "name": "Carol Smith" + }, + "datePublished": "2009-11-05", + "prepTime": "PT30M", + "totalTime": "PT1H30M", + "cookTime": "PT1H", + "nutrition": { + "@type": "NutritionInformation", + "calories": "250 cal", + "fatContent": "12 g", + "servingSize": "1 medium slice" + }, + "recipeIngredient": [ + "Thinly-sliced apples:6 cups", + "White sugar:3/4 cup" + ], + "recipeInstructions": [ + { + "@type": "HowToStep", + "text": "1. Cut and peel apples..." + }, + { + "@type": "HowToStep", + "text": "2. Put in pie shell..." + } + ], + "recipeYield": "1 9 inch pie (8 servings)" + } + """; [Fact] public void ToString_CarouselGoogleStructuredData_ReturnsExpectedJsonLd() => - Assert.Equal(this.json, this.recipe.ToString()); + Assert.Equal(this.json.MinifyJson(), this.recipe.ToString()); [Fact] public void Deserializing_RecipeJsonLd_ReturnsRecipe() diff --git a/Tests/Schema.NET.Test/Examples/RestaurantTest.cs b/Tests/Schema.NET.Test/Examples/RestaurantTest.cs index 8e264b72..6a9b3f31 100644 --- a/Tests/Schema.NET.Test/Examples/RestaurantTest.cs +++ b/Tests/Schema.NET.Test/Examples/RestaurantTest.cs @@ -60,65 +60,67 @@ public class RestaurantTest }, }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"Restaurant\"," + - "\"@id\":\"https://davessteakhouse.example.com\"," + - "\"name\":\"Dave's Steak House\"," + - "\"image\":\"https://davessteakhouse.example.com/logo.jpg\"," + - "\"sameAs\":\"https://davessteakhouse.example.com\"," + - "\"address\":{" + - "\"@type\":\"PostalAddress\"," + - "\"addressCountry\":\"US\"," + - "\"addressLocality\":\"New York\"," + - "\"addressRegion\":\"NY\"," + - "\"postalCode\":\"10019\"," + - "\"streetAddress\":\"148 W 51st St\"" + - "}," + - "\"aggregateRating\":{" + - "\"@type\":\"AggregateRating\"," + - "\"bestRating\":100," + - "\"ratingValue\":88," + - "\"worstRating\":1," + - "\"ratingCount\":20" + - "}," + - "\"geo\":{" + - "\"@type\":\"GeoCoordinates\"," + - "\"latitude\":40.761293," + - "\"longitude\":-73.982294" + - "}," + - "\"review\":{" + - "\"@type\":\"Review\"," + - "\"description\":\"Great old fashioned steaks but the salads are sub par.\"," + - "\"url\":\"https://www.localreviews.com/restaurants/1/2/3/daves-steak-house.html\"," + - "\"author\":{" + - "\"@type\":\"Person\"," + - "\"name\":\"Lisa Kennedy\"," + - "\"sameAs\":\"https://plus.google.com/114108465800532712602\"" + - "}," + - "\"datePublished\":\"2014-03-13\"," + - "\"inLanguage\":\"en\"," + - "\"publisher\":{" + - "\"@type\":\"Organization\"," + - "\"name\":\"Denver Post\"," + - "\"sameAs\":\"https://www.denverpost.com\"" + - "}," + - "\"reviewRating\":{" + - "\"@type\":\"Rating\"," + - "\"bestRating\":4," + - "\"ratingValue\":3.5," + - "\"worstRating\":1" + - "}" + - "}," + - "\"telephone\":\"+12122459600\"," + - "\"priceRange\":\"$$$\"," + - "\"servesCuisine\":\"Steak House\"" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "Restaurant", + "@id": "https://davessteakhouse.example.com", + "name": "Dave's Steak House", + "image": "https://davessteakhouse.example.com/logo.jpg", + "sameAs": "https://davessteakhouse.example.com", + "address": { + "@type": "PostalAddress", + "addressCountry": "US", + "addressLocality": "New York", + "addressRegion": "NY", + "postalCode": "10019", + "streetAddress": "148 W 51st St" + }, + "aggregateRating": { + "@type": "AggregateRating", + "bestRating": 100, + "ratingValue": 88, + "worstRating": 1, + "ratingCount": 20 + }, + "geo": { + "@type": "GeoCoordinates", + "latitude": 40.761293, + "longitude": -73.982294 + }, + "review": { + "@type": "Review", + "description": "Great old fashioned steaks but the salads are sub par.", + "url": "https://www.localreviews.com/restaurants/1/2/3/daves-steak-house.html", + "author": { + "@type": "Person", + "name": "Lisa Kennedy", + "sameAs": "https://plus.google.com/114108465800532712602" + }, + "datePublished": "2014-03-13", + "inLanguage": "en", + "publisher": { + "@type": "Organization", + "name": "Denver Post", + "sameAs": "https://www.denverpost.com" + }, + "reviewRating": { + "@type": "Rating", + "bestRating": 4, + "ratingValue": 3.5, + "worstRating": 1 + } + }, + "telephone": "+12122459600", + "priceRange": "$$$", + "servesCuisine": "Steak House" + } + """; [Fact] public void ToString_RestaurantGoogleStructuredData_ReturnsExpectedJsonLd() => - Assert.Equal(this.json, this.restaurant.ToString()); + Assert.Equal(this.json.MinifyJson(), this.restaurant.ToString()); [Fact] public void Deserializing_RestaurantJsonLd_ReturnsRestaurant() diff --git a/Tests/Schema.NET.Test/Examples/VideoObjectTest.cs b/Tests/Schema.NET.Test/Examples/VideoObjectTest.cs index ab7a3505..dd496ae1 100644 --- a/Tests/Schema.NET.Test/Examples/VideoObjectTest.cs +++ b/Tests/Schema.NET.Test/Examples/VideoObjectTest.cs @@ -32,37 +32,39 @@ public class VideoObjectTest }, }; - private readonly string json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"VideoObject\"," + - "\"name\":\"Title\"," + - "\"description\":\"Video description\"," + - "\"expires\":\"2016-02-05\"," + - "\"interactionStatistic\":{" + - "\"@type\":\"InteractionCounter\"," + - "\"userInteractionCount\":2347" + - "}," + - "\"publisher\":{" + - "\"@type\":\"Organization\"," + - "\"name\":\"Example Publisher\"," + - "\"logo\":{" + - "\"@type\":\"ImageObject\"," + - "\"url\":\"https://example.com/logo.jpg\"," + - "\"height\":60," + - "\"width\":600" + - "}" + - "}," + - "\"thumbnailUrl\":\"https://www.example.com/thumbnail.jpg\"," + - "\"contentUrl\":\"https://www.example.com/video123.flv\"," + - "\"duration\":\"PT1M33S\"," + - "\"embedUrl\":\"https://www.example.com/videoplayer.swf?video=123\"," + - "\"uploadDate\":\"2015-02-05\"" + - "}"; + private readonly string json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "VideoObject", + "name": "Title", + "description": "Video description", + "expires": "2016-02-05", + "interactionStatistic": { + "@type": "InteractionCounter", + "userInteractionCount": 2347 + }, + "publisher":{ + "@type": "Organization", + "name": "Example Publisher", + "logo": { + "@type": "ImageObject", + "url": "https://example.com/logo.jpg", + "height": 60, + "width": 600 + } + }, + "thumbnailUrl": "https://www.example.com/thumbnail.jpg", + "contentUrl": "https://www.example.com/video123.flv", + "duration": "PT1M33S", + "embedUrl": "https://www.example.com/videoplayer.swf?video=123", + "uploadDate": "2015-02-05" + } + """; [Fact] public void ToString_VideoGoogleStructuredData_ReturnsExpectedJsonLd() => - Assert.Equal(this.json, this.videoObject.ToString()); + Assert.Equal(this.json.MinifyJson(), this.videoObject.ToString()); [Fact] public void Deserializing_VideoObjectJsonLd_ReturnsVideoObject() diff --git a/Tests/Schema.NET.Test/Examples/WebsiteTest.cs b/Tests/Schema.NET.Test/Examples/WebsiteTest.cs index db256c14..b722f61a 100644 --- a/Tests/Schema.NET.Test/Examples/WebsiteTest.cs +++ b/Tests/Schema.NET.Test/Examples/WebsiteTest.cs @@ -18,21 +18,23 @@ public void ToString_SiteLinksSearchBoxGoogleStructuredData_ReturnsExpectedJsonL }, Url = new Uri("https://example.com"), // Required }; - var expectedJson = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"WebSite\"," + - "\"potentialAction\":{" + - "\"@type\":\"SearchAction\"," + - "\"target\":\"https://example.com/search?&q={query}\"," + - "\"query-input\":\"required\"" + - "}," + - "\"url\":\"https://example.com\"" + - "}"; + var expectedJson = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "WebSite", + "potentialAction": { + "@type": "SearchAction", + "target": "https://example.com/search?&q={query}", + "query-input": "required" + }, + "url": "https://example.com" + } + """; var json = website.ToString(); - Assert.Equal(expectedJson, json); + Assert.Equal(expectedJson.MinifyJson(), json); } // https://developers.google.com/search/docs/data-types/sitename @@ -45,18 +47,20 @@ public void ToString_SiteNameGoogleStructuredData_ReturnsExpectedJsonLd() Name = "Your Site Name", // Required Url = new Uri("https://example.com"), // Required }; - var expectedJson = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"WebSite\"," + - "\"name\":\"Your Site Name\"," + - "\"alternateName\":\"An Alternative Name\"," + - "\"url\":\"https://example.com\"" + - "}"; + var expectedJson = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "WebSite", + "name": "Your Site Name", + "alternateName": "An Alternative Name", + "url": "https://example.com" + } + """; var json = website.ToString(); - Assert.Equal(expectedJson, json); + Assert.Equal(expectedJson.MinifyJson(), json); } [Fact] @@ -72,17 +76,19 @@ public void Deserializing_WebSiteJsonLd_ReturnsWebSite() Url = new Uri("https://example.com"), // Required }; - var json = - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"WebSite\"," + - "\"potentialAction\":{" + - "\"@type\":\"SearchAction\"," + - "\"target\":\"https://example.com/search?&q={query}\"," + - "\"query-input\":\"required\"" + - "}," + - "\"url\":\"https://example.com\"" + - "}"; + var json = /*lang=json,strict*/ + """ + { + "@context": "https://schema.org", + "@type": "WebSite", + "potentialAction": { + "@type": "SearchAction", + "target": "https://example.com/search?&q={query}", + "query-input": "required" + }, + "url": "https://example.com" + } + """; Assert.Equal(website.ToString(), SchemaSerializer.DeserializeObject(json)!.ToString()); Assert.Equal(SchemaSerializer.SerializeObject(website), SchemaSerializer.SerializeObject(SchemaSerializer.DeserializeObject(json)!)); diff --git a/Tests/Schema.NET.Test/OneOrManyTest.cs b/Tests/Schema.NET.Test/OneOrManyTest.cs index f1407ee3..ee0c1260 100644 --- a/Tests/Schema.NET.Test/OneOrManyTest.cs +++ b/Tests/Schema.NET.Test/OneOrManyTest.cs @@ -4,6 +4,9 @@ namespace Schema.NET.Test; using System.Collections.Generic; using System.Linq; using Xunit; +#pragma warning disable IDE0001 // Simplify Names +using HashCode = Schema.NET.HashCode; +#pragma warning restore IDE0001 // Simplify Names public class OneOrManyTest { @@ -232,7 +235,7 @@ public void GetHashCode_OneItem_HashCodeEqualToSingleItem() => [Fact] public void GetHashCode_TwoItems_HashCodeEqualToTwoItems() => - Assert.Equal(NET.HashCode.OfEach(new List() { 1, 2 }), new OneOrMany(1, 2).GetHashCode()); + Assert.Equal(HashCode.OfEach(new List() { 1, 2 }), new OneOrMany(1, 2).GetHashCode()); [Theory] [InlineData(null)] @@ -242,7 +245,12 @@ public void GetHashCode_TwoItems_HashCodeEqualToTwoItems() => public void ToString_NullEmptyOrWhiteSpace_BookOmitsNameProperty(string name) { var book = new Book() { Name = name }; - Assert.Equal("{\"@context\":\"https://schema.org\",\"@type\":\"Book\"}", book.ToString()); + + var expectedJson = /*lang=json,strict*/ + """ + {"@context":"https://schema.org","@type":"Book"} + """; + Assert.Equal(expectedJson, book.ToString()); } [Theory] @@ -253,7 +261,12 @@ public void ToString_NullEmptyOrWhiteSpace_BookOmitsNameProperty(string name) public void ToString_NullEmptyOrWhiteSpace_OrganizationOmitsAddressProperty(string address) { var organization = new Organization() { Address = address }; - Assert.Equal("{\"@context\":\"https://schema.org\",\"@type\":\"Organization\"}", organization.ToString()); + + var expectedJson = /*lang=json,strict*/ + """ + {"@context":"https://schema.org","@type":"Organization"} + """; + Assert.Equal(expectedJson, organization.ToString()); } [Theory] @@ -264,7 +277,12 @@ public void ToString_NullEmptyOrWhiteSpace_OrganizationOmitsAddressProperty(stri public void ToString_NullEmptyOrWhiteSpace_BookOmitsNamePropertyFromList(string name) { var book = new Book() { Name = new List { "Hamlet", name } }; - Assert.Equal("{\"@context\":\"https://schema.org\",\"@type\":\"Book\",\"name\":\"Hamlet\"}", book.ToString()); + + var expectedJson = /*lang=json,strict*/ + """ + {"@context":"https://schema.org","@type":"Book","name":"Hamlet"} + """; + Assert.Equal(expectedJson, book.ToString()); } [Theory] @@ -275,7 +293,12 @@ public void ToString_NullEmptyOrWhiteSpace_BookOmitsNamePropertyFromList(string public void ToString_NullEmptyOrWhiteSpace_BookOmitsNamePropertyFromArray(string name) { var book = new Book() { Name = new string[] { "Hamlet", name } }; - Assert.Equal("{\"@context\":\"https://schema.org\",\"@type\":\"Book\",\"name\":\"Hamlet\"}", book.ToString()); + + var expectedJson = /*lang=json,strict*/ + """ + {"@context":"https://schema.org","@type":"Book","name":"Hamlet"} + """; + Assert.Equal(expectedJson, book.ToString()); } [Theory] @@ -286,7 +309,12 @@ public void ToString_NullEmptyOrWhiteSpace_BookOmitsNamePropertyFromArray(string public void ToString_EmptyOrWhiteSpace_OrganizationOmitsAddressProperty(string address) { var organization = new Organization() { Address = address }; - Assert.Equal("{\"@context\":\"https://schema.org\",\"@type\":\"Organization\"}", organization.ToString()); + + var expectedJson = /*lang=json,strict*/ + """ + {"@context":"https://schema.org","@type":"Organization"} + """; + Assert.Equal(expectedJson, organization.ToString()); } [Theory] @@ -297,7 +325,12 @@ public void ToString_EmptyOrWhiteSpace_OrganizationOmitsAddressProperty(string a public void ToString_NullEmptyOrWhiteSpace_OrganizationOmitsNamePropertyFromList(string address) { var organization = new Organization() { Name = new List { "Cardiff, UK", address } }; - Assert.Equal("{\"@context\":\"https://schema.org\",\"@type\":\"Organization\",\"name\":\"Cardiff, UK\"}", organization.ToString()); + + var expectedJson = /*lang=json,strict*/ + """ + {"@context":"https://schema.org","@type":"Organization","name":"Cardiff, UK"} + """; + Assert.Equal(expectedJson, organization.ToString()); } [Theory] @@ -308,6 +341,11 @@ public void ToString_NullEmptyOrWhiteSpace_OrganizationOmitsNamePropertyFromList public void ToString_NullEmptyOrWhiteSpace_OrganizationOmitsNamePropertyFromArray(string address) { var organization = new Organization() { Name = new string[] { "Cardiff, UK", address } }; - Assert.Equal("{\"@context\":\"https://schema.org\",\"@type\":\"Organization\",\"name\":\"Cardiff, UK\"}", organization.ToString()); + + var expectedJson = /*lang=json,strict*/ + """ + {"@context":"https://schema.org","@type":"Organization","name":"Cardiff, UK"} + """; + Assert.Equal(expectedJson, organization.ToString()); } } diff --git a/Tests/Schema.NET.Test/Schema.NET.Test.csproj b/Tests/Schema.NET.Test/Schema.NET.Test.csproj index 51290ed6..69cb724d 100644 --- a/Tests/Schema.NET.Test/Schema.NET.Test.csproj +++ b/Tests/Schema.NET.Test/Schema.NET.Test.csproj @@ -1,13 +1,17 @@ - net6.0;net5.0;net472 + net7.0;net6.0;net472 + + + + diff --git a/Tests/Schema.NET.Test/StringExtensions.cs b/Tests/Schema.NET.Test/StringExtensions.cs index d452f1bd..3b078869 100644 --- a/Tests/Schema.NET.Test/StringExtensions.cs +++ b/Tests/Schema.NET.Test/StringExtensions.cs @@ -1,13 +1,36 @@ -#if NET472 || NET461 namespace Schema.NET.Test; +#if NET472 || NET461 using System; - -#pragma warning disable IDE0060 // Remove unused parameter -#pragma warning disable CA1801 // Remove unused parameter +#endif +using System.IO; +using Newtonsoft.Json; public static class StringExtensions { +#if NET472 || NET461 +#pragma warning disable IDE0060 // Remove unused parameter public static int GetHashCode(this string target, StringComparison stringComparison) => target.GetHashCode(); -} +#pragma warning restore IDE0060 // Remove unused parameter + #endif + public static string MinifyJson(this string json, Formatting formatting = Formatting.None) + { + using (var stringReader = new StringReader(json)) + using (var stringWriter = new StringWriter()) + { + MinifyJson(stringReader, stringWriter, formatting); + return stringWriter.ToString(); + } + } + + private static void MinifyJson(TextReader textReader, TextWriter textWriter, Formatting formatting) + { + using (JsonReader jsonReader = new JsonTextReader(textReader)) + using (JsonWriter jsonWriter = new JsonTextWriter(textWriter)) + { + jsonWriter.Formatting = formatting; + jsonWriter.WriteToken(jsonReader); + } + } +} diff --git a/Tests/Schema.NET.Test/ValuesJsonConverterTest.cs b/Tests/Schema.NET.Test/ValuesJsonConverterTest.cs index 61103413..4fceffbf 100644 --- a/Tests/Schema.NET.Test/ValuesJsonConverterTest.cs +++ b/Tests/Schema.NET.Test/ValuesJsonConverterTest.cs @@ -20,7 +20,12 @@ public void WriteJson_Values_OneCountWritesSingle() { var value = new Values("One Value"); var json = SerializeObject(value); - Assert.Equal("{\"Property\":\"One Value\"}", json); + + var expectedJson = /*lang=json,strict*/ + """ + {"Property":"One Value"} + """; + Assert.Equal(expectedJson, json); } [Fact] @@ -28,7 +33,12 @@ public void WriteJson_Values_GreaterThanOneCountWritesArray() { var value = new Values(new[] { "A", "B" }); var json = SerializeObject(value); - Assert.Equal("{\"Property\":[\"A\",\"B\"]}", json); + + var expectedJson = /*lang=json,strict*/ + """ + {"Property":["A","B"]} + """; + Assert.Equal(expectedJson, json); } [Fact] @@ -36,7 +46,12 @@ public void WriteJson_Values_MixedValueTypes() { var value = new Values(new object[] { 123, "B" }); var json = SerializeObject(value); - Assert.Equal("{\"Property\":[123,\"B\"]}", json); + + var expectedJson = /*lang=json,strict*/ + """ + {"Property":[123,"B"]} + """; + Assert.Equal(expectedJson, json); } [Fact] @@ -44,6 +59,7 @@ public void WriteJson_OneOrMany_ZeroCountWritesNull() { var value = default(OneOrMany); var json = SerializeObject(value); + Assert.Equal("{}", json); } @@ -52,7 +68,12 @@ public void WriteJson_OneOrMany_OneCountWritesSingle() { var value = new OneOrMany("One Value"); var json = SerializeObject(value); - Assert.Equal("{\"Property\":\"One Value\"}", json); + + var expectedJson = /*lang=json,strict*/ + """" + {"Property":"One Value"} + """"; + Assert.Equal(expectedJson, json); } [Fact] @@ -60,7 +81,12 @@ public void WriteJson_OneOrMany_GreaterThanOneCountWritesArray() { var value = new OneOrMany(new[] { "A", "B" }); var json = SerializeObject(value); - Assert.Equal("{\"Property\":[\"A\",\"B\"]}", json); + + var expectedJson = /*lang=json,strict*/ + """" + {"Property":["A","B"]} + """"; + Assert.Equal(expectedJson, json); } [Fact] @@ -68,7 +94,12 @@ public void WriteJson_DateTime_ISO8601_DateTime() { var value = new OneOrMany(new DateTime(2000, 1, 1, 12, 34, 56)); var json = SerializeObject(value); - Assert.Equal("{\"Property\":\"2000-01-01T12:34:56\"}", json); + + var expectedJson = /*lang=json,strict*/ + """" + {"Property":"2000-01-01T12:34:56"} + """"; + Assert.Equal(expectedJson, json); } [Fact] @@ -76,7 +107,12 @@ public void WriteJson_DateTimeOffset_ISO8601_DateTimeWithTimeOffset() { var value = new OneOrMany(new DateTimeOffset(2000, 1, 1, 12, 34, 56, TimeSpan.FromHours(1))); var json = SerializeObject(value); - Assert.Equal("{\"Property\":\"2000-01-01T12:34:56+01:00\"}", json); + + var expectedJson = /*lang=json,strict*/ + """" + {"Property":"2000-01-01T12:34:56+01:00"} + """"; + Assert.Equal(expectedJson, json); } [Fact] @@ -84,21 +120,33 @@ public void WriteJson_TimeSpan_ISO8601_TimeOfDay() { var value = new OneOrMany(new TimeSpan(12, 34, 56)); var json = SerializeObject(value); - Assert.Equal("{\"Property\":\"12:34:56\"}", json); + + var expectedJson = /*lang=json,strict*/ + """" + {"Property":"12:34:56"} + """"; + Assert.Equal(expectedJson, json); } [Fact] public void ReadJson_Values_SingleValue_String() { - var json = "{\"Property\":\"Test String\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "Test String"} + """; var result = DeserializeObject>(json); + Assert.Equal("Test String", result.Value2.First()); } [Fact] public void ReadJson_Values_SingleValue_IntegerAsString() { - var json = "{\"Property\":\"123\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "123"} + """; var result = DeserializeObject>(json); Assert.Equal(123, result.Value2.First()); } @@ -106,7 +154,10 @@ public void ReadJson_Values_SingleValue_IntegerAsString() [Fact] public void ReadJson_Values_SingleValue_LongAsString() { - var json = "{\"Property\":\"8294967295\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "8294967295"} + """; var result = DeserializeObject>(json); Assert.Equal(8294967295, result.Value2.First()); } @@ -114,7 +165,10 @@ public void ReadJson_Values_SingleValue_LongAsString() [Fact] public void ReadJson_Values_SingleValue_FloatAsString() { - var json = "{\"Property\":\"123.45\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "123.45"} + """; var result = DeserializeObject>(json); Assert.Equal(123.45f, result.Value2.First()); } @@ -122,7 +176,10 @@ public void ReadJson_Values_SingleValue_FloatAsString() [Fact] public void ReadJson_Values_SingleValue_DoubleAsString() { - var json = "{\"Property\":\"123.45\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "123.45"} + """; var result = DeserializeObject>(json); Assert.Equal(123.45, result.Value2.First()); } @@ -130,7 +187,10 @@ public void ReadJson_Values_SingleValue_DoubleAsString() [Fact] public void ReadJson_Values_SingleValue_BooleanAsString() { - var json = "{\"Property\":\"true\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "true"} + """; var result = DeserializeObject>(json); Assert.True(result.Value2.First()); } @@ -138,7 +198,10 @@ public void ReadJson_Values_SingleValue_BooleanAsString() [Fact] public void ReadJson_Values_SingleValue_GuidAsString() { - var json = "{\"Property\":\"13ec75b3-250c-48a2-8bd0-dfee62852bd4\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "13ec75b3-250c-48a2-8bd0-dfee62852bd4"} + """; var result = DeserializeObject>(json); Assert.Equal(new Guid("13ec75b3-250c-48a2-8bd0-dfee62852bd4"), result.Value2.First()); } @@ -146,7 +209,10 @@ public void ReadJson_Values_SingleValue_GuidAsString() [Fact] public void ReadJson_Values_SingleValue_NullablePrimitiveAsString() { - var json = "{\"Property\":\"123\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "123"} + """; var result = DeserializeObject>(json); Assert.Equal(123, result.Value2.First()); } @@ -154,7 +220,10 @@ public void ReadJson_Values_SingleValue_NullablePrimitiveAsString() [Fact] public void ReadJson_Values_SingleValue_Primitive() { - var json = "{\"Property\":123}"; + var json = /*lang=json,strict*/ + """ + {"Property": 123} + """; var result = DeserializeObject>(json); Assert.Equal(123, result.Value2.First()); } @@ -162,7 +231,10 @@ public void ReadJson_Values_SingleValue_Primitive() [Fact] public void ReadJson_Values_SingleValue_DecimalAsString() { - var json = "{\"Property\":\"123.456\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "123.456"} + """; var result = DeserializeObject>(json); Assert.Equal(123.456m, result.Value2.First()); } @@ -170,7 +242,10 @@ public void ReadJson_Values_SingleValue_DecimalAsString() [Fact] public void ReadJson_Values_SingleValue_Decimal() { - var json = "{\"Property\":123.456}"; + var json = /*lang=json,strict*/ + """ + {"Property": 123.456} + """; var result = DeserializeObject>(json); Assert.Equal(123.456m, result.Value2.First()); } @@ -178,7 +253,10 @@ public void ReadJson_Values_SingleValue_Decimal() [Fact] public void ReadJson_Values_SingleValue_DateTimeAsISO8601String() { - var json = "{\"Property\":\"2000-01-01T12:34\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "2000-01-01T12:34"} + """; var result = DeserializeObject>(json); Assert.Equal(new DateTime(2000, 1, 1, 12, 34, 0), result.Value2.First()); } @@ -186,7 +264,10 @@ public void ReadJson_Values_SingleValue_DateTimeAsISO8601String() [Fact] public void ReadJson_Values_SingleValue_DateTimeAsMicrosoftDateTimeString() { - var json = "{\"Property\":\"\\/Date(946730040000)\\/\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "\/Date(946730040000)\/"} + """; var result = DeserializeObject>(json); Assert.Equal(new DateTime(2000, 1, 1, 12, 34, 0), result.Value2.First()); } @@ -194,7 +275,10 @@ public void ReadJson_Values_SingleValue_DateTimeAsMicrosoftDateTimeString() [Fact] public void ReadJson_Values_SingleValue_DateTimeNegativeOffsetAsMicrosoftDateTimeString() { - var json = "{\"Property\":\"\\/Date(946730040000-0100)\\/\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "\/Date(946730040000-0100)\/"} + """; var result = DeserializeObject>(json); Assert.Equal(new DateTimeOffset(2000, 1, 1, 12, 34, 0, TimeSpan.FromHours(-1)), result.Value2.First()); } @@ -202,7 +286,10 @@ public void ReadJson_Values_SingleValue_DateTimeNegativeOffsetAsMicrosoftDateTim [Fact] public void ReadJson_Values_SingleValue_DateTimePositiveOffsetAsMicrosoftDateTimeString() { - var json = "{\"Property\":\"\\/Date(946730040000+0100)\\/\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "\/Date(946730040000+0100)\/"} + """; var result = DeserializeObject>(json); Assert.Equal(new DateTimeOffset(2000, 1, 1, 12, 34, 0, TimeSpan.FromHours(1)), result.Value2.First()); } @@ -210,7 +297,10 @@ public void ReadJson_Values_SingleValue_DateTimePositiveOffsetAsMicrosoftDateTim [Fact] public void ReadJson_Values_SingleValue_DateTimeOffsetAsISO8601String() { - var json = "{\"Property\":\"2000-01-01T12:34:00+01:00\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "2000-01-01T12:34:00+01:00"} + """; var result = DeserializeObject>(json); Assert.Equal(new DateTimeOffset(2000, 1, 1, 12, 34, 0, TimeSpan.FromHours(1)), result.Value2.First()); } @@ -218,7 +308,10 @@ public void ReadJson_Values_SingleValue_DateTimeOffsetAsISO8601String() [Fact] public void ReadJson_Values_SingleValue_DateTimeOffsetFallback_DateTimeAsISO8601String_NoOffset() { - var json = "{\"Property\":\"2000-01-01T12:34:00\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "2000-01-01T12:34:00"} + """; var result = DeserializeObject>(json); Assert.Equal(new DateTime(2000, 1, 1, 12, 34, 0), result.Value1.First()); } @@ -226,7 +319,10 @@ public void ReadJson_Values_SingleValue_DateTimeOffsetFallback_DateTimeAsISO8601 [Fact] public void ReadJson_Values_SingleValue_DateTimeOffsetNoFallback_DateTimeAsISO8601String_ZOffset() { - var json = "{\"Property\":\"2000-01-01T12:34:00Z\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "2000-01-01T12:34:00Z"} + """; var result = DeserializeObject>(json); Assert.Equal(new DateTimeOffset(2000, 1, 1, 12, 34, 0, TimeSpan.FromHours(0)), result.Value2.First()); } @@ -234,7 +330,10 @@ public void ReadJson_Values_SingleValue_DateTimeOffsetNoFallback_DateTimeAsISO86 [Fact] public void ReadJson_Values_SingleValue_DateTimeOffsetNoFallback_DateTimeAsISO8601String_TimeOffset() { - var json = "{\"Property\":\"2000-01-01T12:34:00+01:00\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "2000-01-01T12:34:00+01:00"} + """; var result = DeserializeObject>(json); Assert.Equal(new DateTimeOffset(2000, 1, 1, 12, 34, 0, TimeSpan.FromHours(1)), result.Value2.First()); } @@ -242,7 +341,10 @@ public void ReadJson_Values_SingleValue_DateTimeOffsetNoFallback_DateTimeAsISO86 [Fact] public void ReadJson_Values_SingleValue_TimeSpanAsISO8601TimeOfDayString() { - var json = "{\"Property\":\"12:34\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "12:34"} + """; var result = DeserializeObject>(json); Assert.Equal(new TimeSpan(12, 34, 0), result.Value2.First()); } @@ -250,7 +352,10 @@ public void ReadJson_Values_SingleValue_TimeSpanAsISO8601TimeOfDayString() [Fact] public void ReadJson_Values_SingleValue_TimeSpanAsISO8601DurationString() { - var json = "{\"Property\":\"PT12H34M\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "PT12H34M"} + """; var result = DeserializeObject>(json); Assert.Equal(new TimeSpan(12, 34, 0), result.Value2.First()); } @@ -258,7 +363,10 @@ public void ReadJson_Values_SingleValue_TimeSpanAsISO8601DurationString() [Fact] public void ReadJson_ParseValueToken_UriAsString() { - var json = "{\"Property\":\"https://schema.org/Thing\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "https://schema.org/Thing"} + """; var result = DeserializeObject>(json); Assert.Equal(new Uri("https://schema.org/Thing"), result.Value2.First()); } @@ -266,7 +374,10 @@ public void ReadJson_ParseValueToken_UriAsString() [Fact] public void ReadJson_ParseValueToken_RelativeUriAsString() { - var json = "{\"Property\":\"thing\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "thing"} + """; var result = DeserializeObject>(json); Assert.Equal(new Uri("thing", UriKind.Relative), result.Value2.First()); } @@ -274,20 +385,23 @@ public void ReadJson_ParseValueToken_RelativeUriAsString() [Fact] public void ReadJson_Values_SingleValue_ThingInterface() { - var json = "{\"Property\":" + - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"Book\"," + - "\"@id\":\"https://example.com/book/1\"," + - "\"name\":\"The Catcher in the Rye\"," + - "\"url\":\"https://www.barnesandnoble.com/store/info/offer/JDSalinger\"," + - "\"image\":\"book1.jpg\"," + - "\"author\":{" + - "\"@type\":\"Person\"," + - "\"name\":\"J.D. Salinger\"" + - "}" + - "}" + - "}"; + var json = /*lang=json,strict*/ + """ + { + "Property": { + "@context": "https://schema.org", + "@type": "Book", + "@id": "https://example.com/book/1", + "name": "The Catcher in the Rye", + "url": "https://www.barnesandnoble.com/store/info/offer/JDSalinger", + "image": "book1.jpg", + "author": { + "@type": "Person", + "name": "J.D. Salinger" + } + } + } + """; var result = DeserializeObject>(json); var actual = result.Value2.First(); @@ -302,19 +416,22 @@ public void ReadJson_Values_SingleValue_ThingInterface() [Fact] public void ReadJson_Values_SingleValue_ThingActual() { - var json = "{\"Property\":" + - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"Book\"," + - "\"@id\":\"https://example.com/book/1\"," + - "\"name\":\"The Catcher in the Rye\"," + - "\"url\":\"https://www.barnesandnoble.com/store/info/offer/JDSalinger\"," + - "\"author\":{" + - "\"@type\":\"Person\"," + - "\"name\":\"J.D. Salinger\"" + - "}" + - "}" + - "}"; + var json = /*lang=json,strict*/ + """ + { + "Property": { + "@context": "https://schema.org", + "@type": "Book", + "@id": "https://example.com/book/1", + "name": "The Catcher in the Rye", + "url": "https://www.barnesandnoble.com/store/info/offer/JDSalinger", + "author": { + "@type": "Person", + "name": "J.D. Salinger" + } + } + } + """; var result = DeserializeObject>(json); var actual = result.Value2.First(); @@ -328,18 +445,21 @@ public void ReadJson_Values_SingleValue_ThingActual() [Fact] public void ReadJson_Values_SingleValue_ThingInterfaceWithNoTypeToken() { - var json = "{\"Property\":" + - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@id\":\"https://example.com/book/1\"," + - "\"name\":\"The Catcher in the Rye\"," + - "\"url\":\"https://www.barnesandnoble.com/store/info/offer/JDSalinger\"," + - "\"author\":{" + - "\"@type\":\"Person\"," + - "\"name\":\"J.D. Salinger\"" + - "}" + - "}" + - "}"; + var json = /*lang=json,strict*/ + """ + { + "Property": { + "@context": "https://schema.org", + "@id": "https://example.com/book/1", + "name": "The Catcher in the Rye", + "url": "https://www.barnesandnoble.com/store/info/offer/JDSalinger", + "author": { + "@type": "Person", + "name": "J.D. Salinger" + } + } + } + """; var result = DeserializeObject>(json); var actual = result.Value2.First(); @@ -353,18 +473,21 @@ public void ReadJson_Values_SingleValue_ThingInterfaceWithNoTypeToken() [Fact] public void ReadJson_Values_SingleValue_ThingActualWithNoTypeToken() { - var json = "{\"Property\":" + - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@id\":\"https://example.com/book/1\"," + - "\"name\":\"The Catcher in the Rye\"," + - "\"url\":\"https://www.barnesandnoble.com/store/info/offer/JDSalinger\"," + - "\"author\":{" + - "\"@type\":\"Person\"," + - "\"name\":\"J.D. Salinger\"" + - "}" + - "}" + - "}"; + var json = /*lang=json,strict*/ + """ + { + "Property": { + "@context": "https://schema.org", + "@id": "https://example.com/book/1", + "name": "The Catcher in the Rye", + "url": "https://www.barnesandnoble.com/store/info/offer/JDSalinger", + "author": { + "@type": "Person", + "name":"J.D. Salinger" + } + } + } + """; var result = DeserializeObject>(json); var actual = result.Value2.First(); @@ -378,7 +501,10 @@ public void ReadJson_Values_SingleValue_ThingActualWithNoTypeToken() [Fact] public void ReadJson_Values_SingleValue_Enum_NoUrl() { - var json = "{\"Property\":\"InStock\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "InStock"} + """; var result = DeserializeObject>(json); Assert.Equal(ItemAvailability.InStock, result.Value2.First()); } @@ -386,7 +512,10 @@ public void ReadJson_Values_SingleValue_Enum_NoUrl() [Fact] public void ReadJson_Values_SingleValue_Enum_HttpSchema() { - var json = "{\"Property\":\"https://schema.org/InStock\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "https://schema.org/InStock"} + """; var result = DeserializeObject>(json); Assert.Equal(ItemAvailability.InStock, result.Value2.First()); } @@ -394,7 +523,10 @@ public void ReadJson_Values_SingleValue_Enum_HttpSchema() [Fact] public void ReadJson_Values_SingleValue_Enum_HttpsSchema() { - var json = "{\"Property\":\"https://schema.org/InStock\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "https://schema.org/InStock"} + """; var result = DeserializeObject>(json); Assert.Equal(ItemAvailability.InStock, result.Value2.First()); } @@ -402,7 +534,10 @@ public void ReadJson_Values_SingleValue_Enum_HttpsSchema() [Fact] public void ReadJson_Values_MultiValue_SameType() { - var json = "{\"Property\":[\"A\",\"B\"]}"; + var json = /*lang=json,strict*/ + """ + {"Property": ["A", "B"]} + """; var result = DeserializeObject>(json); Assert.Equal(new[] { "A", "B" }, result.Value2); } @@ -410,7 +545,10 @@ public void ReadJson_Values_MultiValue_SameType() [Fact] public void ReadJson_Values_MultiValue_SameType_ArgumentsSwapped() { - var json = "{\"Property\":[\"A\",\"B\"]}"; + var json = /*lang=json,strict*/ + """ + {"Property": ["A", "B"]} + """; var result = DeserializeObject>(json); Assert.Equal(new[] { "A", "B" }, result.Value1); } @@ -418,7 +556,10 @@ public void ReadJson_Values_MultiValue_SameType_ArgumentsSwapped() [Fact] public void ReadJson_Values_MultiValue_MixedType() { - var json = "{\"Property\":[1,\"B\"]}"; + var json = /*lang=json,strict*/ + """ + {"Property": [1, "B"]} + """; var result = DeserializeObject>(json); Assert.Equal(new[] { 1 }, result.Value1); Assert.Equal(new[] { "B" }, result.Value2); @@ -427,7 +568,10 @@ public void ReadJson_Values_MultiValue_MixedType() [Fact] public void ReadJson_Values_MultiValue_NullablePrimitiveAsString() { - var json = "{\"Property\":[\"123\",\"456\"]}"; + var json = /*lang=json,strict*/ + """ + {"Property": ["123", "456"]} + """; var result = DeserializeObject>(json); Assert.Equal(new int?[] { 123, 456 }, result.Value2); } @@ -435,30 +579,35 @@ public void ReadJson_Values_MultiValue_NullablePrimitiveAsString() [Fact] public void ReadJson_Values_MultiValue_ThingInterface() { - var json = "{\"Property\":[" + - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"Book\"," + - "\"@id\":\"https://example.com/book/1\"," + - "\"name\":\"The Catcher in the Rye\"," + - "\"url\":\"https://www.barnesandnoble.com/store/info/offer/JDSalinger\"," + - "\"author\":{" + - "\"@type\":\"Person\"," + - "\"name\":\"J.D. Salinger\"" + - "}" + - "}," + - "{" + - "\"@context\":\"https://schema.org\"," + - "\"@type\":\"Book\"," + - "\"@id\":\"https://example.com/book/2\"," + - "\"name\":\"The Lord of the Rings\"," + - "\"url\":\"https://www.barnesandnoble.com/store/info/offer/JRRTolkien\"," + - "\"author\":{" + - "\"@type\":\"Person\"," + - "\"name\":\"J.R.R. Tolkien\"" + - "}" + - "}" + - "]}"; + var json = /*lang=json,strict*/ + """ + { + "Property": [ + { + "@context": "https://schema.org", + "@type": "Book", + "@id": "https://example.com/book/1", + "name": "The Catcher in the Rye", + "url": "https://www.barnesandnoble.com/store/info/offer/JDSalinger", + "author": { + "@type": "Person", + "name": "J.D. Salinger" + } + }, + { + "@context": "https://schema.org", + "@type": "Book", + "@id": "https://example.com/book/2", + "name": "The Lord of the Rings", + "url": "https://www.barnesandnoble.com/store/info/offer/JRRTolkien", + "author": { + "@type": "Person", + "name": "J.R.R. Tolkien" + } + } + ] + } + """; var result = DeserializeObject>(json); var actual = result.Value2.ToArray(); @@ -478,7 +627,10 @@ public void ReadJson_Values_MultiValue_ThingInterface() [Fact] public void ReadJson_OneOrMany_SingleValue_String() { - var json = "{\"Property\":\"Test String\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "Test String"} + """; var result = DeserializeObject>(json); Assert.Equal("Test String", result.First()); } @@ -486,7 +638,10 @@ public void ReadJson_OneOrMany_SingleValue_String() [Fact] public void ReadJson_OneOrMany_MultiValue_String() { - var json = "{\"Property\":[\"A\",\"B\"]}"; + var json = /*lang=json,strict*/ + """ + {"Property": ["A", "B"]} + """; var result = DeserializeObject>(json); Assert.Equal(new[] { "A", "B" }, result); } @@ -494,7 +649,10 @@ public void ReadJson_OneOrMany_MultiValue_String() [Fact] public void ReadJson_OneOrMany_SingleValue_NullablePrimitiveAsString() { - var json = "{\"Property\":\"123\"}"; + var json = /*lang=json,strict*/ + """ + {"Property": "123"} + """; var result = DeserializeObject>(json); Assert.Equal(123, result.First()); } @@ -502,7 +660,10 @@ public void ReadJson_OneOrMany_SingleValue_NullablePrimitiveAsString() [Fact] public void ReadJson_OneOrMany_MultiValue_NullablePrimitiveAsString() { - var json = "{\"Property\":[\"123\",\"456\"]}"; + var json = /*lang=json,strict*/ + """ + {"Property": ["123", "456"]} + """; var result = DeserializeObject>(json); Assert.Equal(new int?[] { 123, 456 }, result); } @@ -510,13 +671,18 @@ public void ReadJson_OneOrMany_MultiValue_NullablePrimitiveAsString() [Fact] public void ReadJson_ExplicitExternalTypes_AllowCustomNamespace() { - var json = "{\"Property\":[" + - "{" + - "\"@type\":\"ExternalSchemaModelCustomNamespace, Schema.NET.Test\"," + - "\"name\":\"Property from Thing\"," + - "\"myCustomProperty\":\"My Test String\"" + - "}" + - "]}"; + var json = /*lang=json,strict*/ + """ + { + "Property": [ + { + "@type": "ExternalSchemaModelCustomNamespace, Schema.NET.Test", + "name": "Property from Thing", + "myCustomProperty": "My Test String" + } + ] + } + """; var result = DeserializeObject>(json); var actual = Assert.Single(result.Value2); Assert.Equal(new[] { "Property from Thing" }, actual.Name); @@ -526,13 +692,18 @@ public void ReadJson_ExplicitExternalTypes_AllowCustomNamespace() [Fact] public void ReadJson_ExplicitExternalTypes_AllowSharedNamespace() { - var json = "{\"Property\":[" + - "{" + - "\"@type\":\"ExternalSchemaModelSharedNamespace, Schema.NET.Test\"," + - "\"name\":\"Property from Thing\"," + - "\"myCustomProperty\":\"My Test String\"" + - "}" + - "]}"; + var json = /*lang=json,strict*/ + """ + { + "Property": [ + { + "@type": "ExternalSchemaModelSharedNamespace, Schema.NET.Test", + "name": "Property from Thing", + "myCustomProperty": "My Test String" + } + ] + } + """; var result = DeserializeObject>(json); var actual = Assert.Single(result.Value2); Assert.Equal(new[] { "Property from Thing" }, actual.Name); @@ -542,13 +713,18 @@ public void ReadJson_ExplicitExternalTypes_AllowSharedNamespace() [Fact] public void ReadJson_ImplicitExternalTypes_AllowCustomNamespace() { - var json = "{\"Property\":[" + - "{" + - "\"@type\":\"SomeCustomNamespace.ExternalSchemaModelCustomNamespace, Schema.NET.Test\"," + - "\"name\":\"Property from Thing\"," + - "\"myCustomProperty\":\"My Test String\"" + - "}" + - "]}"; + var json = /*lang=json,strict*/ + """ + { + "Property": [ + { + "@type": "SomeCustomNamespace.ExternalSchemaModelCustomNamespace, Schema.NET.Test", + "name": "Property from Thing", + "myCustomProperty": "My Test String" + } + ] + } + """; var result = DeserializeObject>(json); var actual = Assert.Single(result.Value2); Assert.IsType(actual); @@ -559,13 +735,18 @@ public void ReadJson_ImplicitExternalTypes_AllowCustomNamespace() [Fact] public void ReadJson_ImplicitExternalTypes_AllowSharedNamespace() { - var json = "{\"Property\":[" + - "{" + - "\"@type\":\"Schema.NET.ExternalSchemaModelSharedNamespace, Schema.NET.Test\"," + - "\"name\":\"Property from Thing\"," + - "\"myCustomProperty\":\"My Test String\"" + - "}" + - "]}"; + var json = /*lang=json,strict*/ + """ + { + "Property": [ + { + "@type": "Schema.NET.ExternalSchemaModelSharedNamespace, Schema.NET.Test", + "name": "Property from Thing", + "myCustomProperty": "My Test String" + } + ] + } + """; var result = DeserializeObject>(json); var actual = Assert.Single(result.Value2); Assert.IsType(actual); @@ -581,7 +762,7 @@ private static T DeserializeObject(string json) where T : struct, IValues => SchemaSerializer.DeserializeObject>(json)!.Property; - private class TestModel + private sealed class TestModel where T : struct, IValues { [JsonConverter(typeof(ValuesJsonConverter))] diff --git a/Tools/Schema.NET.Tool/CollectionExtensions.cs b/Tools/Schema.NET.Tool/CollectionExtensions.cs index 80fca84e..bc41dceb 100644 --- a/Tools/Schema.NET.Tool/CollectionExtensions.cs +++ b/Tools/Schema.NET.Tool/CollectionExtensions.cs @@ -8,8 +8,8 @@ public static class CollectionExtensions public static void AddRange(this ICollection collection, IEnumerable items) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(collection); - ArgumentNullException.ThrowIfNull(items); + ArgumentNullException.ThrowIfNull(collection); + ArgumentNullException.ThrowIfNull(items); #else if (collection is null) { diff --git a/Tools/Schema.NET.Tool/CustomOverrides/AddNumberTypeToMediaObjectHeightAndWidth.cs b/Tools/Schema.NET.Tool/CustomOverrides/AddNumberTypeToMediaObjectHeightAndWidth.cs index 58c98e61..a5fca7e9 100644 --- a/Tools/Schema.NET.Tool/CustomOverrides/AddNumberTypeToMediaObjectHeightAndWidth.cs +++ b/Tools/Schema.NET.Tool/CustomOverrides/AddNumberTypeToMediaObjectHeightAndWidth.cs @@ -10,7 +10,7 @@ public class AddNumberTypeToMediaObjectHeightAndWidth : IClassOverride public bool CanOverride(GeneratorSchemaClass c) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(items); + ArgumentNullException.ThrowIfNull(items); #else if (c is null) { @@ -25,7 +25,7 @@ public bool CanOverride(GeneratorSchemaClass c) public void Override(GeneratorSchemaClass c) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(c); + ArgumentNullException.ThrowIfNull(c); #else if (c is null) { diff --git a/Tools/Schema.NET.Tool/CustomOverrides/AddQueryInputPropertyToSearchAction.cs b/Tools/Schema.NET.Tool/CustomOverrides/AddQueryInputPropertyToSearchAction.cs index cc6b9e06..90c2a11e 100644 --- a/Tools/Schema.NET.Tool/CustomOverrides/AddQueryInputPropertyToSearchAction.cs +++ b/Tools/Schema.NET.Tool/CustomOverrides/AddQueryInputPropertyToSearchAction.cs @@ -9,7 +9,7 @@ public class AddQueryInputPropertyToSearchAction : IClassOverride public bool CanOverride(GeneratorSchemaClass c) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(c); + ArgumentNullException.ThrowIfNull(c); #else if (c is null) { @@ -23,7 +23,7 @@ public bool CanOverride(GeneratorSchemaClass c) public void Override(GeneratorSchemaClass c) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(c); + ArgumentNullException.ThrowIfNull(c); #else if (c is null) { @@ -35,8 +35,8 @@ public void Override(GeneratorSchemaClass c) property.Types.AddRange( new List() { - new GeneratorSchemaPropertyType("Text", "string"), - new GeneratorSchemaPropertyType("PropertyValueSpecification", "PropertyValueSpecification"), + new GeneratorSchemaPropertyType("Text", "string"), + new GeneratorSchemaPropertyType("PropertyValueSpecification", "PropertyValueSpecification"), }); c.Properties.Add(property); } diff --git a/Tools/Schema.NET.Tool/CustomOverrides/AddTextTypeToActionTarget.cs b/Tools/Schema.NET.Tool/CustomOverrides/AddTextTypeToActionTarget.cs index 7bdff1c4..55d3e7bf 100644 --- a/Tools/Schema.NET.Tool/CustomOverrides/AddTextTypeToActionTarget.cs +++ b/Tools/Schema.NET.Tool/CustomOverrides/AddTextTypeToActionTarget.cs @@ -10,7 +10,7 @@ public class AddTextTypeToActionTarget : IClassOverride public bool CanOverride(GeneratorSchemaClass c) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(c); + ArgumentNullException.ThrowIfNull(c); #else if (c is null) { @@ -24,7 +24,7 @@ public bool CanOverride(GeneratorSchemaClass c) public void Override(GeneratorSchemaClass c) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(c); + ArgumentNullException.ThrowIfNull(c); #else if (c is null) { diff --git a/Tools/Schema.NET.Tool/CustomOverrides/RenameEventProperty.cs b/Tools/Schema.NET.Tool/CustomOverrides/RenameEventProperty.cs index 18aafeab..825410be 100644 --- a/Tools/Schema.NET.Tool/CustomOverrides/RenameEventProperty.cs +++ b/Tools/Schema.NET.Tool/CustomOverrides/RenameEventProperty.cs @@ -9,7 +9,7 @@ public class RenameEventProperty : IClassOverride public bool CanOverride(GeneratorSchemaClass c) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(c); + ArgumentNullException.ThrowIfNull(c); #else if (c is null) { @@ -23,7 +23,7 @@ public bool CanOverride(GeneratorSchemaClass c) public void Override(GeneratorSchemaClass c) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(c); + ArgumentNullException.ThrowIfNull(c); #else if (c is null) { diff --git a/Tools/Schema.NET.Tool/EnumerableExtensions.cs b/Tools/Schema.NET.Tool/EnumerableExtensions.cs index d7cad145..b11aa27e 100644 --- a/Tools/Schema.NET.Tool/EnumerableExtensions.cs +++ b/Tools/Schema.NET.Tool/EnumerableExtensions.cs @@ -9,7 +9,7 @@ public static class EnumerableExtensions public static IEnumerable Traverse(T node, Func parent) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(parent); + ArgumentNullException.ThrowIfNull(parent); #else if (parent is null) { @@ -26,7 +26,7 @@ public static IEnumerable Traverse(T node, Func parent) public static IEnumerable Traverse(T node, Func> children) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(children); + ArgumentNullException.ThrowIfNull(children); #else if (children is null) { diff --git a/Tools/Schema.NET.Tool/GeneratorModels/GeneratorSchemaClass.cs b/Tools/Schema.NET.Tool/GeneratorModels/GeneratorSchemaClass.cs index b1eb578d..6d9b0583 100644 --- a/Tools/Schema.NET.Tool/GeneratorModels/GeneratorSchemaClass.cs +++ b/Tools/Schema.NET.Tool/GeneratorModels/GeneratorSchemaClass.cs @@ -41,7 +41,7 @@ public GeneratorSchemaClass(string layer, Uri id, string name, string descriptio public bool IsCombined { get; } - public IEnumerable DeclaredProperties + public ICollection DeclaredProperties { get { @@ -50,7 +50,7 @@ public IEnumerable DeclaredProperties var declaredProperties = this.Properties.Where(classProp => !ancestorProps.Contains(classProp.Name)) .OrderBy(x => x.Order); - return declaredProperties; + return declaredProperties.ToList(); } } diff --git a/Tools/Schema.NET.Tool/GeneratorModels/GeneratorSchemaProperty.cs b/Tools/Schema.NET.Tool/GeneratorModels/GeneratorSchemaProperty.cs index d4884e5b..2bf5956d 100644 --- a/Tools/Schema.NET.Tool/GeneratorModels/GeneratorSchemaProperty.cs +++ b/Tools/Schema.NET.Tool/GeneratorModels/GeneratorSchemaProperty.cs @@ -28,7 +28,7 @@ public GeneratorSchemaProperty(GeneratorSchemaClass @class, string jsonName, str public ICollection Types { get; } = new List(); - public IEnumerable CSharpTypes => this.Types.SelectMany(x => x.CSharpTypeStrings); + public ICollection CSharpTypes => this.Types.SelectMany(x => x.CSharpTypeStrings).ToList(); public string PropertyTypeString { diff --git a/Tools/Schema.NET.Tool/Repositories/SchemaPropertyJsonConverter.cs b/Tools/Schema.NET.Tool/Repositories/SchemaPropertyJsonConverter.cs index bd7347fa..6c956e7c 100644 --- a/Tools/Schema.NET.Tool/Repositories/SchemaPropertyJsonConverter.cs +++ b/Tools/Schema.NET.Tool/Repositories/SchemaPropertyJsonConverter.cs @@ -13,7 +13,7 @@ public class SchemaPropertyJsonConverter : JsonConverter> public override List Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(typeToConvert); + ArgumentNullException.ThrowIfNull(typeToConvert); #else if (typeToConvert is null) { @@ -74,7 +74,7 @@ public override void Write(Utf8JsonWriter writer, List value, Json #if NETSTANDARD2_0 layer = isPartOf.Host.Replace(".schema.org", string.Empty); #else - layer = isPartOf.Host.Replace(".schema.org", string.Empty, StringComparison.Ordinal); + layer = isPartOf.Host.Replace(".schema.org", string.Empty, StringComparison.Ordinal); #endif } diff --git a/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs b/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs index 92d7672c..d7175b0e 100644 --- a/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs +++ b/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs @@ -1,7 +1,6 @@ namespace Schema.NET.Tool; using System; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using Microsoft.CodeAnalysis; @@ -72,10 +71,6 @@ private static bool IncludePendingSchemaObjects(GeneratorExecutionContext contex value.Equals("true", StringComparison.OrdinalIgnoreCase); } - [SuppressMessage( - "StyleCop.CSharp.LayoutRules", - "SA1513:Closing brace should be followed by blank line", - Justification = "Interpolated string")] private static string RenderClass(GeneratorSchemaClass schemaClass) { if (schemaClass.Parents.Count > 1) @@ -85,6 +80,57 @@ private static string RenderClass(GeneratorSchemaClass schemaClass) var parentType = schemaClass.Parents.FirstOrDefault(); + var classModifiers = schemaClass.IsCombined ? " abstract" : string.Empty; + var classImplements = parentType is not null ? $" {parentType.Name}," : string.Empty; + var allProperties = schemaClass.Properties.OrderBy(x => x.Order).ToArray(); + + return + $$""" + #nullable enable + namespace Schema.NET; + + using System; + using System.Collections.Generic; + using System.Text.Json; + using System.Text.Json.Serialization; + + {{RenderInterface(schemaClass, parentType)}} + + /// + /// {{SourceUtility.RenderDoc(4, schemaClass.Description)}} + /// + public{{classModifiers}} partial class {{schemaClass.Name}} :{{classImplements}} I{{schemaClass.Name}}, IEquatable<{{schemaClass.Name}}> + { + /// + /// Gets the name of the type as specified by schema.org. + /// + [JsonPropertyName("@type")] + [JsonPropertyOrder(1)] + public override string Type => "{{schemaClass.Name}}"; + + {{SourceUtility.RenderItems(allProperties, RenderClassProperty, 4, SourceDelimeter.NewLineSpace)}} + + {{RenderClassTrySetValue(allProperties, 4)}} + + {{RenderClassTryGetVaue1(allProperties, 4)}} + + {{RenderClassTryGetVaue2(allProperties, 4)}} + + {{RenderClassEquals(schemaClass, allProperties, 4)}} + + /// + public override bool Equals(object? obj) => this.Equals(obj as {{schemaClass.Name}}); + + /// + public override int GetHashCode() => HashCode.Of(this.Type){{SourceUtility.RenderItems(allProperties, (index, indent, property) => $@" + .And(this.{property.Name})")}} + .And(base.GetHashCode()); + } + """; + } + + private static string RenderInterface(GeneratorSchemaClass schemaClass, GeneratorSchemaClass? parentType) + { var interfaceImplements = string.Empty; if (schemaClass.IsCombined) { @@ -95,174 +141,271 @@ private static string RenderClass(GeneratorSchemaClass schemaClass) interfaceImplements = $" : I{parentType.Name}"; } - var classModifiers = schemaClass.IsCombined ? " abstract" : string.Empty; - var classImplements = parentType is not null ? $" {parentType.Name}," : string.Empty; - var allProperties = schemaClass.Properties.OrderBy(x => x.Order).ToArray(); - return -$@"namespace Schema.NET; + $$""" + /// + /// {{SourceUtility.RenderDoc(4, schemaClass.Description)}} + /// + public partial interface I{{schemaClass.Name}}{{interfaceImplements}} + { + {{SourceUtility.RenderItems(!schemaClass.IsCombined, schemaClass.DeclaredProperties, RenderInterfaceProperty, 4, SourceDelimeter.NewLineSpace)}} + } + """; + } -using System; -using System.Collections.Generic; -using System.Text.Json; -using System.Text.Json.Serialization; - -/// -/// {SourceUtility.RenderDoc(4, schemaClass.Description)} -/// -public partial interface I{schemaClass.Name}{interfaceImplements} -{{{SourceUtility.RenderItems(!schemaClass.IsCombined, schemaClass.DeclaredProperties, property => $@" - /// - /// {SourceUtility.RenderDoc(8, property.Description)} - /// - {property.PropertyTypeString} {property.Name} {{ get; set; }}")} -}} - -/// -/// {SourceUtility.RenderDoc(4, schemaClass.Description)} -/// -public{classModifiers} partial class {schemaClass.Name} :{classImplements} I{schemaClass.Name}, IEquatable<{schemaClass.Name}> -{{ - /// - /// Gets the name of the type as specified by schema.org. - /// - [JsonPropertyName(""@type"")] - [JsonPropertyOrder(1)] - public override string Type => ""{schemaClass.Name}"";{SourceUtility.RenderItems(allProperties, property => $@" - - /// - /// {SourceUtility.RenderDoc(8, property.Description)} - /// - [JsonPropertyName(""{property.JsonName}"")] - [JsonPropertyOrder({property.Order})] - [JsonConverter(typeof({property.JsonConverterType}))] - public{GetAccessModifier(property)} {property.PropertyTypeString} {property.Name} {{ get; set; }}")} - - /// - public override bool TrySetValue(string property, IEnumerable value) - {{ - if (string.IsNullOrWhiteSpace(property)) - {{ - return false; - }} - - var success = false; - {SourceUtility.RenderItems(allProperties, property => $@"if (""{property.Name}"".Equals(property, StringComparison.OrdinalIgnoreCase)) - {{ - this.{property.Name} = new(value); - success = true; - }} - else ")} - {{ - success = base.TrySetValue(property, value); - }} - - return success; - }} - - /// - public override bool TryGetValue(string property, out OneOrMany result) - {{ - if (string.IsNullOrWhiteSpace(property)) - {{ - result = default; - return false; - }} - - var success = false; - {SourceUtility.RenderItems(allProperties, property => $@"if (""{property.Name}"".Equals(property, StringComparison.OrdinalIgnoreCase)) - {{ - {SourceUtility.RenderItems(property.CSharpTypes, (propertyType, index) => $@"if (typeof({propertyType}) == typeof(TValue)) - {{ - result = (OneOrMany)(IValues)this.{property.Name}{(property.CSharpTypes.Count() > 1 ? $".Value{index + 1}" : string.Empty)}; + private static string RenderInterfaceProperty(int index, int indent, GeneratorSchemaProperty property) => + $$""" + /// + /// {{SourceUtility.RenderDoc(indent + 4, property.Description)}} + /// + {{property.PropertyTypeString}} {{property.Name}} { get; set; } + """; + + private static string RenderClassProperty(int index, int indent, GeneratorSchemaProperty property) => + $$""" + /// + /// {{SourceUtility.RenderDoc(indent + 4, property.Description)}} + /// + [JsonPropertyName("{{property.JsonName}}")] + [JsonPropertyOrder({{property.Order}})] + [JsonConverter(typeof({{property.JsonConverterType}}))] + public{{GetAccessModifier(property)}} {{property.PropertyTypeString}} {{property.Name}} { get; set; } + """; + + private static string RenderClassTrySetValue(GeneratorSchemaProperty[] allProperties, int indent) + { + if (allProperties.Length == 0) + { + return SourceUtility.Render( + """ + /// + public override bool TrySetValue(string property, IEnumerable value) + { + if (string.IsNullOrWhiteSpace(property)) + { + return false; + } + + return base.TrySetValue(property, value); + } + """, + indent); + } + + var conditions = SourceUtility.RenderItems( + allProperties, + (index, indent, property) => + $$""" + {{(index == 0 ? "if" : "else if")}} ("{{property.Name}}".Equals(property, StringComparison.OrdinalIgnoreCase)) + { + this.{{property.Name}} = new(value); success = true; - }} - else ")} - {{ - result = default; - }} - }} - else ")} - {{ - success = base.TryGetValue(property, out result); - }} - - return success; - }} - - /// - public override bool TryGetValue(string property, out IValues result) - {{ - if (string.IsNullOrWhiteSpace(property)) - {{ - result = default; - return false; - }} - - var success = false; - {SourceUtility.RenderItems(allProperties, property => $@"if (""{property.Name}"".Equals(property, StringComparison.OrdinalIgnoreCase)) - {{ - result = (IValues)this.{property.Name}; - success = true; - }} - else ")} - {{ - success = base.TryGetValue(property, out result); - }} - - return success; - }} - - /// - public bool Equals({schemaClass.Name} other) - {{ - if (other is null) - {{ - return false; - }} - - if (ReferenceEquals(this, other)) - {{ - return true; - }} - - return this.Type == other.Type{SourceUtility.RenderItems(allProperties, property => $@" && - this.{property.Name} == other.{property.Name}")} && - base.Equals(other); - }} - - /// - public override bool Equals(object obj) => this.Equals(obj as {schemaClass.Name}); - - /// - public override int GetHashCode() => HashCode.Of(this.Type){SourceUtility.RenderItems(allProperties, property => $@" - .And(this.{property.Name})")} - .And(base.GetHashCode()); -}}"; + } + """, + indent, + SourceDelimeter.NewLine); + + return SourceUtility.Render( + $$""" + /// + public override bool TrySetValue(string property, IEnumerable value) + { + if (string.IsNullOrWhiteSpace(property)) + { + return false; + } + + bool success; + {{conditions}} + else + { + success = base.TrySetValue(property, value); + } + + return success; + } + """, + indent); } - [SuppressMessage( - "StyleCop.CSharp.LayoutRules", - "SA1513:Closing brace should be followed by blank line", - Justification = "Interpolated string")] + private static string RenderClassTryGetVaue1(GeneratorSchemaProperty[] allProperties, int indent) + { + if (allProperties.Length == 0) + { + return SourceUtility.Render( + """ + /// + public override bool TryGetValue(string property, out OneOrMany result) + { + if (string.IsNullOrWhiteSpace(property)) + { + result = default; + return false; + } + + return base.TryGetValue(property, out result); + } + """, + indent); + } + + var conditions = SourceUtility.RenderItems( + allProperties, + (index, indent, property) => + $$""" + {{(index == 0 ? "if" : "else if")}} ("{{property.Name}}".Equals(property, StringComparison.OrdinalIgnoreCase)) + { + {{SourceUtility.RenderItems( + property.CSharpTypes, + (index, indent, propertyType) => + $$""" + {{(index == 0 ? "if" : "else if")}} (typeof({{propertyType}}) == typeof(TValue)) + { + result = (OneOrMany)(IValues)this.{{property.Name}}{{(property.CSharpTypes.Count > 1 ? $".Value{index + 1}" : string.Empty)}}; + success = true; + } + """, + indent)}} + else + { + result = default; + } + } + """, + indent, + SourceDelimeter.NewLine); + + return SourceUtility.Render( + $$""" + /// + public override bool TryGetValue(string property, out OneOrMany result) + { + if (string.IsNullOrWhiteSpace(property)) + { + result = default; + return false; + } + + var success = false; + {{conditions}} + else + { + success = base.TryGetValue(property, out result); + } + + return success; + } + """, + indent); + } + + private static string RenderClassTryGetVaue2(GeneratorSchemaProperty[] allProperties, int indent) + { + if (allProperties.Length == 0) + { + return SourceUtility.Render( + """ + /// + public override bool TryGetValue(string property, out IValues? result) + { + if (string.IsNullOrWhiteSpace(property)) + { + result = default; + return false; + } + + return base.TryGetValue(property, out result); + } + """, + indent); + } + + var conditions = SourceUtility.RenderItems( + allProperties, + (index, indent, property) => + $$""" + {{(index == 0 ? "if" : "else if")}} ("{{property.Name}}".Equals(property, StringComparison.OrdinalIgnoreCase)) + { + result = this.{{property.Name}}; + success = true; + } + """, + indent, + SourceDelimeter.NewLine); + + return SourceUtility.Render( + $$""" + /// + public override bool TryGetValue(string property, out IValues? result) + { + if (string.IsNullOrWhiteSpace(property)) + { + result = default; + return false; + } + + bool success; + {{conditions}} + else + { + success = base.TryGetValue(property, out result); + } + + return success; + } + """, + indent); + } + + private static string RenderClassEquals(GeneratorSchemaClass schemaClass, GeneratorSchemaProperty[] allProperties, int indent) => + SourceUtility.Render( + $$""" + /// + public bool Equals({{schemaClass.Name}}? other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.Type == other.Type{{SourceUtility.RenderItems(allProperties, (index, indent, property) => $@" && + this.{property.Name} == other.{property.Name}")}} && + base.Equals(other); + } + """, + indent); + private static string RenderEnumeration(GeneratorSchemaEnumeration schemaEnumeration) => -$@"namespace Schema.NET; - -using System.Runtime.Serialization; -using System.Text.Json.Serialization; - -/// -/// {SourceUtility.RenderDoc(4, schemaEnumeration.Description)} -/// -[JsonConverter(typeof(SchemaEnumJsonConverter<{schemaEnumeration.Name}>))] -public enum {schemaEnumeration.Name} -{{{SourceUtility.RenderItems(schemaEnumeration.Values, value => $@" - /// - /// {SourceUtility.RenderDoc(8, value.Description)} - /// - [EnumMember(Value = ""{value.Uri}"")] - {value.Name},")} -}}"; + $$""" + #nullable enable + namespace Schema.NET; + + using System.Runtime.Serialization; + using System.Text.Json.Serialization; + + /// + /// {{SourceUtility.RenderDoc(4, schemaEnumeration.Description)}} + /// + [JsonConverter(typeof(SchemaEnumJsonConverter<{{schemaEnumeration.Name}}>))] + public enum {{schemaEnumeration.Name}} + { + {{SourceUtility.RenderItems(schemaEnumeration.Values, RenderEnumerationValue, 4, SourceDelimeter.NewLineSpace)}} + } + """; + + private static string RenderEnumerationValue(int index, int indent, GeneratorSchemaEnumerationValue value) => + $""" + /// + /// {SourceUtility.RenderDoc(indent + 4, value.Description)} + /// + [EnumMember(Value = "{value.Uri}")] + {value.Name}, + """; private static string GetAccessModifier(GeneratorSchemaProperty schemaProperty) { diff --git a/Tools/Schema.NET.Tool/Services/EnumerationValueComparer.cs b/Tools/Schema.NET.Tool/Services/EnumerationValueComparer.cs index a132204f..6e82b689 100644 --- a/Tools/Schema.NET.Tool/Services/EnumerationValueComparer.cs +++ b/Tools/Schema.NET.Tool/Services/EnumerationValueComparer.cs @@ -19,8 +19,8 @@ public class EnumerationValueComparer : IComparer public int Compare(string? x, string? y) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(x); - ArgumentNullException.ThrowIfNull(y); + ArgumentNullException.ThrowIfNull(x); + ArgumentNullException.ThrowIfNull(y); #else if (x is null) { diff --git a/Tools/Schema.NET.Tool/Services/PropertyNameComparer.cs b/Tools/Schema.NET.Tool/Services/PropertyNameComparer.cs index 4620cd04..0bf0d060 100644 --- a/Tools/Schema.NET.Tool/Services/PropertyNameComparer.cs +++ b/Tools/Schema.NET.Tool/Services/PropertyNameComparer.cs @@ -18,8 +18,8 @@ public class PropertyNameComparer : IComparer public int Compare(string? x, string? y) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(x); - ArgumentNullException.ThrowIfNull(y); + ArgumentNullException.ThrowIfNull(x); + ArgumentNullException.ThrowIfNull(y); #else if (x is null) { diff --git a/Tools/Schema.NET.Tool/Services/SchemaService.cs b/Tools/Schema.NET.Tool/Services/SchemaService.cs index 3cb5891d..b7c08ef0 100644 --- a/Tools/Schema.NET.Tool/Services/SchemaService.cs +++ b/Tools/Schema.NET.Tool/Services/SchemaService.cs @@ -224,7 +224,7 @@ private static GeneratorSchemaClass TranslateClass( var @class = new GeneratorSchemaClass(schemaClass.Layer, schemaClass.Id, className, schemaClass.Comment); @class.Parents.AddRange(schemaClass.SubClassOfIds - .Where(id => knownSchemaClasses.Contains(id)) + .Where(knownSchemaClasses.Contains) .Select(id => new GeneratorSchemaClass(id))); var properties = schemaProperties diff --git a/Tools/Schema.NET.Tool/SourceDelimeter.cs b/Tools/Schema.NET.Tool/SourceDelimeter.cs new file mode 100644 index 00000000..1a14f692 --- /dev/null +++ b/Tools/Schema.NET.Tool/SourceDelimeter.cs @@ -0,0 +1,8 @@ +namespace Schema.NET.Tool; + +public enum SourceDelimeter +{ + None, + NewLine, + NewLineSpace, +} diff --git a/Tools/Schema.NET.Tool/SourceUtility.cs b/Tools/Schema.NET.Tool/SourceUtility.cs index d6a9ef91..5926cb65 100644 --- a/Tools/Schema.NET.Tool/SourceUtility.cs +++ b/Tools/Schema.NET.Tool/SourceUtility.cs @@ -2,6 +2,7 @@ namespace Schema.NET.Tool; using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Xml; @@ -11,17 +12,23 @@ public static class SourceUtility private const char Space = ' '; private static readonly Regex NewLineReplace = new("[\n ]{0,}\n[\n ]{0,}", RegexOptions.Compiled); - public static string RenderItems(bool canRender, IEnumerable items, Func action) + public static string Render(string value, int indent = 0) + { + var indentString = new string(Space, indent); + return indentString + value.Replace(Environment.NewLine, Environment.NewLine + indentString); + } + + public static string RenderItems(bool canRender, ICollection items, Func action, int indent = 0, SourceDelimeter sourceDelimeter = SourceDelimeter.None) { if (canRender) { - return RenderItems(items, action); + return RenderItems(items, action, indent, sourceDelimeter); } return string.Empty; } - public static string RenderItems(IEnumerable items, Func action) + public static string RenderItems(ICollection items, Func action, int indent = 0, SourceDelimeter sourceDelimeter = SourceDelimeter.None) { #if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(items); @@ -39,37 +46,31 @@ public static string RenderItems(IEnumerable items, Func action #endif var stringBuilder = new StringBuilder(); - foreach (var item in items) - { - stringBuilder.Append(action(item)); - } - - return stringBuilder.ToString(); - } - - public static string RenderItems(IEnumerable items, Func action) - { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(items); - ArgumentNullException.ThrowIfNull(action); -#else - if (items is null) + for (var i = 0; i < items.Count; ++i) { - throw new ArgumentNullException(nameof(items)); - } + var item = items.ElementAt(i); + var line = action(i, indent, item).Replace(Environment.NewLine, Environment.NewLine + new string(Space, indent)); + var isLast = i == items.Count - 1; - if (action is null) - { - throw new ArgumentNullException(nameof(action)); - } -#endif + stringBuilder.Append(Space, indent); - var stringBuilder = new StringBuilder(); - var i = 0; - foreach (var item in items) - { - stringBuilder.Append(action(item, i)); - i++; +#pragma warning disable IDE0072 // Add missing cases + stringBuilder = sourceDelimeter switch + { + SourceDelimeter.None => stringBuilder.Append(line), + SourceDelimeter.NewLine => isLast switch + { + false => stringBuilder.AppendLine(line), + true => stringBuilder.Append(line), + }, + SourceDelimeter.NewLineSpace => isLast switch + { + false => stringBuilder.AppendLine(line).AppendLine(), + true => stringBuilder.Append(line), + }, + _ => throw new ArgumentException("Source delimeter not recognised.", nameof(sourceDelimeter)), + }; +#pragma warning restore IDE0072 // Add missing cases } return stringBuilder.ToString(); diff --git a/Tools/Schema.NET.Updater/Program.cs b/Tools/Schema.NET.Updater/Program.cs index a5dbe40e..17a872d4 100644 --- a/Tools/Schema.NET.Updater/Program.cs +++ b/Tools/Schema.NET.Updater/Program.cs @@ -8,21 +8,23 @@ namespace Schema.NET.Updater; /// /// Updates the local Schema JSON file with data from Schema.org. /// -internal class Program +internal sealed class Program { private const string SchemaJsonSourceUrl = "https://schema.org/version/latest/schemaorg-all-https.jsonld"; private const string SchemaJsonDestinationFilePath = "../../Data/schemaorg-all-https.jsonld"; private static async Task Main() { -#pragma warning disable CA1303 // Do not pass literals as localized parameters Console.WriteLine("Downloading '{0}'...", SchemaJsonSourceUrl); + using var httpClient = new HttpClient(); using var stream = await httpClient.GetStreamAsync(new Uri(SchemaJsonSourceUrl)).ConfigureAwait(true); + Console.WriteLine("Saving to '{0}'...", SchemaJsonDestinationFilePath); + using var fileStream = File.Open(SchemaJsonDestinationFilePath, FileMode.Create); await stream.CopyToAsync(fileStream).ConfigureAwait(true); + Console.WriteLine("Update complete!"); -#pragma warning restore CA1303 // Do not pass literals as localized parameters } } diff --git a/Tools/Schema.NET.Updater/Schema.NET.Updater.csproj b/Tools/Schema.NET.Updater/Schema.NET.Updater.csproj index 36904998..19285c19 100644 --- a/Tools/Schema.NET.Updater/Schema.NET.Updater.csproj +++ b/Tools/Schema.NET.Updater/Schema.NET.Updater.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net7.0 false diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fdd55ebf..2dec08ea 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -49,10 +49,10 @@ stages: packageType: "sdk" version: 3.1.x - task: UseDotNet@2 - displayName: "Install .NET Core 5.0 SDK" + displayName: "Install .NET Core 6.0 SDK" inputs: packageType: "sdk" - version: 5.0.x + version: 6.0.x - task: UseDotNet@2 displayName: "Install .NET Core SDK" inputs: diff --git a/global.json b/global.json index 579ea349..81014239 100644 --- a/global.json +++ b/global.json @@ -2,6 +2,6 @@ "sdk": { "allowPrerelease": true, "rollForward": "latestMajor", - "version": "6.0.402" + "version": "7.0.100-rc.1.22431.12" } }