Skip to content

Commit

Permalink
chore: add FileMetadataPairs converters and JSON roundtrip test
Browse files Browse the repository at this point in the history
  • Loading branch information
filzrev committed Oct 6, 2024
1 parent 289a4b7 commit fe4feef
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 49 deletions.
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
64 changes: 64 additions & 0 deletions src/Docfx.App/Config/FileMetadataPairsConverter.NewtonsoftJson.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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)
{
var value = reader.Value;
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 NotSupportedException($"{elem.ValueKind}");
}
}
}
}
47 changes: 1 addition & 46 deletions src/Docfx.App/Config/FileMetadataPairsConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,51 +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)
{
var value = reader.Value;
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();
}
}
1 change: 1 addition & 0 deletions src/Docfx.Common/Json/JsonUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ private static bool IsSupported()

// Intermediate types for tests. it's expected to be removed later (And return true by default).
case "Docfx.FileMapping":
case "Docfx.FileMetadataPairs":
case "Docfx.ListWithStringFallback":
case "Docfx.Plugins.MarkdownServiceProperties":
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Docfx;
using Docfx.Common;
using FluentAssertions;

namespace docfx.Tests;

public partial class JsonSerializationTest
{
[Theory]
[TestData<FileMetadataPairs>]
public void JsonSerializationTest_FileMetadataPairs(string path)
{
// Arrange
var model = TestData.Load<FileMetadataPairs>(path);

// Act/Assert
ValidateJsonRoundTrip(model);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"priority": {
"**.md": 2.5,
"spec/**.md": 3
},
"keywords": {
"obj/docfx/**": [
"API",
"Reference"
],
"spec/**.md": [
"Spec",
"Conceptual"
]
},
"_noindex": {
"articles/**/article.md": true
},
"others": {
"nested": {
"null": null,
"val": "dummy",
"datetime": "2024-01-01:T00:00:00",
"double": 1.2345,
"array": [ 1, 2, 3, 4 ]
}
}
}

0 comments on commit fe4feef

Please sign in to comment.