From e20a62d706b0ee23e0e42fe725f342add15bb618 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Mon, 22 Mar 2021 21:55:02 +1030 Subject: [PATCH 01/30] Add core System.Text.Json support --- Source/Common/ContextJsonConverter.cs | 103 +++++------ ...ateTimeToIso8601DateValuesJsonConverter.cs | 20 +- Source/Common/JsonLdObject.cs | 10 +- Source/Common/SchemaSerializer.cs | 59 +++--- Source/Common/Thing.Partial.cs | 15 +- ...panToISO8601DurationValuesJsonConverter.cs | 15 +- Source/Common/ValuesJsonConverter.cs | 174 +++++++++--------- Source/Schema.NET/Schema.NET.csproj | 2 +- .../Schema.NET.Tool/SchemaSourceGenerator.cs | 14 +- 9 files changed, 194 insertions(+), 218 deletions(-) diff --git a/Source/Common/ContextJsonConverter.cs b/Source/Common/ContextJsonConverter.cs index 75adba5e..711e7207 100644 --- a/Source/Common/ContextJsonConverter.cs +++ b/Source/Common/ContextJsonConverter.cs @@ -1,8 +1,8 @@ namespace Schema.NET { using System; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; /// /// Converts a object to and from JSON. @@ -11,78 +11,65 @@ namespace Schema.NET public class ContextJsonConverter : JsonConverter { /// - public override JsonLdContext ReadJson(JsonReader reader, Type objectType, JsonLdContext? existingValue, bool hasExistingValue, JsonSerializer serializer) + public override JsonLdContext Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(reader); - ArgumentNullException.ThrowIfNull(objectType); - if (hasExistingValue) - { - ArgumentNullException.ThrowIfNull(existingValue); - } - - ArgumentNullException.ThrowIfNull(serializer); + ArgumentNullException.ThrowIfNull(typeToConvert); + ArgumentNullException.ThrowIfNull(options); #else - if (reader is null) - { - throw new ArgumentNullException(nameof(reader)); - } - - if (objectType is null) - { - throw new ArgumentNullException(nameof(objectType)); - } - - if (hasExistingValue && existingValue is null) + if (typeToConvert is null) { - throw new ArgumentNullException(nameof(existingValue)); + throw new ArgumentNullException(nameof(typeToConvert)); } - if (serializer is null) + if (options is null) { - throw new ArgumentNullException(nameof(serializer)); + throw new ArgumentNullException(nameof(options)); } #endif + var context = new JsonLdContext(); - var context = hasExistingValue ? existingValue! : new JsonLdContext(); - - string? name; - string? language; - if (reader.TokenType == JsonToken.String) + string? name = null; + string? language = null; + if (reader.TokenType == JsonTokenType.String) { - name = (string?)reader.Value; - language = null; + name = reader.GetString(); } - else if (reader.TokenType == JsonToken.StartObject) + else if (reader.TokenType == JsonTokenType.StartObject) { - var o = JObject.Load(reader); + var document = JsonDocument.ParseValue(ref reader); - var nameProperty = o.Property("name", StringComparison.OrdinalIgnoreCase); - name = nameProperty?.Value?.ToString() ?? "https://schema.org"; + if (document.RootElement.TryGetProperty("name", out var nameElement)) + { + name = nameElement.GetString() ?? "http://schema.org"; + } - var languageProperty = o.Property("@language", StringComparison.OrdinalIgnoreCase); - language = languageProperty?.Value?.ToString(); + if (document.RootElement.TryGetProperty("@language", out var languageElement)) + { + language = languageElement.GetString(); + } } else { - var a = JArray.Load(reader); + var array = JsonDocument.ParseValue(ref reader).RootElement.EnumerateArray(); - name = language = null; - foreach (var entry in a) + foreach (var entry in array) { - if (entry.Type == JTokenType.String) + if (entry.ValueKind == JsonValueKind.String) { - name ??= (string?)entry; + name ??= entry.GetString(); } - else + else if (entry.ValueKind == JsonValueKind.Object) { - var o = (JObject)entry; - - var nameProperty = o.Property("name", StringComparison.OrdinalIgnoreCase); - name ??= nameProperty?.Value?.ToString() ?? "https://schema.org"; - - var languageProperty = o.Property("@language", StringComparison.OrdinalIgnoreCase); - language ??= languageProperty?.Value?.ToString(); + if (entry.TryGetProperty("name", out var nameElement)) + { + name ??= nameElement.GetString() ?? "http://schema.org"; + } + + if (entry.TryGetProperty("@language", out var languageElement)) + { + language ??= languageElement.GetString(); + } } } } @@ -95,12 +82,12 @@ public override JsonLdContext ReadJson(JsonReader reader, Type objectType, JsonL } /// - public override void WriteJson(JsonWriter writer, JsonLdContext? value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, JsonLdContext value, JsonSerializerOptions options) { #if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(writer); ArgumentNullException.ThrowIfNull(value); - ArgumentNullException.ThrowIfNull(serializer); + ArgumentNullException.ThrowIfNull(options); #else if (writer is null) { @@ -112,23 +99,23 @@ public override void WriteJson(JsonWriter writer, JsonLdContext? value, JsonSeri throw new ArgumentNullException(nameof(value)); } - if (serializer is null) + if (options is null) { - throw new ArgumentNullException(nameof(serializer)); + throw new ArgumentNullException(nameof(options)); } #endif if (string.IsNullOrWhiteSpace(value.Language)) { - writer.WriteValue(value.Name); + writer.WriteStringValue(value.Name); } else { writer.WriteStartObject(); writer.WritePropertyName("name"); - writer.WriteValue(value.Name); + writer.WriteStringValue(value.Name); writer.WritePropertyName("@language"); - writer.WriteValue(value.Language); + writer.WriteStringValue(value.Language); writer.WriteEndObject(); } } diff --git a/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs b/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs index 0ae92977..ecc45b86 100644 --- a/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs +++ b/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs @@ -2,7 +2,8 @@ namespace Schema.NET { using System; using System.Globalization; - using Newtonsoft.Json; + using System.Text.Json; + using System.Text.Json.Serialization; /// /// Converts an object to JSON. If the contains a @@ -17,8 +18,8 @@ public class DateTimeToIso8601DateValuesJsonConverter : ValuesJsonConverter /// /// The JSON writer. /// The value to write. - /// The JSON serializer. - public override void WriteObject(JsonWriter writer, object? value, JsonSerializer serializer) + /// The JSON serializer options. + public override void WriteObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) { #if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(writer); @@ -29,19 +30,24 @@ public override void WriteObject(JsonWriter writer, object? value, JsonSerialize throw new ArgumentNullException(nameof(writer)); } - if (serializer is null) + if (value is null) { - throw new ArgumentNullException(nameof(serializer)); + throw new ArgumentNullException(nameof(value)); + } + + if (options is null) + { + throw new ArgumentNullException(nameof(options)); } #endif if (value is DateTime dateTimeType) { - writer.WriteValue(dateTimeType.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + writer.WriteStringValue(dateTimeType.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); } else { - base.WriteObject(writer, value, serializer); + base.WriteObject(writer, value, options); } } } diff --git a/Source/Common/JsonLdObject.cs b/Source/Common/JsonLdObject.cs index d5eaa914..ade074c5 100644 --- a/Source/Common/JsonLdObject.cs +++ b/Source/Common/JsonLdObject.cs @@ -2,14 +2,12 @@ namespace Schema.NET { using System; using System.Collections.Generic; - using System.Runtime.Serialization; - using Newtonsoft.Json; + using System.Text.Json.Serialization; /// /// The base JSON-LD object. /// See https://json-ld.org/spec/latest/json-ld /// - [DataContract] public class JsonLdObject : IEquatable { /// @@ -24,7 +22,7 @@ public class JsonLdObject : IEquatable /// Simply speaking, a context is used to map terms to IRIs. Terms are case sensitive and any valid string that /// is not a reserved JSON-LD keyword can be used as a term. /// - [DataMember(Name = "@context", Order = 0)] + [JsonPropertyName("@context")] [JsonConverter(typeof(ContextJsonConverter))] public virtual JsonLdContext Context { get; internal set; } = new JsonLdContext(); @@ -32,7 +30,7 @@ public class JsonLdObject : IEquatable /// Gets the type, used to uniquely identify things that are being described in the document with IRIs or /// blank node identifiers. /// - [DataMember(Name = "@type", Order = 1)] + [JsonPropertyName("@type")] public virtual string? Type { get; } /// @@ -43,7 +41,7 @@ public class JsonLdObject : IEquatable /// result in a representation of that node.This may allow an application to retrieve further information about /// a node. In JSON-LD, a node is identified using the @id keyword: /// - [DataMember(Name = "@id", Order = 2)] + [JsonPropertyName("@id")] public virtual Uri? Id { get; set; } /// diff --git a/Source/Common/SchemaSerializer.cs b/Source/Common/SchemaSerializer.cs index a17cd0b1..243a8485 100644 --- a/Source/Common/SchemaSerializer.cs +++ b/Source/Common/SchemaSerializer.cs @@ -3,8 +3,8 @@ namespace Schema.NET using System; using System.Collections.Generic; using System.Text; - using Newtonsoft.Json; - using Newtonsoft.Json.Converters; + using System.Text.Json; + using System.Text.Json.Serialization; /// /// Schema JSON Serializer @@ -13,41 +13,36 @@ public static class SchemaSerializer { private const string ContextPropertyJson = "\"@context\":\"https://schema.org\","; - /// - /// Default serializer settings used when deserializing - /// - private static readonly JsonSerializerSettings DeserializeSettings = new() - { - DateParseHandling = DateParseHandling.None, - }; - /// /// Default serializer settings used when HTML escaping is not required. /// - private static readonly JsonSerializerSettings DefaultSerializationSettings = new() - { - Converters = new List() - { - new StringEnumConverter(), - }, - DefaultValueHandling = DefaultValueHandling.Ignore, - NullValueHandling = NullValueHandling.Ignore, - }; + private static readonly JsonSerializerOptions DefaultSerializationSettings; /// /// Serializer settings used when trying to avoid XSS vulnerabilities where user-supplied data is used /// and the output of the serialization is embedded into a web page raw. /// - private static readonly JsonSerializerSettings HtmlEscapedSerializationSettings = new() + private static readonly JsonSerializerOptions HtmlEscapedSerializationSettings; + +#pragma warning disable CA1810 // Initialize reference type static fields inline + static SchemaSerializer() +#pragma warning restore CA1810 // Initialize reference type static fields inline { - Converters = new List() + var stringEnumConverter = new JsonStringEnumConverter(); + + DefaultSerializationSettings = new JsonSerializerOptions { - new StringEnumConverter(), - }, - DefaultValueHandling = DefaultValueHandling.Ignore, - NullValueHandling = NullValueHandling.Ignore, - StringEscapeHandling = StringEscapeHandling.EscapeHtml, - }; + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + DefaultSerializationSettings.Converters.Add(stringEnumConverter); + + HtmlEscapedSerializationSettings = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, + }; + HtmlEscapedSerializationSettings.Converters.Add(stringEnumConverter); + } /// /// Deserializes the JSON to the specified type. @@ -55,8 +50,8 @@ public static class SchemaSerializer /// Deserialization target type /// JSON to deserialize /// An instance of deserialized from JSON - public static T? DeserializeObject(string value) => - JsonConvert.DeserializeObject(value, DeserializeSettings); + public static T? DeserializeObject(string value) + => JsonSerializer.Deserialize(value); /// /// Serializes the value to JSON with default serialization settings. @@ -78,10 +73,10 @@ public static string HtmlEscapedSerializeObject(object value) => /// Serializes the value to JSON with custom serialization settings. /// /// Serialization target value - /// JSON serialization settings + /// JSON serialization settings /// The serialized JSON string - public static string SerializeObject(object value, JsonSerializerSettings jsonSerializerSettings) => - RemoveAllButFirstContext(JsonConvert.SerializeObject(value, jsonSerializerSettings)); + public static string SerializeObject(object value, JsonSerializerOptions options) + => RemoveAllButFirstContext(JsonSerializer.Serialize(value, options)); private static string RemoveAllButFirstContext(string json) { diff --git a/Source/Common/Thing.Partial.cs b/Source/Common/Thing.Partial.cs index 9818ccc3..76ab8eb5 100644 --- a/Source/Common/Thing.Partial.cs +++ b/Source/Common/Thing.Partial.cs @@ -1,6 +1,6 @@ namespace Schema.NET { - using Newtonsoft.Json; + using System.Text.Json; /// /// The most generic type of item. @@ -37,18 +37,13 @@ public partial class Thing : JsonLdObject public string ToHtmlEscapedString() => SchemaSerializer.HtmlEscapedSerializeObject(this); /// - /// Returns the JSON-LD representation of this instance using the provided. - /// - /// Caution: You should ensure your has - /// set to - /// if you plan to embed the output using @Html.Raw anywhere in a web page, else you open yourself up a possible - /// Cross-Site Scripting (XSS) attack if untrusted data is set on any of this object's properties. + /// Returns the JSON-LD representation of this instance using the provided. /// - /// Serialization settings. + /// Serialization settings. /// /// A that represents the JSON-LD representation of this instance. /// - public string ToString(JsonSerializerSettings serializerSettings) => - SchemaSerializer.SerializeObject(this, serializerSettings); + public string ToString(JsonSerializerOptions options) => + SchemaSerializer.SerializeObject(this, options); } } diff --git a/Source/Common/TimeSpanToISO8601DurationValuesJsonConverter.cs b/Source/Common/TimeSpanToISO8601DurationValuesJsonConverter.cs index d38bfa14..5ad03a52 100644 --- a/Source/Common/TimeSpanToISO8601DurationValuesJsonConverter.cs +++ b/Source/Common/TimeSpanToISO8601DurationValuesJsonConverter.cs @@ -1,8 +1,9 @@ namespace Schema.NET { using System; + using System.Text.Json; + using System.Text.Json.Serialization; using System.Xml; - using Newtonsoft.Json; /// /// Converts an object to JSON. If the contains a @@ -17,8 +18,8 @@ public class TimeSpanToISO8601DurationValuesJsonConverter : ValuesJsonConverter /// /// The JSON writer. /// The value to write. - /// The JSON serializer. - public override void WriteObject(JsonWriter writer, object? value, JsonSerializer serializer) + /// The JSON serializer options. + public override void WriteObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) { #if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(writer); @@ -35,19 +36,19 @@ public override void WriteObject(JsonWriter writer, object? value, JsonSerialize throw new ArgumentNullException(nameof(value)); } - if (serializer is null) + if (options is null) { - throw new ArgumentNullException(nameof(serializer)); + throw new ArgumentNullException(nameof(options)); } #endif if (value is TimeSpan duration) { - writer.WriteValue(XmlConvert.ToString(duration)); + writer.WriteStringValue(XmlConvert.ToString(duration)); } else { - base.WriteObject(writer, value, serializer); + base.WriteObject(writer, value, options); } } } diff --git a/Source/Common/ValuesJsonConverter.cs b/Source/Common/ValuesJsonConverter.cs index 9e23e48d..79efd2ca 100644 --- a/Source/Common/ValuesJsonConverter.cs +++ b/Source/Common/ValuesJsonConverter.cs @@ -6,110 +6,91 @@ namespace Schema.NET using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; + using System.Text.Json; + using System.Text.Json.Serialization; using System.Xml; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; /// /// Converts a object to and from JSON. /// /// - public class ValuesJsonConverter : JsonConverter + public class ValuesJsonConverter : JsonConverter { private const string HttpSchemaOrgUrl = "http://schema.org/"; private const int HttpSchemaOrgLength = 18; // equivalent to "http://schema.org/".Length private const string HttpsSchemaOrgUrl = "https://schema.org/"; private const int HttpsSchemaOrgLength = 19; // equivalent to "https://schema.org/".Length - private static readonly TypeInfo ThingInterfaceTypeInfo = typeof(IThing).GetTypeInfo(); private static readonly Dictionary BuiltInThingTypeLookup = new(StringComparer.Ordinal); static ValuesJsonConverter() { - var thisAssembly = ThingInterfaceTypeInfo.Assembly; + var thisAssembly = typeof(IThing).Assembly; foreach (var type in thisAssembly.ExportedTypes) { - var typeInfo = type.GetTypeInfo(); - if (typeInfo.IsClass && ThingInterfaceTypeInfo.IsAssignableFrom(typeInfo)) + if (type.IsClass && typeof(IThing).IsAssignableFrom(type)) { BuiltInThingTypeLookup.Add(type.Name, type); } } } - /// - /// Determines whether this instance can convert the specified object type. - /// - /// Type of the object. - /// - /// true if this instance can convert the specified object type; otherwise, false. - /// - public override bool CanConvert(Type objectType) => objectType == typeof(IValues); + /// + public override bool CanConvert(Type typeToConvert) => typeof(IValues).IsAssignableFrom(typeToConvert); /// /// Reads the JSON representation of the object. /// - /// The to read from. - /// Type of the object. - /// The existing value of object being read. - /// The calling serializer. + /// The to read from. + /// Type of the object. + /// The serializer options. /// The object value. - public override object? ReadJson( - JsonReader reader, - Type objectType, - object? existingValue, - JsonSerializer serializer) + public override IValues? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { #if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(reader); - ArgumentNullException.ThrowIfNull(objectType); - ArgumentNullException.ThrowIfNull(serializer); + ArgumentNullException.ThrowIfNull(typeToConvert); + ArgumentNullException.ThrowIfNull(options); #else - if (reader is null) - { - throw new ArgumentNullException(nameof(reader)); - } - - if (objectType is null) + if (typeToConvert is null) { - throw new ArgumentNullException(nameof(objectType)); + throw new ArgumentNullException(nameof(typeToConvert)); } - if (serializer is null) + if (options is null) { - throw new ArgumentNullException(nameof(serializer)); + throw new ArgumentNullException(nameof(options)); } #endif - var dynamicConstructor = FastActivator.GetDynamicConstructor>(objectType); + var dynamicConstructor = FastActivator.GetDynamicConstructor>(typeToConvert); if (dynamicConstructor is not null) { - if (reader.TokenType == JsonToken.StartArray) + if (reader.TokenType == JsonTokenType.StartArray) { var items = new List(); while (reader.Read()) { - if (reader.TokenType == JsonToken.EndArray) + if (reader.TokenType == JsonTokenType.EndArray) { break; } - if (reader.TokenType == JsonToken.Null) + if (reader.TokenType == JsonTokenType.Null) { continue; } - var item = ProcessToken(reader, objectType.GenericTypeArguments, serializer); + var item = ProcessToken(ref reader, typeToConvert.GenericTypeArguments, options); items.Add(item); } - return dynamicConstructor(items); + return (IValues)dynamicConstructor(items); } - else if (reader.TokenType != JsonToken.Null) + else if (reader.TokenType != JsonTokenType.Null) { - var item = ProcessToken(reader, objectType.GenericTypeArguments, serializer); - return dynamicConstructor(new[] { item }); + var item = ProcessToken(ref reader, typeToConvert.GenericTypeArguments, options); + return (IValues)dynamicConstructor(new[] { item }); } } @@ -121,13 +102,13 @@ static ValuesJsonConverter() /// /// The JSON writer. /// The object. - /// The JSON serializer. - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + /// The JSON serializer options. + public override void Write(Utf8JsonWriter writer, IValues value, JsonSerializerOptions options) { #if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(writer); ArgumentNullException.ThrowIfNull(value); - ArgumentNullException.ThrowIfNull(serializer); + ArgumentNullException.ThrowIfNull(options); #else if (writer is null) { @@ -139,29 +120,28 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer throw new ArgumentNullException(nameof(value)); } - if (serializer is null) + if (options is null) { - throw new ArgumentNullException(nameof(serializer)); + throw new ArgumentNullException(nameof(options)); } #endif - var values = (IValues)value; - if (values.Count == 0) + if (value.Count == 0) { - serializer.Serialize(writer, null); + writer.WriteNullValue(); } - else if (values.Count == 1) + else if (value.Count == 1) { - var enumerator = values.GetEnumerator(); - enumerator.MoveNext(); - this.WriteObject(writer, enumerator.Current, serializer); + var enumerator = value.GetEnumerator(); + _ = enumerator.MoveNext(); + this.WriteObject(writer, enumerator.Current, options); } else { writer.WriteStartArray(); - foreach (var item in values) + foreach (var item in value) { - this.WriteObject(writer, item, serializer); + this.WriteObject(writer, item, options); } writer.WriteEndArray(); @@ -173,44 +153,44 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer /// /// The JSON writer. /// The value to write. - /// The JSON serializer. - public virtual void WriteObject(JsonWriter writer, object? value, JsonSerializer serializer) + /// The JSON serializer options. + public virtual void WriteObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) { #if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(writer); - ArgumentNullException.ThrowIfNull(serializer); + ArgumentNullException.ThrowIfNull(options); #else if (writer is null) { throw new ArgumentNullException(nameof(writer)); } - if (serializer is null) + if (options is null) { - throw new ArgumentNullException(nameof(serializer)); + throw new ArgumentNullException(nameof(options)); } #endif - serializer.Serialize(writer, value); + JsonSerializer.Serialize(writer, value, value?.GetType() ?? typeof(object), options); } - private static object? ProcessToken(JsonReader reader, Type[] targetTypes, JsonSerializer serializer) + private static object? ProcessToken(ref Utf8JsonReader reader, Type[] targetTypes, JsonSerializerOptions options) { - if (reader.TokenType == JsonToken.StartObject) + if (reader.TokenType == JsonTokenType.StartObject) { - var token = JToken.ReadFrom(reader); + var objectRoot = JsonDocument.ParseValue(ref reader).RootElement; // Use the type property (if provided) to identify the correct type - var explicitTypeFromToken = token.SelectToken("@type")?.ToString(); - if (!string.IsNullOrEmpty(explicitTypeFromToken) && TryGetConcreteType(explicitTypeFromToken!, out var explicitType)) + if (objectRoot.TryGetProperty("@type", out var typeElement) && + typeElement.ValueKind == JsonValueKind.String && + TryGetConcreteType(typeElement.GetString()!, out var explicitType)) { - var explicitTypeInfo = explicitType!.GetTypeInfo(); for (var i = 0; i < targetTypes.Length; i++) { - var targetTypeInfo = targetTypes[i].GetTypeInfo(); - if (targetTypeInfo.IsAssignableFrom(explicitTypeInfo)) + var targetType = targetTypes[i]; + if (targetType.IsAssignableFrom(explicitType)) { - return token.ToObject(explicitType!, serializer); + return ProcessObject(objectRoot, explicitType!, options); } } } @@ -233,7 +213,7 @@ public virtual void WriteObject(JsonWriter writer, object? value, JsonSerializer localTargetType = concreteType!; } - return token.ToObject(localTargetType, serializer); + return ProcessObject(objectRoot, localTargetType, options); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) @@ -250,7 +230,7 @@ public virtual void WriteObject(JsonWriter writer, object? value, JsonSerializer for (var i = targetTypes.Length - 1; i >= 0; i--) { var underlyingTargetType = targetTypes[i].GetUnderlyingTypeFromNullable(); - if (TryProcessTokenAsType(reader, underlyingTargetType, out var value)) + if (TryProcessTokenAsType(ref reader, underlyingTargetType, out var value)) { return value; } @@ -260,21 +240,21 @@ public virtual void WriteObject(JsonWriter writer, object? value, JsonSerializer return null; } - private static bool TryProcessTokenAsType(JsonReader reader, Type targetType, out object? value) + private static bool TryProcessTokenAsType(ref Utf8JsonReader reader, Type targetType, out object? value) { var success = false; object? result = null; var tokenType = reader.TokenType; - if (reader.ValueType == targetType) + if (tokenType == JsonTokenType.String) { - result = reader.Value; - success = true; - } - else if (tokenType == JsonToken.String) - { - var valueString = (string?)reader.Value; - if (targetType.GetTypeInfo().IsPrimitive) + var valueString = reader.GetString(); + if (targetType == typeof(string)) + { + success = true; + result = valueString; + } + else if (targetType.GetTypeInfo().IsPrimitive) { if (targetType == typeof(int)) { @@ -400,11 +380,19 @@ private static bool TryProcessTokenAsType(JsonReader reader, Type targetType, ou result = localResult; } } - else if (tokenType is JsonToken.Integer or JsonToken.Float) + else if (tokenType == JsonTokenType.Number) { - if (targetType.GetTypeInfo().IsPrimitive || targetType == typeof(decimal)) + if (targetType == typeof(short) || targetType == typeof(int) || targetType == typeof(long) || targetType == typeof(float) || targetType == typeof(double) || targetType == typeof(decimal)) { - result = Convert.ChangeType(reader.Value, targetType, CultureInfo.InvariantCulture); + result = Convert.ChangeType(reader.GetDecimal(), targetType, CultureInfo.InvariantCulture); + success = true; + } + } + else if (tokenType is JsonTokenType.True or JsonTokenType.False) + { + if (targetType == typeof(bool)) + { + result = reader.GetBoolean(); success = true; } } @@ -429,7 +417,7 @@ private static bool TryGetConcreteType( try { var localType = Type.GetType(typeName, false); - if (localType is not null && ThingInterfaceTypeInfo.IsAssignableFrom(localType.GetTypeInfo())) + if (typeof(IThing).IsAssignableFrom(localType)) { type = localType; return true; @@ -450,5 +438,13 @@ private static bool TryGetConcreteType( #pragma warning restore CA1031 // Do not catch general exception types } } + + private static object? ProcessObject(JsonElement element, Type objectType, JsonSerializerOptions options) + { + // TODO: Investigate avoiding the string allocation + // Related issue: https://github.com/dotnet/runtime/issues/31274 + var json = element.GetRawText(); + return JsonSerializer.Deserialize(json, objectType ?? typeof(object), options); + } } } diff --git a/Source/Schema.NET/Schema.NET.csproj b/Source/Schema.NET/Schema.NET.csproj index a3b6e64c..bc2ef8a9 100644 --- a/Source/Schema.NET/Schema.NET.csproj +++ b/Source/Schema.NET/Schema.NET.csproj @@ -14,7 +14,7 @@ - + diff --git a/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs b/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs index 4454f88a..40aef69a 100644 --- a/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs +++ b/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs @@ -100,8 +100,8 @@ private static string RenderClass(GeneratorSchemaClass schemaClass) {{ using System; using System.Collections.Generic; - using System.Runtime.Serialization; - using Newtonsoft.Json; + using System.Text.Json; + using System.Text.Json.Serialization; /// /// {SourceUtility.RenderDoc(4, schemaClass.Description)} @@ -117,19 +117,18 @@ public partial interface I{schemaClass.Name}{interfaceImplements} /// /// {SourceUtility.RenderDoc(4, schemaClass.Description)} /// - [DataContract] public{classModifiers} partial class {schemaClass.Name} :{classImplements} I{schemaClass.Name}, IEquatable<{schemaClass.Name}> {{ /// /// Gets the name of the type as specified by schema.org. /// - [DataMember(Name = ""@type"", Order = 1)] + [JsonPropertyName(""@type"")] public override string Type => ""{schemaClass.Name}"";{SourceUtility.RenderItems(allProperties, property => $@" /// /// {SourceUtility.RenderDoc(8, property.Description)} /// - [DataMember(Name = ""{property.JsonName}"", Order = {property.Order})] + [JsonPropertyName(""{property.JsonName}"")] [JsonConverter(typeof({property.JsonConverterType}))] public{GetAccessModifier(property)} {property.PropertyTypeString} {property.Name} {{ get; set; }}")} @@ -242,13 +241,12 @@ private static string RenderEnumeration(GeneratorSchemaEnumeration schemaEnumera $@"namespace Schema.NET {{ using System.Runtime.Serialization; - using Newtonsoft.Json; - using Newtonsoft.Json.Converters; + using System.Text.Json.Serialization; /// /// {SourceUtility.RenderDoc(4, schemaEnumeration.Description)} /// - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum {schemaEnumeration.Name} {{{SourceUtility.RenderItems(schemaEnumeration.Values, value => $@" /// From 60dc367e6eefccf8f3d62bf5bb853d9227fffe6c Mon Sep 17 00:00:00 2001 From: Turnerj Date: Mon, 22 Mar 2021 21:55:19 +1030 Subject: [PATCH 02/30] Updated benchmarks to use S.T.J --- Benchmarks/Schema.NET.Benchmarks/SchemaBenchmarkBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Benchmarks/Schema.NET.Benchmarks/SchemaBenchmarkBase.cs b/Benchmarks/Schema.NET.Benchmarks/SchemaBenchmarkBase.cs index 3fe423dd..f1eccf94 100644 --- a/Benchmarks/Schema.NET.Benchmarks/SchemaBenchmarkBase.cs +++ b/Benchmarks/Schema.NET.Benchmarks/SchemaBenchmarkBase.cs @@ -1,9 +1,9 @@ namespace Schema.NET.Benchmarks { using System; + using System.Text.Json; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; - using Newtonsoft.Json; [KeepBenchmarkFiles] [MemoryDiagnoser] @@ -36,6 +36,6 @@ public virtual void Setup() public string Serialize() => this.Thing.ToString(); [Benchmark] - public object? Deserialize() => JsonConvert.DeserializeObject(this.SerializedThing, this.ThingType); + public object? Deserialize() => JsonSerializer.Deserialize(this.SerializedThing, this.ThingType); } } From 2bf322e4c9e221dd5d0817a09ac2babb3996eb6e Mon Sep 17 00:00:00 2001 From: Turnerj Date: Mon, 22 Mar 2021 21:55:30 +1030 Subject: [PATCH 03/30] Updated tests to use S.T.J --- .../ExternalSchemaModelCustomNamespace.cs | 2 +- .../ExternalSchemaModelSharedNamespace.cs | 2 +- .../ValuesJsonConverterTest.cs | 26 +++++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Tests/Schema.NET.Test/Models/ExternalSchemaModelCustomNamespace.cs b/Tests/Schema.NET.Test/Models/ExternalSchemaModelCustomNamespace.cs index b5d337a3..6dc2dd75 100644 --- a/Tests/Schema.NET.Test/Models/ExternalSchemaModelCustomNamespace.cs +++ b/Tests/Schema.NET.Test/Models/ExternalSchemaModelCustomNamespace.cs @@ -1,7 +1,7 @@ namespace SomeCustomNamespace { using System.Runtime.Serialization; - using Newtonsoft.Json; + using System.Text.Json.Serialization; using Schema.NET; public class ExternalSchemaModelCustomNamespace : Thing diff --git a/Tests/Schema.NET.Test/Models/ExternalSchemaModelSharedNamespace.cs b/Tests/Schema.NET.Test/Models/ExternalSchemaModelSharedNamespace.cs index beb19001..8f6391f8 100644 --- a/Tests/Schema.NET.Test/Models/ExternalSchemaModelSharedNamespace.cs +++ b/Tests/Schema.NET.Test/Models/ExternalSchemaModelSharedNamespace.cs @@ -1,7 +1,7 @@ namespace Schema.NET { using System.Runtime.Serialization; - using Newtonsoft.Json; + using System.Text.Json.Serialization; public class ExternalSchemaModelSharedNamespace : Thing { diff --git a/Tests/Schema.NET.Test/ValuesJsonConverterTest.cs b/Tests/Schema.NET.Test/ValuesJsonConverterTest.cs index 46417c4a..709d26ac 100644 --- a/Tests/Schema.NET.Test/ValuesJsonConverterTest.cs +++ b/Tests/Schema.NET.Test/ValuesJsonConverterTest.cs @@ -2,7 +2,7 @@ namespace Schema.NET.Test { using System; using System.Linq; - using Newtonsoft.Json; + using System.Text.Json.Serialization; using Xunit; public class ValuesJsonConverterTest @@ -276,7 +276,7 @@ public void ReadJson_Values_SingleValue_ThingInterface() "\"author\":{" + "\"@type\":\"Person\"," + "\"name\":\"J.D. Salinger\"" + - "}," + + "}" + "}" + "}"; var result = DeserializeObject>(json); @@ -302,7 +302,7 @@ public void ReadJson_Values_SingleValue_ThingActual() "\"author\":{" + "\"@type\":\"Person\"," + "\"name\":\"J.D. Salinger\"" + - "}," + + "}" + "}" + "}"; var result = DeserializeObject>(json); @@ -327,7 +327,7 @@ public void ReadJson_Values_SingleValue_ThingInterfaceWithNoTypeToken() "\"author\":{" + "\"@type\":\"Person\"," + "\"name\":\"J.D. Salinger\"" + - "}," + + "}" + "}" + "}"; var result = DeserializeObject>(json); @@ -352,7 +352,7 @@ public void ReadJson_Values_SingleValue_ThingActualWithNoTypeToken() "\"author\":{" + "\"@type\":\"Person\"," + "\"name\":\"J.D. Salinger\"" + - "}," + + "}" + "}" + "}"; var result = DeserializeObject>(json); @@ -435,7 +435,7 @@ public void ReadJson_Values_MultiValue_ThingInterface() "\"author\":{" + "\"@type\":\"Person\"," + "\"name\":\"J.D. Salinger\"" + - "}," + + "}" + "}," + "{" + "\"@context\":\"https://schema.org\"," + @@ -446,7 +446,7 @@ public void ReadJson_Values_MultiValue_ThingInterface() "\"author\":{" + "\"@type\":\"Person\"," + "\"name\":\"J.R.R. Tolkien\"" + - "}," + + "}" + "}" + "]}"; var result = DeserializeObject>(json); @@ -564,18 +564,18 @@ public void ReadJson_ImplicitExternalTypes_AllowSharedNamespace() } private static string SerializeObject(T value) - where T : IValues => - SchemaSerializer.SerializeObject(new TestModel { Property = value }); + where T : struct, IValues + => SchemaSerializer.SerializeObject(new TestModel { Property = value }); private static T DeserializeObject(string json) - where T : IValues => - SchemaSerializer.DeserializeObject>(json)!.Property!; + where T : struct, IValues + => SchemaSerializer.DeserializeObject>(json)!.Property; private class TestModel - where T : IValues + where T : struct, IValues { [JsonConverter(typeof(ValuesJsonConverter))] - public T? Property { get; set; } + public T Property { get; set; } } } } From 49cf046e0d1cd65c125963adfd2fda9e0060806d Mon Sep 17 00:00:00 2001 From: Turnerj Date: Mon, 22 Mar 2021 22:25:11 +1030 Subject: [PATCH 04/30] Use 6.0.0-preview.2 of S.T.J This has the fix for handling default values --- Source/Schema.NET/Schema.NET.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Schema.NET/Schema.NET.csproj b/Source/Schema.NET/Schema.NET.csproj index bc2ef8a9..6a00148a 100644 --- a/Source/Schema.NET/Schema.NET.csproj +++ b/Source/Schema.NET/Schema.NET.csproj @@ -14,7 +14,7 @@ - + From fefb1aabd6f1942ff949e820fdade9f223f41b69 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Mon, 22 Mar 2021 22:25:27 +1030 Subject: [PATCH 05/30] Use correct attribute for tests --- .../Models/ExternalSchemaModelCustomNamespace.cs | 5 ++--- .../Models/ExternalSchemaModelSharedNamespace.cs | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Tests/Schema.NET.Test/Models/ExternalSchemaModelCustomNamespace.cs b/Tests/Schema.NET.Test/Models/ExternalSchemaModelCustomNamespace.cs index 6dc2dd75..ab81375a 100644 --- a/Tests/Schema.NET.Test/Models/ExternalSchemaModelCustomNamespace.cs +++ b/Tests/Schema.NET.Test/Models/ExternalSchemaModelCustomNamespace.cs @@ -1,15 +1,14 @@ namespace SomeCustomNamespace { - using System.Runtime.Serialization; using System.Text.Json.Serialization; using Schema.NET; public class ExternalSchemaModelCustomNamespace : Thing { - [DataMember(Name = "@type")] + [JsonPropertyName("@type")] public override string Type => "ExternalSchemaModelCustomNamespace"; - [DataMember(Name = "myCustomProperty")] + [JsonPropertyName("myCustomProperty")] [JsonConverter(typeof(ValuesJsonConverter))] public OneOrMany MyCustomProperty { get; set; } } diff --git a/Tests/Schema.NET.Test/Models/ExternalSchemaModelSharedNamespace.cs b/Tests/Schema.NET.Test/Models/ExternalSchemaModelSharedNamespace.cs index 8f6391f8..98cc69f0 100644 --- a/Tests/Schema.NET.Test/Models/ExternalSchemaModelSharedNamespace.cs +++ b/Tests/Schema.NET.Test/Models/ExternalSchemaModelSharedNamespace.cs @@ -5,10 +5,10 @@ namespace Schema.NET public class ExternalSchemaModelSharedNamespace : Thing { - [DataMember(Name = "@type")] + [JsonPropertyName("@type")] public override string Type => "ExternalSchemaModelSharedNamespace"; - [DataMember(Name = "myCustomProperty")] + [JsonPropertyName("myCustomProperty")] [JsonConverter(typeof(ValuesJsonConverter))] public OneOrMany MyCustomProperty { get; set; } } From e0699e681592b9fe591ce8c664e912e27a6ba2c4 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Mon, 22 Mar 2021 22:46:06 +1030 Subject: [PATCH 06/30] Removed unused using --- .../Schema.NET.Test/Models/ExternalSchemaModelSharedNamespace.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/Schema.NET.Test/Models/ExternalSchemaModelSharedNamespace.cs b/Tests/Schema.NET.Test/Models/ExternalSchemaModelSharedNamespace.cs index 98cc69f0..09add982 100644 --- a/Tests/Schema.NET.Test/Models/ExternalSchemaModelSharedNamespace.cs +++ b/Tests/Schema.NET.Test/Models/ExternalSchemaModelSharedNamespace.cs @@ -1,6 +1,5 @@ namespace Schema.NET { - using System.Runtime.Serialization; using System.Text.Json.Serialization; public class ExternalSchemaModelSharedNamespace : Thing From 78a860d0a4860708ab6c2c36148b1f5f011e17fb Mon Sep 17 00:00:00 2001 From: Turnerj Date: Mon, 22 Mar 2021 22:46:24 +1030 Subject: [PATCH 07/30] Fixed support for time of day --- Source/Common/ValuesJsonConverter.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Source/Common/ValuesJsonConverter.cs b/Source/Common/ValuesJsonConverter.cs index 79efd2ca..3f5a94cd 100644 --- a/Source/Common/ValuesJsonConverter.cs +++ b/Source/Common/ValuesJsonConverter.cs @@ -171,7 +171,19 @@ public virtual void WriteObject(Utf8JsonWriter writer, object? value, JsonSerial } #endif - JsonSerializer.Serialize(writer, value, value?.GetType() ?? typeof(object), options); + if (value is null) + { + writer.WriteNullValue(); + } + else if (value is TimeSpan timeSpan) + { + // System.Text.Json won't support timespans as time of day. See https://github.com/dotnet/runtime/issues/29932 + writer.WriteStringValue(timeSpan.ToString("c", CultureInfo.InvariantCulture)); + } + else + { + JsonSerializer.Serialize(writer, value, value.GetType(), options); + } } private static object? ProcessToken(ref Utf8JsonReader reader, Type[] targetTypes, JsonSerializerOptions options) From 57ed1cf4643628ffe44976c92d18e993473f3e9f Mon Sep 17 00:00:00 2001 From: Turnerj Date: Mon, 22 Mar 2021 22:52:48 +1030 Subject: [PATCH 08/30] Added tests for extended type converters --- .../DateValuesJsonConverterTest.cs | 28 +++++++++++++++++++ .../DurationValuesJsonConverterTest.cs | 28 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 Tests/Schema.NET.Test/DateValuesJsonConverterTest.cs create mode 100644 Tests/Schema.NET.Test/DurationValuesJsonConverterTest.cs diff --git a/Tests/Schema.NET.Test/DateValuesJsonConverterTest.cs b/Tests/Schema.NET.Test/DateValuesJsonConverterTest.cs new file mode 100644 index 00000000..1fa8cee4 --- /dev/null +++ b/Tests/Schema.NET.Test/DateValuesJsonConverterTest.cs @@ -0,0 +1,28 @@ +namespace Schema.NET.Test +{ + using System; + using System.Text.Json.Serialization; + using Xunit; + + public class DateValuesJsonConverterTest + { + [Fact] + 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); + } + + private static string SerializeObject(T value) + where T : struct, IValues + => SchemaSerializer.SerializeObject(new TestModel { Property = value }); + + private class TestModel + where T : struct, IValues + { + [JsonConverter(typeof(DateTimeToIso8601DateValuesJsonConverter))] + public T Property { get; set; } + } + } +} diff --git a/Tests/Schema.NET.Test/DurationValuesJsonConverterTest.cs b/Tests/Schema.NET.Test/DurationValuesJsonConverterTest.cs new file mode 100644 index 00000000..a4e4ab63 --- /dev/null +++ b/Tests/Schema.NET.Test/DurationValuesJsonConverterTest.cs @@ -0,0 +1,28 @@ +namespace Schema.NET.Test +{ + using System; + using System.Text.Json.Serialization; + using Xunit; + + public class DurationValuesJsonConverterTest + { + [Fact] + public void WriteJson_TimeSpan_ISO8601_Duration() + { + var value = new OneOrMany(new TimeSpan(12, 34, 56)); + var json = SerializeObject(value); + Assert.Equal("{\"Property\":\"PT12H34M56S\"}", json); + } + + private static string SerializeObject(T value) + where T : struct, IValues + => SchemaSerializer.SerializeObject(new TestModel { Property = value }); + + private class TestModel + where T : struct, IValues + { + [JsonConverter(typeof(TimeSpanToISO8601DurationValuesJsonConverter))] + public T Property { get; set; } + } + } +} From c409839fbbad204e44b32fb6796557991af1c536 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Sat, 19 Jun 2021 15:30:49 +0930 Subject: [PATCH 09/30] Update S.T.J to v6.0.0-preview.5 --- Source/Schema.NET/Schema.NET.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Schema.NET/Schema.NET.csproj b/Source/Schema.NET/Schema.NET.csproj index 6a00148a..f27dd1a1 100644 --- a/Source/Schema.NET/Schema.NET.csproj +++ b/Source/Schema.NET/Schema.NET.csproj @@ -14,7 +14,7 @@ - + From e7e79bfdb08e8dc4c9788b9367f1912ad31d4bc9 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Sat, 31 Jul 2021 23:34:46 +0930 Subject: [PATCH 10/30] Switched DataMember to JsonPropertyName --- Source/Common/JsonLdContext.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Common/JsonLdContext.cs b/Source/Common/JsonLdContext.cs index dbeef495..91d77f52 100644 --- a/Source/Common/JsonLdContext.cs +++ b/Source/Common/JsonLdContext.cs @@ -1,7 +1,7 @@ namespace Schema.NET { using System; - using System.Runtime.Serialization; + using System.Text.Json.Serialization; /// /// The @context for a JSON-LD document. @@ -12,13 +12,13 @@ public class JsonLdContext : IEquatable /// /// Gets or sets the name. /// - [DataMember(Name = "name", Order = 0)] + [JsonPropertyName("name")] public string? Name { get; set; } = "https://schema.org"; /// /// Gets or sets the language. /// - [DataMember(Name = "@language", Order = 1)] + [JsonPropertyName("@language")] public string? Language { get; set; } /// From 8a091acd30f4c78c70c29fcb3c89fc8efcfc2f22 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Sat, 31 Jul 2021 23:35:07 +0930 Subject: [PATCH 11/30] Updated to S.T.J Preview 6 --- Source/Schema.NET.Pending/Schema.NET.Pending.csproj | 2 +- Source/Schema.NET/Schema.NET.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Schema.NET.Pending/Schema.NET.Pending.csproj b/Source/Schema.NET.Pending/Schema.NET.Pending.csproj index d93855e8..0ccac54f 100644 --- a/Source/Schema.NET.Pending/Schema.NET.Pending.csproj +++ b/Source/Schema.NET.Pending/Schema.NET.Pending.csproj @@ -14,7 +14,7 @@ - + diff --git a/Source/Schema.NET/Schema.NET.csproj b/Source/Schema.NET/Schema.NET.csproj index f27dd1a1..efe57cf7 100644 --- a/Source/Schema.NET/Schema.NET.csproj +++ b/Source/Schema.NET/Schema.NET.csproj @@ -14,7 +14,7 @@ - + From b442944467d74ea66eebde83f3372299c87985d7 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Thu, 12 Aug 2021 17:06:18 +0930 Subject: [PATCH 12/30] Update to S.T.J Preview 7 --- Source/Schema.NET.Pending/Schema.NET.Pending.csproj | 2 +- Source/Schema.NET/Schema.NET.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Schema.NET.Pending/Schema.NET.Pending.csproj b/Source/Schema.NET.Pending/Schema.NET.Pending.csproj index 0ccac54f..202898a2 100644 --- a/Source/Schema.NET.Pending/Schema.NET.Pending.csproj +++ b/Source/Schema.NET.Pending/Schema.NET.Pending.csproj @@ -14,7 +14,7 @@ - + diff --git a/Source/Schema.NET/Schema.NET.csproj b/Source/Schema.NET/Schema.NET.csproj index efe57cf7..a8b0611f 100644 --- a/Source/Schema.NET/Schema.NET.csproj +++ b/Source/Schema.NET/Schema.NET.csproj @@ -14,7 +14,7 @@ - + From d8466411935fdf974ae3c9f2176048a3c7ba6512 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Thu, 12 Aug 2021 17:13:54 +0930 Subject: [PATCH 13/30] Re-add JSON property order --- Source/Common/JsonLdContext.cs | 2 ++ Source/Common/JsonLdObject.cs | 3 +++ Tools/Schema.NET.Tool/SchemaSourceGenerator.cs | 2 ++ 3 files changed, 7 insertions(+) diff --git a/Source/Common/JsonLdContext.cs b/Source/Common/JsonLdContext.cs index 91d77f52..57c9163b 100644 --- a/Source/Common/JsonLdContext.cs +++ b/Source/Common/JsonLdContext.cs @@ -13,12 +13,14 @@ public class JsonLdContext : IEquatable /// Gets or sets the name. /// [JsonPropertyName("name")] + [JsonPropertyOrder(0)] public string? Name { get; set; } = "https://schema.org"; /// /// Gets or sets the language. /// [JsonPropertyName("@language")] + [JsonPropertyOrder(1)] public string? Language { get; set; } /// diff --git a/Source/Common/JsonLdObject.cs b/Source/Common/JsonLdObject.cs index ade074c5..0e918d10 100644 --- a/Source/Common/JsonLdObject.cs +++ b/Source/Common/JsonLdObject.cs @@ -23,6 +23,7 @@ public class JsonLdObject : IEquatable /// is not a reserved JSON-LD keyword can be used as a term. /// [JsonPropertyName("@context")] + [JsonPropertyOrder(0)] [JsonConverter(typeof(ContextJsonConverter))] public virtual JsonLdContext Context { get; internal set; } = new JsonLdContext(); @@ -31,6 +32,7 @@ public class JsonLdObject : IEquatable /// blank node identifiers. /// [JsonPropertyName("@type")] + [JsonPropertyOrder(1)] public virtual string? Type { get; } /// @@ -42,6 +44,7 @@ public class JsonLdObject : IEquatable /// a node. In JSON-LD, a node is identified using the @id keyword: /// [JsonPropertyName("@id")] + [JsonPropertyOrder(2)] public virtual Uri? Id { get; set; } /// diff --git a/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs b/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs index 40aef69a..e53f9e78 100644 --- a/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs +++ b/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs @@ -123,12 +123,14 @@ public partial interface I{schemaClass.Name}{interfaceImplements} /// 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; }}")} From 04b27ef74aeadb9d54ead78d58ce1edc7ff1e97c Mon Sep 17 00:00:00 2001 From: Turnerj Date: Thu, 12 Aug 2021 17:17:05 +0930 Subject: [PATCH 14/30] Ignore casing for escape test --- Tests/Schema.NET.Test/ThingTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Schema.NET.Test/ThingTest.cs b/Tests/Schema.NET.Test/ThingTest.cs index 889d8cb3..1cfb6f4b 100644 --- a/Tests/Schema.NET.Test/ThingTest.cs +++ b/Tests/Schema.NET.Test/ThingTest.cs @@ -179,7 +179,7 @@ public void ToHtmlEscapedString_UnsafeStringData_ReturnsExpectedJsonLd() Name = "Test", }; - Assert.Equal(expectedJson, thing.ToHtmlEscapedString()); + Assert.Equal(expectedJson, thing.ToHtmlEscapedString(), ignoreCase: true); } [Fact] From ea066db38b0e8ba96e8412e6ac75c66fc2faf379 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Thu, 12 Aug 2021 17:17:20 +0930 Subject: [PATCH 15/30] Allow trailing commas --- Source/Common/SchemaSerializer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Common/SchemaSerializer.cs b/Source/Common/SchemaSerializer.cs index 243a8485..5a300a03 100644 --- a/Source/Common/SchemaSerializer.cs +++ b/Source/Common/SchemaSerializer.cs @@ -32,6 +32,7 @@ static SchemaSerializer() DefaultSerializationSettings = new JsonSerializerOptions { + AllowTrailingCommas = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, }; @@ -39,6 +40,7 @@ static SchemaSerializer() HtmlEscapedSerializationSettings = new JsonSerializerOptions { + AllowTrailingCommas = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, }; HtmlEscapedSerializationSettings.Converters.Add(stringEnumConverter); From 7705f71ed146ac384cc652bde21a660e08dd5e46 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Thu, 12 Aug 2021 17:54:05 +0930 Subject: [PATCH 16/30] Apply serializer settings for deserialization Actually makes the "allow trailing commas" change work --- Source/Common/SchemaSerializer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Common/SchemaSerializer.cs b/Source/Common/SchemaSerializer.cs index 5a300a03..4822ce5f 100644 --- a/Source/Common/SchemaSerializer.cs +++ b/Source/Common/SchemaSerializer.cs @@ -53,7 +53,7 @@ static SchemaSerializer() /// JSON to deserialize /// An instance of deserialized from JSON public static T? DeserializeObject(string value) - => JsonSerializer.Deserialize(value); + => JsonSerializer.Deserialize(value, DefaultSerializationSettings); /// /// Serializes the value to JSON with default serialization settings. From 8e2056405fad9e203757894862675342dae35415 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Fri, 13 Aug 2021 23:18:03 +0930 Subject: [PATCH 17/30] Support EnumMember for schema enum serialization --- Schema.NET.sln | 5 +- Source/Common/EnumHelper.cs | 50 +++++++++++++++ Source/Common/SchemaEnumJsonConverter{T}.cs | 61 +++++++++++++++++++ Source/Common/SchemaSerializer.cs | 4 -- Source/Common/ValuesJsonConverter.cs | 35 +---------- .../Schema.NET.Tool/SchemaSourceGenerator.cs | 2 +- 6 files changed, 117 insertions(+), 40 deletions(-) create mode 100644 Source/Common/SchemaEnumJsonConverter{T}.cs diff --git a/Schema.NET.sln b/Schema.NET.sln index 1b4a12ec..d808502a 100644 --- a/Schema.NET.sln +++ b/Schema.NET.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31423.177 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31515.178 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{719809C2-A551-4C4A-9EFD-B10FB5E35BC0}" ProjectSection(SolutionItems) = preProject @@ -106,6 +106,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{AF228B Source\Common\JsonLdObject.cs = Source\Common\JsonLdObject.cs Source\Common\OneOrMany{T}.cs = Source\Common\OneOrMany{T}.cs Source\Common\PropertyValueSpecification.Partial.cs = Source\Common\PropertyValueSpecification.Partial.cs + Source\Common\SchemaEnumJsonConverter{T}.cs = Source\Common\SchemaEnumJsonConverter{T}.cs Source\Common\SchemaSerializer.cs = Source\Common\SchemaSerializer.cs Source\Common\Thing.Partial.cs = Source\Common\Thing.Partial.cs Source\Common\TimeSpanToISO8601DurationValuesJsonConverter.cs = Source\Common\TimeSpanToISO8601DurationValuesJsonConverter.cs diff --git a/Source/Common/EnumHelper.cs b/Source/Common/EnumHelper.cs index f0e1429c..88b0331a 100644 --- a/Source/Common/EnumHelper.cs +++ b/Source/Common/EnumHelper.cs @@ -1,6 +1,7 @@ namespace Schema.NET { using System; + using System.Diagnostics; using System.Diagnostics.CodeAnalysis; /// @@ -8,6 +9,11 @@ namespace Schema.NET /// internal static class EnumHelper { + private const string HttpSchemaOrgUrl = "http://schema.org/"; + private const int HttpSchemaOrgLength = 18; // equivalent to "http://schema.org/".Length + private const string HttpsSchemaOrgUrl = "https://schema.org/"; + private const int HttpsSchemaOrgLength = 19; // equivalent to "https://schema.org/".Length + /// /// Converts the string representation of the name or numeric value of one or more /// enumerated constants to an equivalent enumerated object. @@ -43,5 +49,49 @@ public static bool TryParse( #pragma warning restore IDE0022 // Use expression body for methods #endif } + + /// + /// Converts the Schema URI representation of the enum type to an equivalent enumerated object. + /// + /// The enum type to use for parsing. + /// The string representation of the name or numeric value of one or more enumerated constants. + /// When this method returns true, an object containing an enumeration constant representing the parsed value. + /// if the conversion succeeded; otherwise. + public static bool TryParseEnumFromSchemaUri( + Type enumType, +#if NETCOREAPP3_1_OR_GREATER + [NotNullWhen(true)] +#endif + string? value, + out object? result) + { + string? enumString; + if (value is not null && value.StartsWith(HttpSchemaOrgUrl, StringComparison.OrdinalIgnoreCase)) + { +#pragma warning disable IDE0057 // Use range operator. Need to multi-target. + enumString = value.Substring(HttpSchemaOrgLength); +#pragma warning restore IDE0057 // Use range operator. Need to multi-target. + } + else if (value is not null && value.StartsWith(HttpsSchemaOrgUrl, StringComparison.OrdinalIgnoreCase)) + { +#pragma warning disable IDE0057 // Use range operator. Need to multi-target. + enumString = value.Substring(HttpsSchemaOrgLength); +#pragma warning restore IDE0057 // Use range operator. Need to multi-target. + } + else + { + enumString = value; + } + + if (TryParse(enumType, enumString, out result)) + { + return true; + } + else + { + Debug.WriteLine($"Unable to parse enumeration of type {enumType.FullName} with value {enumString}."); + return false; + } + } } } diff --git a/Source/Common/SchemaEnumJsonConverter{T}.cs b/Source/Common/SchemaEnumJsonConverter{T}.cs new file mode 100644 index 00000000..e570864a --- /dev/null +++ b/Source/Common/SchemaEnumJsonConverter{T}.cs @@ -0,0 +1,61 @@ +namespace Schema.NET +{ + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Runtime.Serialization; + using System.Text.Json; + using System.Text.Json.Serialization; + + /// + /// Converts a Schema enumeration to and from JSON. + /// + /// The enum type to convert + public class SchemaEnumJsonConverter : JsonConverter + where T : struct, Enum + { + private readonly Dictionary valueNameMap = new(); + + /// + /// Initializes a new instance of the class. + /// + public SchemaEnumJsonConverter() + { + var enumType = typeof(T); + var values = Enum.GetValues(enumType); + + foreach (var value in values) + { + var enumMember = enumType.GetMember(value!.ToString()!)[0]; + var enumMemberAttribute = enumMember.GetCustomAttribute(false); + this.valueNameMap[(T)value] = enumMemberAttribute!.Value!; + } + } + + /// + /// Reads the JSON representation of the enumeration. + /// + /// The to read from. + /// Type of the enumeration. + /// The serializer options. + /// The enumeration value. + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var valueString = reader.GetString(); + if (EnumHelper.TryParseEnumFromSchemaUri(typeToConvert, valueString, out var result)) + { + return (T)(result!); + } + + return default; + } + + /// + /// Writes the specified enumeration using the JSON writer. + /// + /// The JSON writer. + /// The enumeration value. + /// The JSON serializer options. + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => writer.WriteStringValue(this.valueNameMap[value]); + } +} diff --git a/Source/Common/SchemaSerializer.cs b/Source/Common/SchemaSerializer.cs index 4822ce5f..683c87c3 100644 --- a/Source/Common/SchemaSerializer.cs +++ b/Source/Common/SchemaSerializer.cs @@ -28,22 +28,18 @@ public static class SchemaSerializer static SchemaSerializer() #pragma warning restore CA1810 // Initialize reference type static fields inline { - var stringEnumConverter = new JsonStringEnumConverter(); - DefaultSerializationSettings = new JsonSerializerOptions { AllowTrailingCommas = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, }; - DefaultSerializationSettings.Converters.Add(stringEnumConverter); HtmlEscapedSerializationSettings = new JsonSerializerOptions { AllowTrailingCommas = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, }; - HtmlEscapedSerializationSettings.Converters.Add(stringEnumConverter); } /// diff --git a/Source/Common/ValuesJsonConverter.cs b/Source/Common/ValuesJsonConverter.cs index 3f5a94cd..86d09455 100644 --- a/Source/Common/ValuesJsonConverter.cs +++ b/Source/Common/ValuesJsonConverter.cs @@ -16,11 +16,6 @@ namespace Schema.NET /// public class ValuesJsonConverter : JsonConverter { - private const string HttpSchemaOrgUrl = "http://schema.org/"; - private const int HttpSchemaOrgLength = 18; // equivalent to "http://schema.org/".Length - private const string HttpsSchemaOrgUrl = "https://schema.org/"; - private const int HttpsSchemaOrgLength = 19; // equivalent to "https://schema.org/".Length - private static readonly Dictionary BuiltInThingTypeLookup = new(StringComparer.Ordinal); static ValuesJsonConverter() @@ -299,35 +294,9 @@ private static bool TryProcessTokenAsType(ref Utf8JsonReader reader, Type target success = decimal.TryParse(valueString, NumberStyles.Float, CultureInfo.InvariantCulture, out var localResult); result = localResult; } - else if (targetType.GetTypeInfo().IsEnum) + else if (targetType.IsEnum) { - string? enumString; - if (valueString is not null && valueString.StartsWith(HttpSchemaOrgUrl, StringComparison.OrdinalIgnoreCase)) - { -#pragma warning disable IDE0057 // Use range operator. Need to multi-target. - enumString = valueString.Substring(HttpSchemaOrgLength); -#pragma warning restore IDE0057 // Use range operator. Need to multi-target. - } - else if (valueString is not null && valueString.StartsWith(HttpsSchemaOrgUrl, StringComparison.OrdinalIgnoreCase)) - { -#pragma warning disable IDE0057 // Use range operator. Need to multi-target. - enumString = valueString.Substring(HttpsSchemaOrgLength); -#pragma warning restore IDE0057 // Use range operator. Need to multi-target. - } - else - { - enumString = valueString; - } - - if (EnumHelper.TryParse(targetType, enumString, out result)) - { - success = true; - } - else - { - Debug.WriteLine($"Unable to parse enumeration of type {targetType.FullName} with value {enumString}."); - success = false; - } + success = EnumHelper.TryParseEnumFromSchemaUri(targetType, valueString, out result); } else if (targetType == typeof(DateTime)) { diff --git a/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs b/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs index e53f9e78..14143b71 100644 --- a/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs +++ b/Tools/Schema.NET.Tool/SchemaSourceGenerator.cs @@ -248,7 +248,7 @@ private static string RenderEnumeration(GeneratorSchemaEnumeration schemaEnumera /// /// {SourceUtility.RenderDoc(4, schemaEnumeration.Description)} /// - [JsonConverter(typeof(JsonStringEnumConverter))] + [JsonConverter(typeof(SchemaEnumJsonConverter<{schemaEnumeration.Name}>))] public enum {schemaEnumeration.Name} {{{SourceUtility.RenderItems(schemaEnumeration.Values, value => $@" /// From 5f1f2f03340368c2d30cf1c263b69ca19b77983d Mon Sep 17 00:00:00 2001 From: Turnerj Date: Fri, 13 Aug 2021 23:56:41 +0930 Subject: [PATCH 18/30] Decimal and double values to truncate trailing zeroes --- Source/Common/ValuesJsonConverter.cs | 9 +++++++++ Tests/Schema.NET.Test/Examples/JobPostingTest.cs | 2 +- Tests/Schema.NET.Test/Examples/RecipeTest.cs | 2 +- Tests/Schema.NET.Test/Examples/RestaurantTest.cs | 10 +++++----- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Source/Common/ValuesJsonConverter.cs b/Source/Common/ValuesJsonConverter.cs index 86d09455..f0b167cc 100644 --- a/Source/Common/ValuesJsonConverter.cs +++ b/Source/Common/ValuesJsonConverter.cs @@ -175,6 +175,15 @@ public virtual void WriteObject(Utf8JsonWriter writer, object? value, JsonSerial // System.Text.Json won't support timespans as time of day. See https://github.com/dotnet/runtime/issues/29932 writer.WriteStringValue(timeSpan.ToString("c", CultureInfo.InvariantCulture)); } + else if (value is decimal decimalNumber) + { + //TODO: Potential unnecessary allocation - may be able to write to a stackalloc span. + writer.WriteRawValue(decimalNumber.ToString("G0", CultureInfo.InvariantCulture)); + } + else if (value is double doubleNumber) + { + writer.WriteRawValue(doubleNumber.ToString("G0", CultureInfo.InvariantCulture)); + } else { JsonSerializer.Serialize(writer, value, value.GetType(), options); diff --git a/Tests/Schema.NET.Test/Examples/JobPostingTest.cs b/Tests/Schema.NET.Test/Examples/JobPostingTest.cs index e9a2e1af..57752b01 100644 --- a/Tests/Schema.NET.Test/Examples/JobPostingTest.cs +++ b/Tests/Schema.NET.Test/Examples/JobPostingTest.cs @@ -62,7 +62,7 @@ public class JobPostingTest "\"value\":{" + "\"@type\":\"QuantitativeValue\"," + "\"unitText\":\"HOUR\"," + - "\"value\":40.0" + + "\"value\":40" + "}" + "}," + "\"datePosted\":\"2017-01-18\"," + diff --git a/Tests/Schema.NET.Test/Examples/RecipeTest.cs b/Tests/Schema.NET.Test/Examples/RecipeTest.cs index a6ea336f..27a4ac31 100644 --- a/Tests/Schema.NET.Test/Examples/RecipeTest.cs +++ b/Tests/Schema.NET.Test/Examples/RecipeTest.cs @@ -53,7 +53,7 @@ public class RecipeTest "\"image\":\"https://example.com/image.jpg\"," + "\"aggregateRating\":{" + "\"@type\":\"AggregateRating\"," + - "\"ratingValue\":4.0," + + "\"ratingValue\":4," + "\"reviewCount\":35" + "}," + "\"author\":{" + diff --git a/Tests/Schema.NET.Test/Examples/RestaurantTest.cs b/Tests/Schema.NET.Test/Examples/RestaurantTest.cs index f54b1a1d..fd1bce80 100644 --- a/Tests/Schema.NET.Test/Examples/RestaurantTest.cs +++ b/Tests/Schema.NET.Test/Examples/RestaurantTest.cs @@ -78,9 +78,9 @@ public class RestaurantTest "}," + "\"aggregateRating\":{" + "\"@type\":\"AggregateRating\"," + - "\"bestRating\":100.0," + - "\"ratingValue\":88.0," + - "\"worstRating\":1.0," + + "\"bestRating\":100," + + "\"ratingValue\":88," + + "\"worstRating\":1," + "\"ratingCount\":20" + "}," + "\"geo\":{" + @@ -106,9 +106,9 @@ public class RestaurantTest "}," + "\"reviewRating\":{" + "\"@type\":\"Rating\"," + - "\"bestRating\":4.0," + + "\"bestRating\":4," + "\"ratingValue\":3.5," + - "\"worstRating\":1.0" + + "\"worstRating\":1" + "}" + "}," + "\"telephone\":\"+12122459600\"," + From 5886f889fe34e7cd297acc9241d38d96c7cb4849 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Sat, 14 Aug 2021 11:36:23 +0930 Subject: [PATCH 19/30] Fixing build warnings and errors --- Source/Common/SchemaEnumJsonConverter{T}.cs | 15 ++++++++++++++- Source/Common/ValuesJsonConverter.cs | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Source/Common/SchemaEnumJsonConverter{T}.cs b/Source/Common/SchemaEnumJsonConverter{T}.cs index e570864a..3045d9e3 100644 --- a/Source/Common/SchemaEnumJsonConverter{T}.cs +++ b/Source/Common/SchemaEnumJsonConverter{T}.cs @@ -41,6 +41,11 @@ public SchemaEnumJsonConverter() /// The enumeration value. public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + if (typeToConvert is null) + { + throw new ArgumentNullException(nameof(typeToConvert)); + } + var valueString = reader.GetString(); if (EnumHelper.TryParseEnumFromSchemaUri(typeToConvert, valueString, out var result)) { @@ -56,6 +61,14 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial /// The JSON writer. /// The enumeration value. /// The JSON serializer options. - public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => writer.WriteStringValue(this.valueNameMap[value]); + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + writer.WriteStringValue(this.valueNameMap[value]); + } } } diff --git a/Source/Common/ValuesJsonConverter.cs b/Source/Common/ValuesJsonConverter.cs index f0b167cc..22ca0ae7 100644 --- a/Source/Common/ValuesJsonConverter.cs +++ b/Source/Common/ValuesJsonConverter.cs @@ -177,7 +177,7 @@ public virtual void WriteObject(Utf8JsonWriter writer, object? value, JsonSerial } else if (value is decimal decimalNumber) { - //TODO: Potential unnecessary allocation - may be able to write to a stackalloc span. + // TODO: Potential unnecessary allocation - may be able to write to a stackalloc span. writer.WriteRawValue(decimalNumber.ToString("G0", CultureInfo.InvariantCulture)); } else if (value is double doubleNumber) @@ -407,7 +407,7 @@ private static bool TryGetConcreteType( try { var localType = Type.GetType(typeName, false); - if (typeof(IThing).IsAssignableFrom(localType)) + if (localType is not null && typeof(IThing).IsAssignableFrom(localType)) { type = localType; return true; From 99e95702c0ee82a29db1314da54b700e2fe9f33c Mon Sep 17 00:00:00 2001 From: Turnerj Date: Sat, 14 Aug 2021 14:15:18 +0930 Subject: [PATCH 20/30] Force serialization of non-public setter for JsonLdContext --- Source/Common/JsonLdObject.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Common/JsonLdObject.cs b/Source/Common/JsonLdObject.cs index 0e918d10..e5b8871c 100644 --- a/Source/Common/JsonLdObject.cs +++ b/Source/Common/JsonLdObject.cs @@ -25,6 +25,7 @@ public class JsonLdObject : IEquatable [JsonPropertyName("@context")] [JsonPropertyOrder(0)] [JsonConverter(typeof(ContextJsonConverter))] + [JsonInclude] public virtual JsonLdContext Context { get; internal set; } = new JsonLdContext(); /// From 048fedc1859ef2cd9da620b4fe3ece3d181a23bb Mon Sep 17 00:00:00 2001 From: Turnerj Date: Tue, 17 Aug 2021 21:37:48 +0930 Subject: [PATCH 21/30] Addressing major feedback items --- Schema.NET.sln | 1 + Source/Common/Constants.cs | 18 ++++++ Source/Common/ContextJsonConverter.cs | 4 +- Source/Common/EnumHelper.cs | 6 +- Source/Common/JsonLdContext.cs | 2 +- Source/Common/SchemaSerializer.cs | 8 +-- Source/Common/ValuesJsonConverter.cs | 91 ++++++++++++++++++++++----- 7 files changed, 103 insertions(+), 27 deletions(-) create mode 100644 Source/Common/Constants.cs diff --git a/Schema.NET.sln b/Schema.NET.sln index d808502a..3b74d74a 100644 --- a/Schema.NET.sln +++ b/Schema.NET.sln @@ -94,6 +94,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schema.NET.Pending", "Sourc EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{AF228B8A-7290-4E26-8506-934C290C6AE3}" ProjectSection(SolutionItems) = preProject + Source\Common\Constants.cs = Source\Common\Constants.cs Source\Common\ContactType.cs = Source\Common\ContactType.cs Source\Common\ContextJsonConverter.cs = Source\Common\ContextJsonConverter.cs Source\Common\DateTimeHelper.cs = Source\Common\DateTimeHelper.cs diff --git a/Source/Common/Constants.cs b/Source/Common/Constants.cs new file mode 100644 index 00000000..2b789e7a --- /dev/null +++ b/Source/Common/Constants.cs @@ -0,0 +1,18 @@ +namespace Schema.NET +{ + /// + /// Various constants for use within Schema.NET + /// + public static class Constants + { + /// + /// The HTTPS URL for Schema.org + /// + public const string HttpsSchemaOrgUrl = "https://schema.org"; + + /// + /// The HTTPS URL for Schema.org + /// + public const string HttpSchemaOrgUrl = "http://schema.org"; + } +} diff --git a/Source/Common/ContextJsonConverter.cs b/Source/Common/ContextJsonConverter.cs index 711e7207..8ee5cf0c 100644 --- a/Source/Common/ContextJsonConverter.cs +++ b/Source/Common/ContextJsonConverter.cs @@ -41,7 +41,7 @@ public override JsonLdContext Read(ref Utf8JsonReader reader, Type typeToConvert if (document.RootElement.TryGetProperty("name", out var nameElement)) { - name = nameElement.GetString() ?? "http://schema.org"; + name = nameElement.GetString() ?? Constants.HttpsSchemaOrgUrl; } if (document.RootElement.TryGetProperty("@language", out var languageElement)) @@ -74,10 +74,8 @@ public override JsonLdContext Read(ref Utf8JsonReader reader, Type typeToConvert } } -#pragma warning disable CA1062 // Validate arguments of public methods context.Name = name; context.Language = language; -#pragma warning restore CA1062 // Validate arguments of public methods return context; } diff --git a/Source/Common/EnumHelper.cs b/Source/Common/EnumHelper.cs index 88b0331a..93f25db6 100644 --- a/Source/Common/EnumHelper.cs +++ b/Source/Common/EnumHelper.cs @@ -9,9 +9,7 @@ namespace Schema.NET /// internal static class EnumHelper { - private const string HttpSchemaOrgUrl = "http://schema.org/"; private const int HttpSchemaOrgLength = 18; // equivalent to "http://schema.org/".Length - private const string HttpsSchemaOrgUrl = "https://schema.org/"; private const int HttpsSchemaOrgLength = 19; // equivalent to "https://schema.org/".Length /// @@ -66,13 +64,13 @@ public static bool TryParseEnumFromSchemaUri( out object? result) { string? enumString; - if (value is not null && value.StartsWith(HttpSchemaOrgUrl, StringComparison.OrdinalIgnoreCase)) + if (value is not null && value.StartsWith(Constants.HttpSchemaOrgUrl, StringComparison.OrdinalIgnoreCase)) { #pragma warning disable IDE0057 // Use range operator. Need to multi-target. enumString = value.Substring(HttpSchemaOrgLength); #pragma warning restore IDE0057 // Use range operator. Need to multi-target. } - else if (value is not null && value.StartsWith(HttpsSchemaOrgUrl, StringComparison.OrdinalIgnoreCase)) + else if (value is not null && value.StartsWith(Constants.HttpsSchemaOrgUrl, StringComparison.OrdinalIgnoreCase)) { #pragma warning disable IDE0057 // Use range operator. Need to multi-target. enumString = value.Substring(HttpsSchemaOrgLength); diff --git a/Source/Common/JsonLdContext.cs b/Source/Common/JsonLdContext.cs index 57c9163b..74333219 100644 --- a/Source/Common/JsonLdContext.cs +++ b/Source/Common/JsonLdContext.cs @@ -14,7 +14,7 @@ public class JsonLdContext : IEquatable /// [JsonPropertyName("name")] [JsonPropertyOrder(0)] - public string? Name { get; set; } = "https://schema.org"; + public string? Name { get; set; } = Constants.HttpsSchemaOrgUrl; /// /// Gets or sets the language. diff --git a/Source/Common/SchemaSerializer.cs b/Source/Common/SchemaSerializer.cs index 683c87c3..07d71dfe 100644 --- a/Source/Common/SchemaSerializer.cs +++ b/Source/Common/SchemaSerializer.cs @@ -48,8 +48,8 @@ static SchemaSerializer() /// Deserialization target type /// JSON to deserialize /// An instance of deserialized from JSON - public static T? DeserializeObject(string value) - => JsonSerializer.Deserialize(value, DefaultSerializationSettings); + public static T? DeserializeObject(string value) => + JsonSerializer.Deserialize(value, DefaultSerializationSettings); /// /// Serializes the value to JSON with default serialization settings. @@ -73,8 +73,8 @@ public static string HtmlEscapedSerializeObject(object value) => /// Serialization target value /// JSON serialization settings /// The serialized JSON string - public static string SerializeObject(object value, JsonSerializerOptions options) - => RemoveAllButFirstContext(JsonSerializer.Serialize(value, options)); + public static string SerializeObject(object value, JsonSerializerOptions options) => + RemoveAllButFirstContext(JsonSerializer.Serialize(value, options)); private static string RemoveAllButFirstContext(string json) { diff --git a/Source/Common/ValuesJsonConverter.cs b/Source/Common/ValuesJsonConverter.cs index 22ca0ae7..34972575 100644 --- a/Source/Common/ValuesJsonConverter.cs +++ b/Source/Common/ValuesJsonConverter.cs @@ -110,18 +110,13 @@ public override void Write(Utf8JsonWriter writer, IValues value, JsonSerializerO throw new ArgumentNullException(nameof(writer)); } - if (value is null) - { - throw new ArgumentNullException(nameof(value)); - } - if (options is null) { throw new ArgumentNullException(nameof(options)); } #endif - if (value.Count == 0) + if (value is null || value.Count == 0) { writer.WriteNullValue(); } @@ -272,29 +267,52 @@ private static bool TryProcessTokenAsType(ref Utf8JsonReader reader, Type target } else if (targetType.GetTypeInfo().IsPrimitive) { + // Common primitives first if (targetType == typeof(int)) { success = int.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var localResult); result = localResult; } - else if (targetType == typeof(long)) + else if (targetType == typeof(double)) { - success = long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var localResult); + success = double.TryParse(valueString, NumberStyles.Float, CultureInfo.InvariantCulture, out var localResult); result = localResult; } - else if (targetType == typeof(float)) + else if (targetType == typeof(bool)) { - success = float.TryParse(valueString, NumberStyles.Float, CultureInfo.InvariantCulture, out var localResult); + success = bool.TryParse(valueString, out var localResult); result = localResult; } - else if (targetType == typeof(double)) + + // All other supported primitives + else if (targetType == typeof(short)) { - success = double.TryParse(valueString, NumberStyles.Float, CultureInfo.InvariantCulture, out var localResult); + success = short.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var localResult); result = localResult; } - else if (targetType == typeof(bool)) + else if (targetType == typeof(ushort)) { - success = bool.TryParse(valueString, out var localResult); + success = ushort.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var localResult); + result = localResult; + } + else if (targetType == typeof(uint)) + { + success = uint.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var localResult); + result = localResult; + } + else if (targetType == typeof(long)) + { + success = long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var localResult); + result = localResult; + } + else if (targetType == typeof(ulong)) + { + success = ulong.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var localResult); + result = localResult; + } + else if (targetType == typeof(float)) + { + success = float.TryParse(valueString, NumberStyles.Float, CultureInfo.InvariantCulture, out var localResult); result = localResult; } } @@ -372,7 +390,50 @@ private static bool TryProcessTokenAsType(ref Utf8JsonReader reader, Type target } else if (tokenType == JsonTokenType.Number) { - if (targetType == typeof(short) || targetType == typeof(int) || targetType == typeof(long) || targetType == typeof(float) || targetType == typeof(double) || targetType == typeof(decimal)) + // Common number types first + if (targetType == typeof(int)) + { + success = reader.TryGetInt32(out var localResult); + result = localResult; + } + else if (targetType == typeof(double)) + { + success = reader.TryGetDouble(out var localResult); + result = localResult; + } + else if (targetType == typeof(decimal)) + { + success = reader.TryGetDecimal(out var localResult); + result = localResult; + } + + // All other supported number types + else if (targetType == typeof(short)) + { + success = reader.TryGetInt16(out var localResult); + result = localResult; + } + else if (targetType == typeof(ushort)) + { + success = reader.TryGetUInt16(out var localResult); + result = localResult; + } + else if (targetType == typeof(uint)) + { + success = reader.TryGetUInt32(out var localResult); + result = localResult; + } + else if (targetType == typeof(long)) + { + success = reader.TryGetInt64(out var localResult); + result = localResult; + } + else if (targetType == typeof(ulong)) + { + success = reader.TryGetUInt64(out var localResult); + result = localResult; + } + else if (targetType == typeof(float)) { result = Convert.ChangeType(reader.GetDecimal(), targetType, CultureInfo.InvariantCulture); success = true; From 2e75bb3980410e35fd6dff6b36eceaae8257c2c2 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Tue, 17 Aug 2021 21:45:29 +0930 Subject: [PATCH 22/30] Removed unnecessary null-checks The values can be null here --- Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs | 5 ----- .../Common/TimeSpanToISO8601DurationValuesJsonConverter.cs | 5 ----- 2 files changed, 10 deletions(-) diff --git a/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs b/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs index ecc45b86..ae27245c 100644 --- a/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs +++ b/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs @@ -30,11 +30,6 @@ public override void WriteObject(Utf8JsonWriter writer, object? value, JsonSeria throw new ArgumentNullException(nameof(writer)); } - if (value is null) - { - throw new ArgumentNullException(nameof(value)); - } - if (options is null) { throw new ArgumentNullException(nameof(options)); diff --git a/Source/Common/TimeSpanToISO8601DurationValuesJsonConverter.cs b/Source/Common/TimeSpanToISO8601DurationValuesJsonConverter.cs index 5ad03a52..1be4f401 100644 --- a/Source/Common/TimeSpanToISO8601DurationValuesJsonConverter.cs +++ b/Source/Common/TimeSpanToISO8601DurationValuesJsonConverter.cs @@ -31,11 +31,6 @@ public override void WriteObject(Utf8JsonWriter writer, object? value, JsonSeria throw new ArgumentNullException(nameof(writer)); } - if (value is null) - { - throw new ArgumentNullException(nameof(value)); - } - if (options is null) { throw new ArgumentNullException(nameof(options)); From d2dffea9383584b3590b4290651d964b39867b78 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Tue, 24 Aug 2021 18:39:15 +0930 Subject: [PATCH 23/30] Use VS 2022 for solution version --- Schema.NET.sln | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Schema.NET.sln b/Schema.NET.sln index 3b74d74a..f13f63f5 100644 --- a/Schema.NET.sln +++ b/Schema.NET.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31515.178 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31612.314 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{719809C2-A551-4C4A-9EFD-B10FB5E35BC0}" ProjectSection(SolutionItems) = preProject From 1ec52c1589e94c5eb4a8ae5c839fed327884a53f Mon Sep 17 00:00:00 2001 From: Turnerj Date: Mon, 20 Sep 2021 17:14:24 +0930 Subject: [PATCH 24/30] Use RC1 version of System.Text.Json --- Source/Common/ValuesJsonConverter.cs | 12 ++---------- Source/Schema.NET.Pending/Schema.NET.Pending.csproj | 2 +- Source/Schema.NET/Schema.NET.csproj | 2 +- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Source/Common/ValuesJsonConverter.cs b/Source/Common/ValuesJsonConverter.cs index 34972575..2107b06d 100644 --- a/Source/Common/ValuesJsonConverter.cs +++ b/Source/Common/ValuesJsonConverter.cs @@ -201,7 +201,7 @@ public virtual void WriteObject(Utf8JsonWriter writer, object? value, JsonSerial var targetType = targetTypes[i]; if (targetType.IsAssignableFrom(explicitType)) { - return ProcessObject(objectRoot, explicitType!, options); + return objectRoot.Deserialize(explicitType!, options); } } } @@ -224,7 +224,7 @@ public virtual void WriteObject(Utf8JsonWriter writer, object? value, JsonSerial localTargetType = concreteType!; } - return ProcessObject(objectRoot, localTargetType, options); + return objectRoot.Deserialize(localTargetType, options); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) @@ -489,13 +489,5 @@ private static bool TryGetConcreteType( #pragma warning restore CA1031 // Do not catch general exception types } } - - private static object? ProcessObject(JsonElement element, Type objectType, JsonSerializerOptions options) - { - // TODO: Investigate avoiding the string allocation - // Related issue: https://github.com/dotnet/runtime/issues/31274 - var json = element.GetRawText(); - return JsonSerializer.Deserialize(json, objectType ?? typeof(object), options); - } } } diff --git a/Source/Schema.NET.Pending/Schema.NET.Pending.csproj b/Source/Schema.NET.Pending/Schema.NET.Pending.csproj index 202898a2..e0a2ffe8 100644 --- a/Source/Schema.NET.Pending/Schema.NET.Pending.csproj +++ b/Source/Schema.NET.Pending/Schema.NET.Pending.csproj @@ -14,7 +14,7 @@ - + diff --git a/Source/Schema.NET/Schema.NET.csproj b/Source/Schema.NET/Schema.NET.csproj index a8b0611f..a829d104 100644 --- a/Source/Schema.NET/Schema.NET.csproj +++ b/Source/Schema.NET/Schema.NET.csproj @@ -14,7 +14,7 @@ - + From df836cb2378dda35a05ba225b2d40f3e772dcd2d Mon Sep 17 00:00:00 2001 From: Turnerj Date: Mon, 20 Sep 2021 21:12:02 +0930 Subject: [PATCH 25/30] Include prereleases for SDK installs --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 11dfe2bc..82c6a59c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,6 +43,8 @@ jobs: dotnet-version: "5.0.x" - name: "Install .NET Core SDK" uses: actions/setup-dotnet@v1.8.2 + with: + include-prerelease: true - name: "Dotnet Tool Restore" run: dotnet tool restore shell: pwsh From d7ffe2216de4f5ac010da4dcc3b94c1c52d4098c Mon Sep 17 00:00:00 2001 From: Turnerj Date: Tue, 9 Nov 2021 21:33:15 +1030 Subject: [PATCH 26/30] Fix compilation error with null checks --- Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs | 2 +- Source/Common/TimeSpanToISO8601DurationValuesJsonConverter.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs b/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs index ae27245c..38ec019d 100644 --- a/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs +++ b/Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs @@ -23,7 +23,7 @@ public override void WriteObject(Utf8JsonWriter writer, object? value, JsonSeria { #if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(writer); - ArgumentNullException.ThrowIfNull(serializer); + ArgumentNullException.ThrowIfNull(options); #else if (writer is null) { diff --git a/Source/Common/TimeSpanToISO8601DurationValuesJsonConverter.cs b/Source/Common/TimeSpanToISO8601DurationValuesJsonConverter.cs index 1be4f401..55e163ff 100644 --- a/Source/Common/TimeSpanToISO8601DurationValuesJsonConverter.cs +++ b/Source/Common/TimeSpanToISO8601DurationValuesJsonConverter.cs @@ -23,8 +23,7 @@ public override void WriteObject(Utf8JsonWriter writer, object? value, JsonSeria { #if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(writer); - ArgumentNullException.ThrowIfNull(value); - ArgumentNullException.ThrowIfNull(serializer); + ArgumentNullException.ThrowIfNull(options); #else if (writer is null) { From 47a372707c6a8488613d9307a8727c15e6427669 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Tue, 9 Nov 2021 21:33:39 +1030 Subject: [PATCH 27/30] Update S.T.J to final v6.0.0 --- Source/Schema.NET.Pending/Schema.NET.Pending.csproj | 2 +- Source/Schema.NET/Schema.NET.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Schema.NET.Pending/Schema.NET.Pending.csproj b/Source/Schema.NET.Pending/Schema.NET.Pending.csproj index e0a2ffe8..89d261b9 100644 --- a/Source/Schema.NET.Pending/Schema.NET.Pending.csproj +++ b/Source/Schema.NET.Pending/Schema.NET.Pending.csproj @@ -14,7 +14,7 @@ - + diff --git a/Source/Schema.NET/Schema.NET.csproj b/Source/Schema.NET/Schema.NET.csproj index a829d104..4282351c 100644 --- a/Source/Schema.NET/Schema.NET.csproj +++ b/Source/Schema.NET/Schema.NET.csproj @@ -14,7 +14,7 @@ - + From 47433884e858bd73f4f9576bdc8d421973189ea1 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Tue, 9 Nov 2021 22:42:11 +1030 Subject: [PATCH 28/30] Fix constant usage --- Source/Common/ContextJsonConverter.cs | 2 +- Source/Common/EnumHelper.cs | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Source/Common/ContextJsonConverter.cs b/Source/Common/ContextJsonConverter.cs index 8ee5cf0c..d7617e53 100644 --- a/Source/Common/ContextJsonConverter.cs +++ b/Source/Common/ContextJsonConverter.cs @@ -63,7 +63,7 @@ public override JsonLdContext Read(ref Utf8JsonReader reader, Type typeToConvert { if (entry.TryGetProperty("name", out var nameElement)) { - name ??= nameElement.GetString() ?? "http://schema.org"; + name ??= nameElement.GetString() ?? Constants.HttpsSchemaOrgUrl; } if (entry.TryGetProperty("@language", out var languageElement)) diff --git a/Source/Common/EnumHelper.cs b/Source/Common/EnumHelper.cs index 93f25db6..e61fb09f 100644 --- a/Source/Common/EnumHelper.cs +++ b/Source/Common/EnumHelper.cs @@ -9,9 +9,6 @@ namespace Schema.NET /// internal static class EnumHelper { - private const int HttpSchemaOrgLength = 18; // equivalent to "http://schema.org/".Length - private const int HttpsSchemaOrgLength = 19; // equivalent to "https://schema.org/".Length - /// /// Converts the string representation of the name or numeric value of one or more /// enumerated constants to an equivalent enumerated object. @@ -67,13 +64,13 @@ public static bool TryParseEnumFromSchemaUri( if (value is not null && value.StartsWith(Constants.HttpSchemaOrgUrl, StringComparison.OrdinalIgnoreCase)) { #pragma warning disable IDE0057 // Use range operator. Need to multi-target. - enumString = value.Substring(HttpSchemaOrgLength); + enumString = value.Substring(Constants.HttpSchemaOrgUrl.Length); #pragma warning restore IDE0057 // Use range operator. Need to multi-target. } else if (value is not null && value.StartsWith(Constants.HttpsSchemaOrgUrl, StringComparison.OrdinalIgnoreCase)) { #pragma warning disable IDE0057 // Use range operator. Need to multi-target. - enumString = value.Substring(HttpsSchemaOrgLength); + enumString = value.Substring(Constants.HttpsSchemaOrgUrl.Length); #pragma warning restore IDE0057 // Use range operator. Need to multi-target. } else From 017d2cbbef5546236d50ecd16478503a53b87944 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Tue, 9 Nov 2021 22:44:59 +1030 Subject: [PATCH 29/30] Remove pre-release requirement for build --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 82c6a59c..11dfe2bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,8 +43,6 @@ jobs: dotnet-version: "5.0.x" - name: "Install .NET Core SDK" uses: actions/setup-dotnet@v1.8.2 - with: - include-prerelease: true - name: "Dotnet Tool Restore" run: dotnet tool restore shell: pwsh From e7d3ae88ae42fc6f33c2e8c1ee38f8c2a72c2717 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Tue, 9 Nov 2021 22:55:15 +1030 Subject: [PATCH 30/30] Minor bugfix for enum parsing --- Source/Common/Constants.cs | 4 ++-- Source/Common/EnumHelper.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Common/Constants.cs b/Source/Common/Constants.cs index 2b789e7a..77a8032e 100644 --- a/Source/Common/Constants.cs +++ b/Source/Common/Constants.cs @@ -6,12 +6,12 @@ namespace Schema.NET public static class Constants { /// - /// The HTTPS URL for Schema.org + /// The HTTPS URL for Schema.org, excluding trailing slash. /// public const string HttpsSchemaOrgUrl = "https://schema.org"; /// - /// The HTTPS URL for Schema.org + /// The HTTP URL for Schema.org, excluding trailing slash. /// public const string HttpSchemaOrgUrl = "http://schema.org"; } diff --git a/Source/Common/EnumHelper.cs b/Source/Common/EnumHelper.cs index e61fb09f..cdb24a31 100644 --- a/Source/Common/EnumHelper.cs +++ b/Source/Common/EnumHelper.cs @@ -64,13 +64,13 @@ public static bool TryParseEnumFromSchemaUri( if (value is not null && value.StartsWith(Constants.HttpSchemaOrgUrl, StringComparison.OrdinalIgnoreCase)) { #pragma warning disable IDE0057 // Use range operator. Need to multi-target. - enumString = value.Substring(Constants.HttpSchemaOrgUrl.Length); + enumString = value.Substring(Constants.HttpSchemaOrgUrl.Length + 1); #pragma warning restore IDE0057 // Use range operator. Need to multi-target. } else if (value is not null && value.StartsWith(Constants.HttpsSchemaOrgUrl, StringComparison.OrdinalIgnoreCase)) { #pragma warning disable IDE0057 // Use range operator. Need to multi-target. - enumString = value.Substring(Constants.HttpsSchemaOrgUrl.Length); + enumString = value.Substring(Constants.HttpsSchemaOrgUrl.Length + 1); #pragma warning restore IDE0057 // Use range operator. Need to multi-target. } else