Skip to content

Switch to System.Text.Json #100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e20a62d
Add core System.Text.Json support
Turnerj Mar 22, 2021
60dc367
Updated benchmarks to use S.T.J
Turnerj Mar 22, 2021
2bf322e
Updated tests to use S.T.J
Turnerj Mar 22, 2021
49cf046
Use 6.0.0-preview.2 of S.T.J
Turnerj Mar 22, 2021
fefb1aa
Use correct attribute for tests
Turnerj Mar 22, 2021
e0699e6
Removed unused using
Turnerj Mar 22, 2021
78a860d
Fixed support for time of day
Turnerj Mar 22, 2021
57ed1cf
Added tests for extended type converters
Turnerj Mar 22, 2021
c409839
Update S.T.J to v6.0.0-preview.5
Turnerj Jun 19, 2021
e7e79bf
Switched DataMember to JsonPropertyName
Turnerj Jul 31, 2021
8a091ac
Updated to S.T.J Preview 6
Turnerj Jul 31, 2021
b442944
Update to S.T.J Preview 7
Turnerj Aug 12, 2021
d846641
Re-add JSON property order
Turnerj Aug 12, 2021
04b27ef
Ignore casing for escape test
Turnerj Aug 12, 2021
ea066db
Allow trailing commas
Turnerj Aug 12, 2021
7705f71
Apply serializer settings for deserialization
Turnerj Aug 12, 2021
8e20564
Support EnumMember for schema enum serialization
Turnerj Aug 13, 2021
5f1f2f0
Decimal and double values to truncate trailing zeroes
Turnerj Aug 13, 2021
5886f88
Fixing build warnings and errors
Turnerj Aug 14, 2021
99e9570
Force serialization of non-public setter for JsonLdContext
Turnerj Aug 14, 2021
048fedc
Addressing major feedback items
Turnerj Aug 17, 2021
2e75bb3
Removed unnecessary null-checks
Turnerj Aug 17, 2021
d2dffea
Use VS 2022 for solution version
Turnerj Aug 24, 2021
1ec52c1
Use RC1 version of System.Text.Json
Turnerj Sep 20, 2021
df836cb
Include prereleases for SDK installs
Turnerj Sep 20, 2021
d7ffe22
Fix compilation error with null checks
Turnerj Nov 9, 2021
47a3727
Update S.T.J to final v6.0.0
Turnerj Nov 9, 2021
4743388
Fix constant usage
Turnerj Nov 9, 2021
017d2cb
Remove pre-release requirement for build
Turnerj Nov 9, 2021
e7d3ae8
Minor bugfix for enum parsing
Turnerj Nov 9, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Benchmarks/Schema.NET.Benchmarks/SchemaBenchmarkBase.cs
Original file line number Diff line number Diff line change
@@ -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]
Expand Down Expand Up @@ -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);
}
}
4 changes: 3 additions & 1 deletion Schema.NET.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31423.177
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
Expand Down Expand Up @@ -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
Expand All @@ -106,6 +107,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
Expand Down
18 changes: 18 additions & 0 deletions Source/Common/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Schema.NET
{
/// <summary>
/// Various constants for use within Schema.NET
/// </summary>
public static class Constants
{
/// <summary>
/// The HTTPS URL for Schema.org
/// </summary>
public const string HttpsSchemaOrgUrl = "https://schema.org";

/// <summary>
/// The HTTPS URL for Schema.org
/// </summary>
public const string HttpSchemaOrgUrl = "http://schema.org";
}
}
105 changes: 45 additions & 60 deletions Source/Common/ContextJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Converts a <see cref="JsonLdContext"/> object to and from JSON.
Expand All @@ -11,96 +11,81 @@ namespace Schema.NET
public class ContextJsonConverter : JsonConverter<JsonLdContext>
{
/// <inheritdoc />
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() ?? Constants.HttpsSchemaOrgUrl;
}

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();
}
}
}
}

#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;
}

/// <inheritdoc />
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)
{
Expand All @@ -112,23 +97,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();
}
}
Expand Down
17 changes: 9 additions & 8 deletions Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ namespace Schema.NET
{
using System;
using System.Globalization;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;

/// <summary>
/// Converts an <see cref="IValues"/> object to JSON. If the <see cref="IValues"/> contains a
Expand All @@ -17,31 +18,31 @@ public class DateTimeToIso8601DateValuesJsonConverter : ValuesJsonConverter
/// </summary>
/// <param name="writer">The JSON writer.</param>
/// <param name="value">The value to write.</param>
/// <param name="serializer">The JSON serializer.</param>
public override void WriteObject(JsonWriter writer, object? value, JsonSerializer serializer)
/// <param name="options">The JSON serializer options.</param>
public override 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

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);
}
}
}
Expand Down
48 changes: 48 additions & 0 deletions Source/Common/EnumHelper.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
namespace Schema.NET
{
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

/// <summary>
/// Helper for parsing strings into Enum values.
/// </summary>
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

/// <summary>
/// Converts the string representation of the name or numeric value of one or more
/// enumerated constants to an equivalent enumerated object.
Expand Down Expand Up @@ -43,5 +47,49 @@ public static bool TryParse(
#pragma warning restore IDE0022 // Use expression body for methods
#endif
}

/// <summary>
/// Converts the Schema URI representation of the enum type to an equivalent enumerated object.
/// </summary>
/// <param name="enumType">The enum type to use for parsing.</param>
/// <param name="value">The string representation of the name or numeric value of one or more enumerated constants.</param>
/// <param name="result">When this method returns true, an object containing an enumeration constant representing the parsed value.</param>
/// <returns><see langword="true"/> if the conversion succeeded; <see langword="false"/> otherwise.</returns>
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(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(Constants.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;
}
}
}
}
10 changes: 6 additions & 4 deletions Source/Common/JsonLdContext.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Schema.NET
{
using System;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;

/// <summary>
/// The @context for a JSON-LD document.
Expand All @@ -12,13 +12,15 @@ public class JsonLdContext : IEquatable<JsonLdContext>
/// <summary>
/// Gets or sets the name.
/// </summary>
[DataMember(Name = "name", Order = 0)]
public string? Name { get; set; } = "https://schema.org";
[JsonPropertyName("name")]
[JsonPropertyOrder(0)]
public string? Name { get; set; } = Constants.HttpsSchemaOrgUrl;

/// <summary>
/// Gets or sets the language.
/// </summary>
[DataMember(Name = "@language", Order = 1)]
[JsonPropertyName("@language")]
[JsonPropertyOrder(1)]
public string? Language { get; set; }

/// <summary>
Expand Down
Loading