Skip to content
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

feat: Add System.Text.Json serialization/deserialization supports #10217

Merged
merged 25 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
fda9353
chore: refactoring JsonUtility code to support newtonsoftjson/systemt…
filzrev Sep 12, 2024
92a47a6
chore: add json/yaml serialization test infrastructure
filzrev Sep 14, 2024
643c89b
chore: switch XRefMap serialization logics to use System.Text.Json
filzrev Sep 14, 2024
2fa823a
chore: add json roundtrip test with JsonUtility
filzrev Sep 26, 2024
7b3dd78
chore: Add MarkdigExtensionSetting json roundtrip tests
filzrev Sep 26, 2024
9492ef8
chore: add ManagedReference YAML roundtrip tests
filzrev Sep 26, 2024
12c7394
chore: add UniversalReference YAML roundtrip tests
filzrev Sep 28, 2024
448c933
chore: add ApiPage YAML roundtrip tests
filzrev Sep 30, 2024
a9fde45
chore: add FilterConfig YAML roundtrip tests
filzrev Oct 1, 2024
9c13117
chore: Add Manifest model json roundtrip tests
filzrev Oct 1, 2024
9466b91
chore: change JavascriptEncoder settings and add related tests
filzrev Oct 4, 2024
da94915
chore: add TOC model YAML roundtrip tests
filzrev Oct 4, 2024
d101fdc
chore: add `dummy property name` for deserialize `metadata` key data
filzrev Oct 4, 2024
7b06344
chore: add FileMapping/FileItems converters and JSON roundtrip tests
filzrev Oct 5, 2024
8eaed9f
chore: disable `MarkdigExtensionSettingConverter` JSON formatting
filzrev Oct 5, 2024
948052a
chore: add ListWithStringFallback converters and JSON roundtrip test
filzrev Oct 5, 2024
6eb819c
chore: add FileMetadataPairs converters and JSON roundtrip test
filzrev Oct 6, 2024
b8e4d85
chore: add MergeJsonConfig converters and JSON roundtrip test
filzrev Oct 11, 2024
80d10a7
chore: add BuildJsonConfig JSON roundtrip test
filzrev Oct 11, 2024
c62a025
chore: add MetadataJsonConfig JSON roundtrip test
filzrev Oct 11, 2024
28c55aa
chore: add DocfxConfig JSON roundtrip test
filzrev Oct 11, 2024
850625c
chore: Switch to use SystemTextJson by default and fix related tests
filzrev Oct 12, 2024
f89752a
chore: modify converter to throw JsonException if unexpected token found
filzrev Oct 13, 2024
7e1670a
Merge remote-tracking branch 'upstream/main' into feature/add-system-…
filzrev Oct 19, 2024
3cbc225
chore: merge changes of FileMetadataPairsConverter.cs
filzrev Oct 19, 2024
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
7 changes: 4 additions & 3 deletions src/Docfx.App/Config/FileMetadataPairs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ namespace Docfx;
/// </summary>
/// <see cref="BuildJsonConfig.FileMetadata"/>
/// <see cref="MergeJsonItemConfig.FileMetadata"/>
[Newtonsoft.Json.JsonConverter(typeof(FileMetadataPairsConverter))]
[Newtonsoft.Json.JsonConverter(typeof(FileMetadataPairsConverter.NewtonsoftJsonConverter))]
[System.Text.Json.Serialization.JsonConverter(typeof(FileMetadataPairsConverter.SystemTextJsonConverter))]
internal class FileMetadataPairs
{
// Order matters, the latter one overrides the former one
Expand All @@ -28,9 +29,9 @@ public IReadOnlyList<FileMetadataPairsItem> Items
/// <summary>
/// Initializes a new instance of the <see cref="FileMetadataPairs"/> class.
/// </summary>
public FileMetadataPairs(List<FileMetadataPairsItem> items)
public FileMetadataPairs(IEnumerable<FileMetadataPairsItem> items)
{
_items = items;
_items = items.ToList();
}

/// <summary>
Expand Down
63 changes: 63 additions & 0 deletions src/Docfx.App/Config/FileMetadataPairsConverter.NewtonsoftJson.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Docfx.Common;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Docfx;

internal partial class FileMetadataPairsConverter
{
/// <summary>
/// JsonConverter for FileMetadataPairs
/// </summary>
internal class NewtonsoftJsonConverter : JsonConverter
{
/// <inheritdoc/>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(FileMetadataPairs);
}

/// <inheritdoc/>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
IEnumerable<JToken> jItems;
if (reader.TokenType == JsonToken.StartObject)
{
jItems = JContainer.Load(reader);
}
else throw new JsonReaderException($"{reader.TokenType} is not a valid {objectType.Name}.");
return new FileMetadataPairs(jItems.Select(ParseItem).ToList());
}

/// <inheritdoc/>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
foreach (var item in ((FileMetadataPairs)value).Items)
{
writer.WritePropertyName(item.Glob.Raw);
writer.WriteRawValue(JsonUtility.Serialize(item.Value));
}
writer.WriteEndObject();
}

