Skip to content

Commit 2f222a6

Browse files
authored
Merge pull request #100 from Turnerj/system-text-json-update
Switch from Newtonsoft.Json to System.Text.Json
2 parents 0e20b2f + e7d3ae8 commit 2f222a6

25 files changed

+514
-309
lines changed

Benchmarks/Schema.NET.Benchmarks/SchemaBenchmarkBase.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
namespace Schema.NET.Benchmarks
22
{
33
using System;
4+
using System.Text.Json;
45
using BenchmarkDotNet.Attributes;
56
using BenchmarkDotNet.Jobs;
6-
using Newtonsoft.Json;
77

88
[KeepBenchmarkFiles]
99
[MemoryDiagnoser]
@@ -36,6 +36,6 @@ public virtual void Setup()
3636
public string Serialize() => this.Thing.ToString();
3737

3838
[Benchmark]
39-
public object? Deserialize() => JsonConvert.DeserializeObject(this.SerializedThing, this.ThingType);
39+
public object? Deserialize() => JsonSerializer.Deserialize(this.SerializedThing, this.ThingType);
4040
}
4141
}

Schema.NET.sln

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 17
4-
VisualStudioVersion = 17.0.31423.177
4+
VisualStudioVersion = 17.0.31612.314
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{719809C2-A551-4C4A-9EFD-B10FB5E35BC0}"
77
ProjectSection(SolutionItems) = preProject
@@ -94,6 +94,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schema.NET.Pending", "Sourc
9494
EndProject
9595
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{AF228B8A-7290-4E26-8506-934C290C6AE3}"
9696
ProjectSection(SolutionItems) = preProject
97+
Source\Common\Constants.cs = Source\Common\Constants.cs
9798
Source\Common\ContactType.cs = Source\Common\ContactType.cs
9899
Source\Common\ContextJsonConverter.cs = Source\Common\ContextJsonConverter.cs
99100
Source\Common\DateTimeHelper.cs = Source\Common\DateTimeHelper.cs
@@ -106,6 +107,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{AF228B
106107
Source\Common\JsonLdObject.cs = Source\Common\JsonLdObject.cs
107108
Source\Common\OneOrMany{T}.cs = Source\Common\OneOrMany{T}.cs
108109
Source\Common\PropertyValueSpecification.Partial.cs = Source\Common\PropertyValueSpecification.Partial.cs
110+
Source\Common\SchemaEnumJsonConverter{T}.cs = Source\Common\SchemaEnumJsonConverter{T}.cs
109111
Source\Common\SchemaSerializer.cs = Source\Common\SchemaSerializer.cs
110112
Source\Common\Thing.Partial.cs = Source\Common\Thing.Partial.cs
111113
Source\Common\TimeSpanToISO8601DurationValuesJsonConverter.cs = Source\Common\TimeSpanToISO8601DurationValuesJsonConverter.cs

Source/Common/Constants.cs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Schema.NET
2+
{
3+
/// <summary>
4+
/// Various constants for use within Schema.NET
5+
/// </summary>
6+
public static class Constants
7+
{
8+
/// <summary>
9+
/// The HTTPS URL for Schema.org, excluding trailing slash.
10+
/// </summary>
11+
public const string HttpsSchemaOrgUrl = "https://schema.org";
12+
13+
/// <summary>
14+
/// The HTTP URL for Schema.org, excluding trailing slash.
15+
/// </summary>
16+
public const string HttpSchemaOrgUrl = "http://schema.org";
17+
}
18+
}

Source/Common/ContextJsonConverter.cs

+45-60
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
namespace Schema.NET
22
{
33
using System;
4-
using Newtonsoft.Json;
5-
using Newtonsoft.Json.Linq;
4+
using System.Text.Json;
5+
using System.Text.Json.Serialization;
66

77
/// <summary>
88
/// Converts a <see cref="JsonLdContext"/> object to and from JSON.
@@ -11,96 +11,81 @@ namespace Schema.NET
1111
public class ContextJsonConverter : JsonConverter<JsonLdContext>
1212
{
1313
/// <inheritdoc />
14-
public override JsonLdContext ReadJson(JsonReader reader, Type objectType, JsonLdContext? existingValue, bool hasExistingValue, JsonSerializer serializer)
14+
public override JsonLdContext Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
1515
{
1616
#if NET6_0_OR_GREATER
17-
ArgumentNullException.ThrowIfNull(reader);
18-
ArgumentNullException.ThrowIfNull(objectType);
19-
if (hasExistingValue)
20-
{
21-
ArgumentNullException.ThrowIfNull(existingValue);
22-
}
23-
24-
ArgumentNullException.ThrowIfNull(serializer);
17+
ArgumentNullException.ThrowIfNull(typeToConvert);
18+
ArgumentNullException.ThrowIfNull(options);
2519
#else
26-
if (reader is null)
27-
{
28-
throw new ArgumentNullException(nameof(reader));
29-
}
30-
31-
if (objectType is null)
32-
{
33-
throw new ArgumentNullException(nameof(objectType));
34-
}
35-
36-
if (hasExistingValue && existingValue is null)
20+
if (typeToConvert is null)
3721
{
38-
throw new ArgumentNullException(nameof(existingValue));
22+
throw new ArgumentNullException(nameof(typeToConvert));
3923
}
4024

41-
if (serializer is null)
25+
if (options is null)
4226
{
43-
throw new ArgumentNullException(nameof(serializer));
27+
throw new ArgumentNullException(nameof(options));
4428
}
4529
#endif
30+
var context = new JsonLdContext();
4631

47-
var context = hasExistingValue ? existingValue! : new JsonLdContext();
48-
49-
string? name;
50-
string? language;
51-
if (reader.TokenType == JsonToken.String)
32+
string? name = null;
33+
string? language = null;
34+
if (reader.TokenType == JsonTokenType.String)
5235
{
53-
name = (string?)reader.Value;
54-
language = null;
36+
name = reader.GetString();
5537
}
56-
else if (reader.TokenType == JsonToken.StartObject)
38+
else if (reader.TokenType == JsonTokenType.StartObject)
5739
{
58-
var o = JObject.Load(reader);
40+
var document = JsonDocument.ParseValue(ref reader);
5941

60-
var nameProperty = o.Property("name", StringComparison.OrdinalIgnoreCase);
61-
name = nameProperty?.Value?.ToString() ?? "https://schema.org";
42+
if (document.RootElement.TryGetProperty("name", out var nameElement))
43+
{
44+
name = nameElement.GetString() ?? Constants.HttpsSchemaOrgUrl;
45+
}
6246

63-
var languageProperty = o.Property("@language", StringComparison.OrdinalIgnoreCase);
64-
language = languageProperty?.Value?.ToString();
47+
if (document.RootElement.TryGetProperty("@language", out var languageElement))
48+
{
49+
language = languageElement.GetString();
50+
}
6551
}
6652
else
6753
{
68-
var a = JArray.Load(reader);
54+
var array = JsonDocument.ParseValue(ref reader).RootElement.EnumerateArray();
6955

70-
name = language = null;
71-
foreach (var entry in a)
56+
foreach (var entry in array)
7257
{
73-
if (entry.Type == JTokenType.String)
58+
if (entry.ValueKind == JsonValueKind.String)
7459
{
75-
name ??= (string?)entry;
60+
name ??= entry.GetString();
7661
}
77-
else
62+
else if (entry.ValueKind == JsonValueKind.Object)
7863
{
79-
var o = (JObject)entry;
80-
81-
var nameProperty = o.Property("name", StringComparison.OrdinalIgnoreCase);
82-
name ??= nameProperty?.Value?.ToString() ?? "https://schema.org";
83-
84-
var languageProperty = o.Property("@language", StringComparison.OrdinalIgnoreCase);
85-
language ??= languageProperty?.Value?.ToString();
64+
if (entry.TryGetProperty("name", out var nameElement))
65+
{
66+
name ??= nameElement.GetString() ?? Constants.HttpsSchemaOrgUrl;
67+
}
68+
69+
if (entry.TryGetProperty("@language", out var languageElement))
70+
{
71+
language ??= languageElement.GetString();
72+
}
8673
}
8774
}
8875
}
8976

90-
#pragma warning disable CA1062 // Validate arguments of public methods
9177
context.Name = name;
9278
context.Language = language;
93-
#pragma warning restore CA1062 // Validate arguments of public methods
9479
return context;
9580
}
9681

9782
/// <inheritdoc />
98-
public override void WriteJson(JsonWriter writer, JsonLdContext? value, JsonSerializer serializer)
83+
public override void Write(Utf8JsonWriter writer, JsonLdContext value, JsonSerializerOptions options)
9984
{
10085
#if NET6_0_OR_GREATER
10186
ArgumentNullException.ThrowIfNull(writer);
10287
ArgumentNullException.ThrowIfNull(value);
103-
ArgumentNullException.ThrowIfNull(serializer);
88+
ArgumentNullException.ThrowIfNull(options);
10489
#else
10590
if (writer is null)
10691
{
@@ -112,23 +97,23 @@ public override void WriteJson(JsonWriter writer, JsonLdContext? value, JsonSeri
11297
throw new ArgumentNullException(nameof(value));
11398
}
11499

115-
if (serializer is null)
100+
if (options is null)
116101
{
117-
throw new ArgumentNullException(nameof(serializer));
102+
throw new ArgumentNullException(nameof(options));
118103
}
119104
#endif
120105

121106
if (string.IsNullOrWhiteSpace(value.Language))
122107
{
123-
writer.WriteValue(value.Name);
108+
writer.WriteStringValue(value.Name);
124109
}
125110
else
126111
{
127112
writer.WriteStartObject();
128113
writer.WritePropertyName("name");
129-
writer.WriteValue(value.Name);
114+
writer.WriteStringValue(value.Name);
130115
writer.WritePropertyName("@language");
131-
writer.WriteValue(value.Language);
116+
writer.WriteStringValue(value.Language);
132117
writer.WriteEndObject();
133118
}
134119
}

Source/Common/DateTimeToIso8601DateValuesJsonConverter.cs

+9-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ namespace Schema.NET
22
{
33
using System;
44
using System.Globalization;
5-
using Newtonsoft.Json;
5+
using System.Text.Json;
6+
using System.Text.Json.Serialization;
67

78
/// <summary>
89
/// Converts an <see cref="IValues"/> object to JSON. If the <see cref="IValues"/> contains a
@@ -17,31 +18,31 @@ public class DateTimeToIso8601DateValuesJsonConverter : ValuesJsonConverter
1718
/// </summary>
1819
/// <param name="writer">The JSON writer.</param>
1920
/// <param name="value">The value to write.</param>
20-
/// <param name="serializer">The JSON serializer.</param>
21-
public override void WriteObject(JsonWriter writer, object? value, JsonSerializer serializer)
21+
/// <param name="options">The JSON serializer options.</param>
22+
public override void WriteObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options)
2223
{
2324
#if NET6_0_OR_GREATER
2425
ArgumentNullException.ThrowIfNull(writer);
25-
ArgumentNullException.ThrowIfNull(serializer);
26+
ArgumentNullException.ThrowIfNull(options);
2627
#else
2728
if (writer is null)
2829
{
2930
throw new ArgumentNullException(nameof(writer));
3031
}
3132

32-
if (serializer is null)
33+
if (options is null)
3334
{
34-
throw new ArgumentNullException(nameof(serializer));
35+
throw new ArgumentNullException(nameof(options));
3536
}
3637
#endif
3738

3839
if (value is DateTime dateTimeType)
3940
{
40-
writer.WriteValue(dateTimeType.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
41+
writer.WriteStringValue(dateTimeType.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
4142
}
4243
else
4344
{
44-
base.WriteObject(writer, value, serializer);
45+
base.WriteObject(writer, value, options);
4546
}
4647
}
4748
}

Source/Common/EnumHelper.cs

+45
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace Schema.NET
22
{
33
using System;
4+
using System.Diagnostics;
45
using System.Diagnostics.CodeAnalysis;
56

67
/// <summary>
@@ -43,5 +44,49 @@ public static bool TryParse(
4344
#pragma warning restore IDE0022 // Use expression body for methods
4445
#endif
4546
}
47+
48+
/// <summary>
49+
/// Converts the Schema URI representation of the enum type to an equivalent enumerated object.
50+
/// </summary>
51+
/// <param name="enumType">The enum type to use for parsing.</param>
52+
/// <param name="value">The string representation of the name or numeric value of one or more enumerated constants.</param>
53+
/// <param name="result">When this method returns true, an object containing an enumeration constant representing the parsed value.</param>
54+
/// <returns><see langword="true"/> if the conversion succeeded; <see langword="false"/> otherwise.</returns>
55+
public static bool TryParseEnumFromSchemaUri(
56+
Type enumType,
57+
#if NETCOREAPP3_1_OR_GREATER
58+
[NotNullWhen(true)]
59+
#endif
60+
string? value,
61+
out object? result)
62+
{
63+
string? enumString;
64+
if (value is not null && value.StartsWith(Constants.HttpSchemaOrgUrl, StringComparison.OrdinalIgnoreCase))
65+
{
66+
#pragma warning disable IDE0057 // Use range operator. Need to multi-target.
67+
enumString = value.Substring(Constants.HttpSchemaOrgUrl.Length + 1);
68+
#pragma warning restore IDE0057 // Use range operator. Need to multi-target.
69+
}
70+
else if (value is not null && value.StartsWith(Constants.HttpsSchemaOrgUrl, StringComparison.OrdinalIgnoreCase))
71+
{
72+
#pragma warning disable IDE0057 // Use range operator. Need to multi-target.
73+
enumString = value.Substring(Constants.HttpsSchemaOrgUrl.Length + 1);
74+
#pragma warning restore IDE0057 // Use range operator. Need to multi-target.
75+
}
76+
else
77+
{
78+
enumString = value;
79+
}
80+
81+
if (TryParse(enumType, enumString, out result))
82+
{
83+
return true;
84+
}
85+
else
86+
{
87+
Debug.WriteLine($"Unable to parse enumeration of type {enumType.FullName} with value {enumString}.");
88+
return false;
89+
}
90+
}
4691
}
4792
}

Source/Common/JsonLdContext.cs

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
namespace Schema.NET
22
{
33
using System;
4-
using System.Runtime.Serialization;
4+
using System.Text.Json.Serialization;
55

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

1819
/// <summary>
1920
/// Gets or sets the language.
2021
/// </summary>
21-
[DataMember(Name = "@language", Order = 1)]
22+
[JsonPropertyName("@language")]
23+
[JsonPropertyOrder(1)]
2224
public string? Language { get; set; }
2325

2426
/// <summary>

0 commit comments

Comments
 (0)