Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Add core System.Text.Json support
  • Loading branch information
Turnerj committed Nov 9, 2021
commit e20a62d706b0ee23e0e42fe725f342add15bb618
103 changes: 45 additions & 58 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,78 +11,65 @@ 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() ?? "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();
}
}
}
}
Expand All @@ -95,12 +82,12 @@ public override JsonLdContext ReadJson(JsonReader reader, Type objectType, JsonL
}

/// <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 +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();
}
}
Expand Down
20 changes: 13 additions & 7 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,8 +18,8 @@ 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);
Expand All @@ -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);
}
}
}
Expand Down
10 changes: 4 additions & 6 deletions Source/Common/JsonLdObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// The base JSON-LD object.
/// See https://json-ld.org/spec/latest/json-ld
/// </summary>
[DataContract]
public class JsonLdObject : IEquatable<JsonLdObject>
{
/// <summary>
Expand All @@ -24,15 +22,15 @@ public class JsonLdObject : IEquatable<JsonLdObject>
/// 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.
/// </summary>
[DataMember(Name = "@context", Order = 0)]
[JsonPropertyName("@context")]
[JsonConverter(typeof(ContextJsonConverter))]
public virtual JsonLdContext Context { get; internal set; } = new JsonLdContext();

/// <summary>
/// Gets the type, used to uniquely identify things that are being described in the document with IRIs or
/// blank node identifiers.
/// </summary>
[DataMember(Name = "@type", Order = 1)]
[JsonPropertyName("@type")]
public virtual string? Type { get; }

/// <summary>
Expand All @@ -43,7 +41,7 @@ public class JsonLdObject : IEquatable<JsonLdObject>
/// 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:
/// </summary>
[DataMember(Name = "@id", Order = 2)]
[JsonPropertyName("@id")]
public virtual Uri? Id { get; set; }

/// <summary>
Expand Down
59 changes: 27 additions & 32 deletions Source/Common/SchemaSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// Schema JSON Serializer
Expand All @@ -13,50 +13,45 @@ public static class SchemaSerializer
{
private const string ContextPropertyJson = "\"@context\":\"https://schema.org\",";

/// <summary>
/// Default serializer settings used when deserializing
/// </summary>
private static readonly JsonSerializerSettings DeserializeSettings = new()
{
DateParseHandling = DateParseHandling.None,
};

/// <summary>
/// Default serializer settings used when HTML escaping is not required.
/// </summary>
private static readonly JsonSerializerSettings DefaultSerializationSettings = new()
{
Converters = new List<JsonConverter>()
{
new StringEnumConverter(),
},
DefaultValueHandling = DefaultValueHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
};
private static readonly JsonSerializerOptions DefaultSerializationSettings;

/// <summary>
/// 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.
/// </summary>
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<JsonConverter>()
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);
}

/// <summary>
/// Deserializes the JSON to the specified type.
/// </summary>
/// <typeparam name="T">Deserialization target type</typeparam>
/// <param name="value">JSON to deserialize</param>
/// <returns>An instance of <typeparamref name="T"/> deserialized from JSON</returns>
public static T? DeserializeObject<T>(string value) =>
JsonConvert.DeserializeObject<T>(value, DeserializeSettings);
public static T? DeserializeObject<T>(string value)
=> JsonSerializer.Deserialize<T>(value);

/// <summary>
/// Serializes the value to JSON with default serialization settings.
Expand All @@ -78,10 +73,10 @@ public static string HtmlEscapedSerializeObject(object value) =>
/// Serializes the value to JSON with custom serialization settings.
/// </summary>
/// <param name="value">Serialization target value</param>
/// <param name="jsonSerializerSettings">JSON serialization settings</param>
/// <param name="options">JSON serialization settings</param>
/// <returns>The serialized JSON string</returns>
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)
{
Expand Down
15 changes: 5 additions & 10 deletions Source/Common/Thing.Partial.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Schema.NET
{
using Newtonsoft.Json;
using System.Text.Json;

/// <summary>
/// The most generic type of item.
Expand Down Expand Up @@ -37,18 +37,13 @@ public partial class Thing : JsonLdObject
public string ToHtmlEscapedString() => SchemaSerializer.HtmlEscapedSerializeObject(this);

/// <summary>
/// Returns the JSON-LD representation of this instance using the <see cref="JsonSerializerSettings"/> provided.
///
/// Caution: You should ensure your <paramref name="serializerSettings"/> has
/// <see cref="JsonSerializerSettings.StringEscapeHandling"/> set to <see cref="StringEscapeHandling.EscapeHtml"/>
/// 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 <see cref="JsonSerializerOptions"/> provided.
/// </summary>
/// <param name="serializerSettings">Serialization settings.</param>
/// <param name="options">Serialization settings.</param>
/// <returns>
/// A <see cref="string" /> that represents the JSON-LD representation of this instance.
/// </returns>
public string ToString(JsonSerializerSettings serializerSettings) =>
SchemaSerializer.SerializeObject(this, serializerSettings);
public string ToString(JsonSerializerOptions options) =>
SchemaSerializer.SerializeObject(this, options);
}
}
Loading