private static FileMetadataPairsItem ParseItem(JToken item)
{
if (item.Type == JTokenType.Property)
{
JProperty jProperty = item as JProperty;
var pattern = jProperty.Name;
var rawValue = jProperty.Value;
return new FileMetadataPairsItem(pattern, rawValue);
}
else
{
throw new JsonReaderException($"Unsupported value {item} (type: {item.Type}).");
}
}
}
}
78 changes: 78 additions & 0 deletions src/Docfx.App/Config/FileMetadataPairsConverter.SystemTextJson.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Docfx.Common;

#nullable enable

namespace Docfx;

internal partial class FileMetadataPairsConverter
{
/// <summary>
/// JsonConverter for FileMetadataPairs
/// </summary>
internal class SystemTextJsonConverter : JsonConverter<FileMetadataPairs>
{
public override FileMetadataPairs Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException($"{reader.TokenType} is not a valid {typeToConvert.Name}.");
}

using var document = JsonDocument.ParseValue(ref reader);
var properties = document.RootElement.EnumerateObject();
var items = properties.Select(x => new FileMetadataPairsItem(x.Name, ToInferredType(x.Value))).ToArray();
return new FileMetadataPairs(items);
}

public override void Write(Utf8JsonWriter writer, FileMetadataPairs value, JsonSerializerOptions options)
{
writer.WriteStartObject();
foreach (var item in value.Items)
{
writer.WritePropertyName(item.Glob.Raw);
writer.WriteRawValue(JsonUtility.Serialize(item.Value));
}
writer.WriteEndObject();
}

/// <summary>
/// Convert JsonElement to .NET object.
/// </summary>
private static object? ToInferredType(JsonElement elem)
{
switch (elem.ValueKind)
{
case JsonValueKind.Null:
return null;
case JsonValueKind.True:
return true;
case JsonValueKind.False:
return false;
case JsonValueKind.String when elem.TryGetDateTime(out DateTime datetime):
return datetime;
case JsonValueKind.String:
return elem.GetString();
case JsonValueKind.Array:
return elem.EnumerateArray().Select(ToInferredType).ToArray();
case JsonValueKind.Object:
var properties = elem.EnumerateObject();
return properties.ToDictionary(x => x.Name, x => ToInferredType(x.Value));
case JsonValueKind.Number when elem.TryGetInt32(out int intValue):
return intValue;
case JsonValueKind.Number when elem.TryGetInt64(out long longValue):
return longValue;
case JsonValueKind.Number:
return elem.GetDouble();
case JsonValueKind.Undefined:
default:
throw new JsonException($"JsonValueKind({elem.ValueKind}) is not supported.");
}
}
}
}
46 changes: 1 addition & 45 deletions src/Docfx.App/Config/FileMetadataPairsConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,50 +11,6 @@ namespace Docfx;
/// <summary>
/// JsonConverter for FileMetadataPairs
/// </summary>
internal class FileMetadataPairsConverter : JsonConverter
internal partial class FileMetadataPairsConverter
{
/// <inheritdoc/>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(FileMetadataPairs);
}

/// <inheritdoc/>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
IEnumerable<JToken> jItems;
if (reader.TokenType == JsonToken.StartObject)
{
jItems = JContainer.Load(reader);
}
else throw new JsonReaderException($"{reader.TokenType} is not a valid {objectType.Name}.");
return new FileMetadataPairs(jItems.Select(ParseItem).ToList());
}

/// <inheritdoc/>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
foreach (var item in ((FileMetadataPairs)value).Items)
{
writer.WritePropertyName(item.Glob.Raw);
writer.WriteRawValue(JsonUtility.Serialize(item.Value));
}
writer.WriteEndObject();
}

