diff --git a/src/Docfx.Common/Json/JsonUtility.cs b/src/Docfx.Common/Json/JsonUtility.cs index 4677cceedf8..0f0b3c471e4 100644 --- a/src/Docfx.Common/Json/JsonUtility.cs +++ b/src/Docfx.Common/Json/JsonUtility.cs @@ -87,12 +87,18 @@ private static bool IsSupported() var type = typeof(T); var fullName = type.FullName; - // TODO: Return `true` for types that support serialize/deserializenon with System.Text.Json. switch (fullName) { + // TODO: Return `true` for types that support serialize/deserializenon with System.Text.Json. case "Docfx.Build.Engine.XRefMap": + case "Docfx.DataContracts.ManagedReference.PageViewModel": return true; - + + // Intermediate types for tests. it's expected to be removed later (And return true by default). + case "Docfx.Plugins.MarkdownServiceProperties": + return true; + + // TODO: default to true default: return false; } diff --git a/src/Docfx.Common/Json/System.Text.Json/SystemTextJsonUtility.cs b/src/Docfx.Common/Json/System.Text.Json/SystemTextJsonUtility.cs index 36e7230ca2f..96961c8be26 100644 --- a/src/Docfx.Common/Json/System.Text.Json/SystemTextJsonUtility.cs +++ b/src/Docfx.Common/Json/System.Text.Json/SystemTextJsonUtility.cs @@ -40,7 +40,7 @@ static SystemTextJsonUtility() NumberHandling = JsonNumberHandling.AllowReadingFromString, Converters = { - // new JsonStringEnumConverter(JsonNamingPolicy.CamelCase), + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase), new ObjectToInferredTypesConverter(), // Required for `Dictionary` type deserialization. }, WriteIndented = false, diff --git a/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSetting.cs b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSetting.cs index aee8908e57f..cf5f0434eb2 100644 --- a/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSetting.cs +++ b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSetting.cs @@ -14,18 +14,10 @@ namespace Docfx.MarkdigEngine.Extensions; /// Markdig extension setting. /// [DebuggerDisplay("Name = {Name}")] -[Newtonsoft.Json.JsonConverter(typeof(MarkdigExtensionSettingConverter))] +[Newtonsoft.Json.JsonConverter(typeof(MarkdigExtensionSettingConverter.NewtonsoftJsonConverter))] +[System.Text.Json.Serialization.JsonConverter(typeof(MarkdigExtensionSettingConverter.SystemTextJsonConverter))] public class MarkdigExtensionSetting { - private static readonly JsonSerializerOptions DefaultSerializerOptions = new() - { - AllowTrailingCommas = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = { - new JsonStringEnumConverter() - }, - }; - /// /// Initializes a new instance of the class. /// @@ -59,7 +51,7 @@ public MarkdigExtensionSetting(string name, JsonNode? options = null) public T GetOptions(T fallbackValue) { return Options is null ? fallbackValue - : JsonSerializer.Deserialize(JsonSerializer.Serialize(Options), DefaultSerializerOptions) ?? fallbackValue; + : JsonSerializer.Deserialize(JsonSerializer.Serialize(Options), MarkdigExtensionSettingConverter.DefaultSerializerOptions) ?? fallbackValue; } /// diff --git a/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.NewtonsoftJson.cs b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.NewtonsoftJson.cs new file mode 100644 index 00000000000..76f84d6fb60 --- /dev/null +++ b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.NewtonsoftJson.cs @@ -0,0 +1,88 @@ +// 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.Linq; +using Newtonsoft.Json; + +namespace Docfx.MarkdigEngine.Extensions; + +internal partial class MarkdigExtensionSettingConverter +{ + internal class NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter + { + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(MarkdigExtensionSetting); + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + // var value = reader.Value; + switch (reader.TokenType) + { + case JsonToken.String: + { + var name = (string)reader.Value; + return new MarkdigExtensionSetting(name); + } + case JsonToken.StartObject: + { + var jObj = JObject.Load(reader); + + var props = jObj.Properties().ToArray(); + + // Object key must be the name of markdig extension. + if (props.Length != 1) + return null; + + var prop = props[0]; + var name = prop.Name; + + var options = prop.Value; + if (options.Count() == 0) + { + return new MarkdigExtensionSetting(name); + } + + // Convert JToken to JsonElement. + var json = options.ToString(); + using var doc = System.Text.Json.JsonDocument.Parse(json); + var jsonElement = doc.RootElement.Clone(); + + return new MarkdigExtensionSetting(name) + { + Options = jsonElement, + }; + } + + default: + return null; + } + } + + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + return; + + var model = (MarkdigExtensionSetting)value; + + if (model.Options == null || !model.Options.HasValue) + { + writer.WriteValue(model.Name); + } + else + { + writer.WriteStartObject(); + writer.WritePropertyName(model.Name); + var json = model.Options.ToString(); + writer.WriteRawValue(json); + writer.WriteEndObject(); + } + } + } +} + diff --git a/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.SystemTextJson.cs b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.SystemTextJson.cs new file mode 100644 index 00000000000..1828548c0e7 --- /dev/null +++ b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.SystemTextJson.cs @@ -0,0 +1,75 @@ +// 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 System.Text.Json.Nodes; +using System.Xml.Linq; + +namespace Docfx.MarkdigEngine.Extensions; + +internal partial class MarkdigExtensionSettingConverter +{ + internal class SystemTextJsonConverter : JsonConverter + { + public override MarkdigExtensionSetting Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + { + var name = reader.GetString(); + return new MarkdigExtensionSetting(name); + } + case JsonTokenType.StartObject: + { + var elem = JsonElement.ParseValue(ref reader); + + var props = elem.EnumerateObject().ToArray(); + + // Object key must be the name of markdig extension. + if (props.Length != 1) + return null; + + var prop = props[0]; + var name = prop.Name; + var value = prop.Value; + + if (value.ValueKind != JsonValueKind.Object) + { + return new MarkdigExtensionSetting(name); + } + + return new MarkdigExtensionSetting(name) + { + Options = value, + }; + } + + default: + return null; + } + } + + public override void Write(Utf8JsonWriter writer, MarkdigExtensionSetting value, JsonSerializerOptions options) + { + if (value == null) + return; + + var model = (MarkdigExtensionSetting)value; + + if (model.Options == null || !model.Options.HasValue) + { + writer.WriteStringValue(model.Name); + } + else + { + writer.WriteStartObject(); + writer.WritePropertyName(model.Name); + var json = model.Options.ToString(); + writer.WriteRawValue(json); + writer.WriteEndObject(); + } + } + } +} diff --git a/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.cs b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.cs index 5ff3ba24f4b..d72be8b184e 100644 --- a/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.cs +++ b/src/Docfx.MarkdigEngine.Extensions/MarkdigExtensionSettingConverter.cs @@ -1,96 +1,25 @@ // 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; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Docfx.MarkdigEngine.Extensions; -internal class MarkdigExtensionSettingConverter : Newtonsoft.Json.JsonConverter +internal partial class MarkdigExtensionSettingConverter { - // JsonSerializerOptions that used to deserialize MarkdigExtension options. + // Shared JsonSerializerOptions instance. internal static readonly System.Text.Json.JsonSerializerOptions DefaultSerializerOptions = new() { IncludeFields = true, AllowTrailingCommas = true, - DictionaryKeyPolicy = System.Text.Json.JsonNamingPolicy.CamelCase, - PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true, Converters = { - new System.Text.Json.Serialization.JsonStringEnumConverter() + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }, WriteIndented = false, }; - - /// - public override bool CanConvert(Type objectType) - { - return objectType == typeof(MarkdigExtensionSetting); - } - - /// - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - // var value = reader.Value; - switch (reader.TokenType) - { - case JsonToken.String: - { - var name = (string)reader.Value; - return new MarkdigExtensionSetting(name); - } - case JsonToken.StartObject: - { - var jObj = JObject.Load(reader); - - var props = jObj.Properties().ToArray(); - - // Object key must be the name of markdig extension. - if (props.Length != 1) - return null; - - var prop = props[0]; - var name = prop.Name; - - var options = prop.Value; - if (options.Count() == 0) - { - return new MarkdigExtensionSetting(name); - } - - // Serialize options to JsonElement. - var jsonElement = System.Text.Json.JsonSerializer.SerializeToElement(options, DefaultSerializerOptions); - - return new MarkdigExtensionSetting(name) - { - Options = jsonElement, - }; - } - - default: - return null; - } - } - - /// - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - if (value == null) - return; - - var model = (MarkdigExtensionSetting)value; - - if (model.Options == null || !model.Options.HasValue) - { - writer.WriteValue(model.Name); - } - else - { - writer.WriteStartObject(); - writer.WritePropertyName(model.Name); - var json = model.Options.ToString(); - writer.WriteRawValue(json); - writer.WriteEndObject(); - } - } } + diff --git a/test/docfx.Tests/Api.verified.cs b/test/docfx.Tests/Api.verified.cs index e10ca4eff7e..126141a7185 100644 --- a/test/docfx.Tests/Api.verified.cs +++ b/test/docfx.Tests/Api.verified.cs @@ -3575,8 +3575,9 @@ public LineNumberExtension(System.Func getFilePath = null) { } public void Setup(Markdig.MarkdownPipelineBuilder pipeline) { } public void Setup(Markdig.MarkdownPipeline pipeline, Markdig.Renderers.IMarkdownRenderer renderer) { } } - [Newtonsoft.Json.JsonConverter(typeof(Docfx.MarkdigEngine.Extensions.MarkdigExtensionSettingConverter))] + [Newtonsoft.Json.JsonConverter(typeof(Docfx.MarkdigEngine.Extensions.MarkdigExtensionSettingConverter.NewtonsoftJsonConverter))] [System.Diagnostics.DebuggerDisplay("Name = {Name}")] + [System.Text.Json.Serialization.JsonConverter(typeof(Docfx.MarkdigEngine.Extensions.MarkdigExtensionSettingConverter.SystemTextJsonConverter))] public class MarkdigExtensionSetting { public MarkdigExtensionSetting(string name, System.Text.Json.Nodes.JsonNode? options = null) { } diff --git a/test/docfx.Tests/SerializationTests/JsonSerializationTest.MarkdownServiceProperties.cs b/test/docfx.Tests/SerializationTests/JsonSerializationTest.MarkdownServiceProperties.cs new file mode 100644 index 00000000000..c1e16b18353 --- /dev/null +++ b/test/docfx.Tests/SerializationTests/JsonSerializationTest.MarkdownServiceProperties.cs @@ -0,0 +1,25 @@ +// 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 Docfx; +using Docfx.Common; +using Docfx.MarkdigEngine.Extensions; +using Docfx.Plugins; +using FluentAssertions; + +namespace docfx.Tests; + +public partial class JsonSerializationTest +{ + [Theory] + [TestData] + public void JsonSerializationTest_MarkdownServiceProperties(string path) + { + // Arrange + var model = TestData.Load(path); + + // Act/Assert + ValidateJsonRoundTrip(model); + } +} diff --git a/test/docfx.Tests/SerializationTests/TestData/MarkdownServiceProperties/markdownServiceProperties_01.json b/test/docfx.Tests/SerializationTests/TestData/MarkdownServiceProperties/markdownServiceProperties_01.json new file mode 100644 index 00000000000..f297f648163 --- /dev/null +++ b/test/docfx.Tests/SerializationTests/TestData/MarkdownServiceProperties/markdownServiceProperties_01.json @@ -0,0 +1,28 @@ +{ + "enableSourceInfo": false, + "markdigExtensions": [ + "FootNotes", + { "Emojis": "default" }, + { "AutoIdentifiers": "default" }, + { + "MediaLinks": { + "width": 800, + "height": 400 + } + } + ], + "fallbackFolders": [ + "fallbackdir" + ], + "alerts": { + "TODO": "alert alert-secondary" + }, + "plantUml": { + "javaPath": "dummy", + "localGraphvizDotPath": "dummy", + "localPlantUmlPath": "dummy", + "outputFormat": "svg", + "remoteUrl": "dummy", + "renderingMode": "local" + } +}