From 062ab3c62339d2333e2bf0e0b452cf6cdfb91fbd Mon Sep 17 00:00:00 2001 From: filzrev <103790468+filzrev@users.noreply.github.com> Date: Fri, 29 Mar 2024 13:25:28 +0900 Subject: [PATCH] perf: optimize xrefmap.json file deserialization --- src/Docfx.Build/XRefMaps/XRefMapDownloader.cs | 39 ++++++++-- .../XRefMaps/XRefMapRedirection.cs | 6 ++ test/Docfx.Build.Tests/TestData/xrefmap.json | 10 +++ .../XRefMapDownloaderTest.cs | 15 ++++ .../XRefMapSerializationTest.cs | 74 +++++++++++++++++++ test/docfx.Tests/Api.verified.cs | 4 + 6 files changed, 140 insertions(+), 8 deletions(-) create mode 100644 test/Docfx.Build.Tests/TestData/xrefmap.json create mode 100644 test/Docfx.Build.Tests/XRefMapSerializationTest.cs diff --git a/src/Docfx.Build/XRefMaps/XRefMapDownloader.cs b/src/Docfx.Build/XRefMaps/XRefMapDownloader.cs index 586c93167b9..55d477394b5 100644 --- a/src/Docfx.Build/XRefMaps/XRefMapDownloader.cs +++ b/src/Docfx.Build/XRefMaps/XRefMapDownloader.cs @@ -111,13 +111,25 @@ protected static IXRefContainer DownloadFromLocal(Uri uri) private static IXRefContainer ReadLocalFile(string filePath) { Logger.LogVerbose($"Reading from file: {filePath}"); - if (".zip".Equals(Path.GetExtension(filePath), StringComparison.OrdinalIgnoreCase)) + + switch (Path.GetExtension(filePath).ToLowerInvariant()) { - return XRefArchive.Open(filePath, XRefArchiveMode.Read); - } + case ".zip": + return XRefArchive.Open(filePath, XRefArchiveMode.Read); - using var sr = File.OpenText(filePath); - return YamlUtility.Deserialize(sr); + case ".json": + { + using var stream = File.OpenText(filePath); + return JsonUtility.Deserialize(stream); + } + + case ".yml": + default: + { + using var sr = File.OpenText(filePath); + return YamlUtility.Deserialize(sr); + } + } } protected static async Task DownloadFromWebAsync(Uri uri) @@ -134,10 +146,21 @@ protected static async Task DownloadFromWebAsync(Uri uri) }; using var stream = await httpClient.GetStreamAsync(uri); - using var sr = new StreamReader(stream, bufferSize: 81920); // Default :1024 byte - var map = YamlUtility.Deserialize(sr); - return map; + switch (Path.GetExtension(uri.AbsolutePath).ToLowerInvariant()) + { + case ".json": + { + using var sr = new StreamReader(stream, bufferSize: 81920); // Default :1024 byte + return JsonUtility.Deserialize(sr); + } + case ".yml": + default: + { + using var sr = new StreamReader(stream, bufferSize: 81920); // Default :1024 byte + return YamlUtility.Deserialize(sr); + } + } } public static void UpdateHref(XRefMap map, Uri uri) diff --git a/src/Docfx.Build/XRefMaps/XRefMapRedirection.cs b/src/Docfx.Build/XRefMaps/XRefMapRedirection.cs index 09ba60984f8..1035a7f0650 100644 --- a/src/Docfx.Build/XRefMaps/XRefMapRedirection.cs +++ b/src/Docfx.Build/XRefMaps/XRefMapRedirection.cs @@ -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.Serialization; +using Newtonsoft.Json; using YamlDotNet.Serialization; namespace Docfx.Build.Engine; @@ -8,8 +10,12 @@ namespace Docfx.Build.Engine; public class XRefMapRedirection { [YamlMember(Alias = "uidPrefix")] + [JsonProperty("uidPrefix")] + [JsonPropertyName("uidPrefix")] public string UidPrefix { get; set; } [YamlMember(Alias = "href")] + [JsonProperty("Href")] + [JsonPropertyName("href")] public string Href { get; set; } } diff --git a/test/Docfx.Build.Tests/TestData/xrefmap.json b/test/Docfx.Build.Tests/TestData/xrefmap.json new file mode 100644 index 00000000000..7a3aad507fb --- /dev/null +++ b/test/Docfx.Build.Tests/TestData/xrefmap.json @@ -0,0 +1,10 @@ +{ + "references": [ + { + "fullName": "str", + "href": "https://docs.python.org/3.5/library/stdtypes.html#str", + "name": "str", + "uid": "str" + } + ] +} diff --git a/test/Docfx.Build.Tests/XRefMapDownloaderTest.cs b/test/Docfx.Build.Tests/XRefMapDownloaderTest.cs index e7536736935..c8ae4b7ec57 100644 --- a/test/Docfx.Build.Tests/XRefMapDownloaderTest.cs +++ b/test/Docfx.Build.Tests/XRefMapDownloaderTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Net; +using FluentAssertions; using Xunit; namespace Docfx.Build.Engine.Tests; @@ -35,4 +36,18 @@ public async Task ReadLocalXRefMapWithFallback() Assert.NotNull(xrefSpec); Assert.Equal("https://docs.python.org/3.5/library/stdtypes.html#str", xrefSpec.Href); } + + [Fact] + public async Task ReadLocalXRefMapJsonFileTest() + { + // Arrange + var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "xrefmap.json"); + + XRefMapDownloader downloader = new XRefMapDownloader(); + var xrefMap = await downloader.DownloadAsync(new Uri(path)) as XRefMap; + + // Assert + xrefMap.Should().NotBeNull(); + xrefMap.References.Should().HaveCount(1); + } } diff --git a/test/Docfx.Build.Tests/XRefMapSerializationTest.cs b/test/Docfx.Build.Tests/XRefMapSerializationTest.cs new file mode 100644 index 00000000000..6f6cac18377 --- /dev/null +++ b/test/Docfx.Build.Tests/XRefMapSerializationTest.cs @@ -0,0 +1,74 @@ +// 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; +using Docfx.Common; +using Docfx.Plugins; +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; + +namespace Docfx.Build.Engine.Tests; + +public class XRefMapSerializationTest +{ + [Fact] + public void XRefMapSerializationRoundTripTest() + { + var model = new XRefMap + { + BaseUrl = "http://localhost", + Sorted = true, + HrefUpdated = null, + Redirections = new List + { + new XRefMapRedirection + { + Href = "Dummy", + UidPrefix = "Dummy" + }, + }, + References = new List + { + new XRefSpec(new Dictionary + { + ["Additional1"] = "Dummy", + }) + { + Uid = "Dummy", + Name = "Dummy", + Href = "Dummy", + CommentId ="Dummy", + IsSpec = true, + }, + }, + Others = new Dictionary + { + ["Other1"] = "Dummy", + } + }; + + // Arrange + var jsonResult = RoundtripByNewtonsoftJson(model); + var yamlResult = RoundtripWithYamlDotNet(model); + + // Assert + jsonResult.Should().BeEquivalentTo(model); + yamlResult.Should().BeEquivalentTo(model); + } + + private static T RoundtripByNewtonsoftJson(T model) + { + var json = JsonUtility.Serialize(model); + return JsonUtility.Deserialize(new StringReader(json)); + } + + private static T RoundtripWithYamlDotNet(T model) + { + var sb = new StringBuilder(); + using var sw = new StringWriter(sb); + YamlUtility.Serialize(sw, model); + var json = sb.ToString(); + return YamlUtility.Deserialize(new StringReader(json)); + } +} diff --git a/test/docfx.Tests/Api.verified.cs b/test/docfx.Tests/Api.verified.cs index 0933d8366bb..348e099c9b3 100644 --- a/test/docfx.Tests/Api.verified.cs +++ b/test/docfx.Tests/Api.verified.cs @@ -486,8 +486,12 @@ protected override Docfx.Build.Engine.IXRefContainer GetMap(string name) { } public class XRefMapRedirection { public XRefMapRedirection() { } + [Newtonsoft.Json.JsonProperty("Href")] + [System.Text.Json.Serialization.JsonPropertyName("href")] [YamlDotNet.Serialization.YamlMember(Alias="href")] public string Href { get; set; } + [Newtonsoft.Json.JsonProperty("uidPrefix")] + [System.Text.Json.Serialization.JsonPropertyName("uidPrefix")] [YamlDotNet.Serialization.YamlMember(Alias="uidPrefix")] public string UidPrefix { get; set; } }