private static FileMetadataPairsItem ParseItem(JToken item)
{
if (item.Type == JTokenType.Property)
{
JProperty jProperty = item as JProperty;
var pattern = jProperty.Name;
var rawValue = jProperty.Value;
return new FileMetadataPairsItem(pattern, rawValue);
}
else
{
throw new JsonReaderException($"Unsupported value {item} (type: {item.Type}).");
}
}
}
16 changes: 16 additions & 0 deletions src/Docfx.App/Config/FileMetadataPairsItem.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;
using System.Text.Json.Serialization;
using Docfx.Common;
using Docfx.Glob;

Expand All @@ -20,6 +22,7 @@ internal class FileMetadataPairsItem
/// <summary>
/// JObject, no need to transform it to object as the metadata value will not be used but only to be serialized
/// </summary>
[System.Text.Json.Serialization.JsonConverter(typeof(JsonElementConverter))]
public object Value { get; }

/// <summary>
Expand All @@ -31,3 +34,16 @@ public FileMetadataPairsItem(string pattern, object value)
Value = ConvertToObjectHelper.ConvertJObjectToObject(value);
}
}

internal class JsonElementConverter : JsonConverter<JsonElement>
{
public override JsonElement Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}

public override void Write(Utf8JsonWriter writer, JsonElement value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
3 changes: 2 additions & 1 deletion src/Docfx.App/Config/ListWithStringFallback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ namespace Docfx;
/// <summary>
/// ListWithStringFallback.
/// </summary>
[Newtonsoft.Json.JsonConverter(typeof(ListWithStringFallbackConverter))]
[Newtonsoft.Json.JsonConverter(typeof(ListWithStringFallbackConverter.NewtonsoftJsonConverter))]
[System.Text.Json.Serialization.JsonConverter(typeof(ListWithStringFallbackConverter.SystemTextJsonConverter))]
internal class ListWithStringFallback : List<string>
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Docfx;


internal partial class ListWithStringFallbackConverter
{
/// <summary>
/// JsonConverter for <see cref="ListWithStringFallback"/>.
/// </summary>
internal class NewtonsoftJsonConverter : JsonConverter
{
/// <inheritdoc/>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(FileMapping);
}

/// <inheritdoc/>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var model = new ListWithStringFallback();
var value = reader.Value;
IEnumerable<JToken> jItems;
if (reader.TokenType == JsonToken.StartArray)
{
jItems = JArray.Load(reader);
}
else if (reader.TokenType == JsonToken.StartObject)
{
jItems = JContainer.Load(reader);
}
else if (reader.TokenType == JsonToken.String)
{
jItems = JRaw.Load(reader);
}
else
{
jItems = JObject.Load(reader);
}

if (jItems is JValue)
{
model.Add(jItems.ToString());
}
else
{
foreach (var item in jItems)
{
model.Add(item.ToString());
}
}

return model;
}

/// <inheritdoc/>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartArray();
foreach (var item in (ListWithStringFallback)value)
{
serializer.Serialize(writer, item);
}
writer.WriteEndArray();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using YamlDotNet.Serialization;

namespace Docfx;


internal partial class ListWithStringFallbackConverter
{
/// <summary>
/// JsonConverter for <see cref="ListWithStringFallback"/>.
/// </summary>
internal class SystemTextJsonConverter : JsonConverter<ListWithStringFallback>
{
public override ListWithStringFallback Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var tokenType = reader.TokenType;
switch (tokenType)
{
case JsonTokenType.String:
{
var value = reader.GetString();
return new ListWithStringFallback([value]);
}
case JsonTokenType.StartArray:
{
var items = JsonSerializer.Deserialize<string[]>(ref reader, options);
return new ListWithStringFallback(items);
}
case JsonTokenType.StartObject:
{
using var document = JsonDocument.ParseValue(ref reader);
JsonElement root = document.RootElement;
var values = root.EnumerateObject().Select(x=>x.ToString());
return new ListWithStringFallback(values);
}
default:
throw new JsonException($"TokenType({reader.TokenType}) is not supported.");
}
}

public override void Write(Utf8JsonWriter writer, ListWithStringFallback value, JsonSerializerOptions options)
{
writer.WriteStartArray();
foreach (var item in value)
{
writer.WriteStringValue(item);
}
writer.WriteEndArray();
}
}
}
Loading