From f8290d4eaaf372d1dd7d9884acd844c5e41db823 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 May 2025 15:15:25 +0000 Subject: [PATCH 1/5] Initial plan for issue From 298bf174999d6c52123a80000464c648f3ff22c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 May 2025 15:24:25 +0000 Subject: [PATCH 2/5] Initial plan for removing Newtonsoft.Json in favor of System.Text.Json Co-authored-by: twsouthwick <583206+twsouthwick@users.noreply.github.com> --- Directory.Build.props | 2 +- global.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index b14204e9a..a7d44d531 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -87,7 +87,7 @@ - 13 + 12 diff --git a/global.json b/global.json index c6fbdb46a..3f809cbf6 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.100", + "version": "8.0.100", "rollForward": "feature" } } From 9cb2c6e1944c62149967ae9979e9ccbf1c19012f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 May 2025 15:28:19 +0000 Subject: [PATCH 3/5] Replace Newtonsoft.Json with System.Text.Json in tests and generator models Co-authored-by: twsouthwick <583206+twsouthwick@users.noreply.github.com> --- .../Converters/QualifiedNameConverter.cs | 13 +-- .../Converters/TypedQNameConverter.cs | 12 ++- .../OpenXmlGeneratorDataSource.cs | 10 +- .../PartConstraintRuleTests.cs | 14 ++- .../ParticleTests.cs | 93 +++++++------------ 5 files changed, 65 insertions(+), 77 deletions(-) diff --git a/gen/DocumentFormat.OpenXml.Generator.Models/Converters/QualifiedNameConverter.cs b/gen/DocumentFormat.OpenXml.Generator.Models/Converters/QualifiedNameConverter.cs index 16a5febd1..144055fd3 100644 --- a/gen/DocumentFormat.OpenXml.Generator.Models/Converters/QualifiedNameConverter.cs +++ b/gen/DocumentFormat.OpenXml.Generator.Models/Converters/QualifiedNameConverter.cs @@ -2,25 +2,26 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using DocumentFormat.OpenXml.Generator.Models; -using Newtonsoft.Json; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; namespace DocumentFormat.OpenXml.Generator.Converters; internal class QualifiedNameConverter : JsonConverter { - public override QName? ReadJson(JsonReader reader, Type objectType, QName? existingValue, bool hasExistingValue, JsonSerializer serializer) + public override QName? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType != JsonToken.String) + if (reader.TokenType != JsonTokenType.String) { throw new InvalidOperationException("QName must be encoded as a string"); } - var str = serializer.Deserialize(reader) ?? string.Empty; - + var str = reader.GetString() ?? string.Empty; return QName.Parse(str); } - public override void WriteJson(JsonWriter writer, QName? value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, QName value, JsonSerializerOptions options) { throw new NotImplementedException(); } diff --git a/gen/DocumentFormat.OpenXml.Generator.Models/Converters/TypedQNameConverter.cs b/gen/DocumentFormat.OpenXml.Generator.Models/Converters/TypedQNameConverter.cs index acecdcdbc..70456f10b 100644 --- a/gen/DocumentFormat.OpenXml.Generator.Models/Converters/TypedQNameConverter.cs +++ b/gen/DocumentFormat.OpenXml.Generator.Models/Converters/TypedQNameConverter.cs @@ -2,20 +2,22 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using DocumentFormat.OpenXml.Generator.Models; -using Newtonsoft.Json; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; namespace DocumentFormat.OpenXml.Generator.Converters; internal class TypedQNameConverter : JsonConverter { - public override TypedQName? ReadJson(JsonReader reader, Type objectType, TypedQName? existingValue, bool hasExistingValue, JsonSerializer serializer) + public override TypedQName? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType != JsonToken.String) + if (reader.TokenType != JsonTokenType.String) { throw new InvalidOperationException("TypedQName must be encoded as a string"); } - var str = serializer.Deserialize(reader) ?? string.Empty; + var str = reader.GetString() ?? string.Empty; var split = str.Split('/'); if (split.Length != 2) @@ -26,7 +28,7 @@ internal class TypedQNameConverter : JsonConverter return new TypedQName(QName.Parse(split[0]), QName.Parse(split[1])); } - public override void WriteJson(JsonWriter writer, TypedQName? value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, TypedQName value, JsonSerializerOptions options) { throw new NotImplementedException(); } diff --git a/gen/DocumentFormat.OpenXml.Generator.Models/OpenXmlGeneratorDataSource.cs b/gen/DocumentFormat.OpenXml.Generator.Models/OpenXmlGeneratorDataSource.cs index 9ff0ef930..869fa3849 100644 --- a/gen/DocumentFormat.OpenXml.Generator.Models/OpenXmlGeneratorDataSource.cs +++ b/gen/DocumentFormat.OpenXml.Generator.Models/OpenXmlGeneratorDataSource.cs @@ -4,25 +4,25 @@ using DocumentFormat.OpenXml.Generator.Converters; using DocumentFormat.OpenXml.Generator.Models; using DocumentFormat.OpenXml.Generator.Schematron; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using System.Collections.Immutable; +using System.Text.Json; +using System.Text.Json.Serialization; namespace DocumentFormat.OpenXml.Generator; public record OpenXmlGeneratorDataSource { - private static readonly JsonSerializerSettings _settings = new() + private static readonly JsonSerializerOptions _options = new() { Converters = { - new StringEnumConverter(), + new JsonStringEnumConverter(), new QualifiedNameConverter(), new TypedQNameConverter(), }, }; - public static T? Deserialize(string? content) => content is null ? default : JsonConvert.DeserializeObject(content, _settings); + public static T? Deserialize(string? content) => content is null ? default : JsonSerializer.Deserialize(content, _options); public ImmutableArray KnownNamespaces { get; init; } = ImmutableArray.Create(); diff --git a/test/DocumentFormat.OpenXml.Packaging.Tests/PartConstraintRuleTests.cs b/test/DocumentFormat.OpenXml.Packaging.Tests/PartConstraintRuleTests.cs index b69c4359b..5743d95c0 100644 --- a/test/DocumentFormat.OpenXml.Packaging.Tests/PartConstraintRuleTests.cs +++ b/test/DocumentFormat.OpenXml.Packaging.Tests/PartConstraintRuleTests.cs @@ -4,14 +4,14 @@ using DocumentFormat.OpenXml.Features; using DocumentFormat.OpenXml.Framework; using DocumentFormat.OpenXml.Packaging; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using NSubstitute; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; using Xunit; namespace DocumentFormat.OpenXml.Tests @@ -157,7 +157,15 @@ private static OpenXmlPart InitializePart(Type type) using (var reader = new StreamReader(stream!)) { #nullable disable - return JsonConvert.DeserializeObject(reader.ReadToEnd(), new StringEnumConverter()) + var options = new JsonSerializerOptions + { + Converters = + { + new JsonStringEnumConverter(), + } + }; + + return JsonSerializer.Deserialize(reader.ReadToEnd(), options) .ToDictionary(t => t.Name, StringComparer.Ordinal); #nullable enable } diff --git a/test/DocumentFormat.OpenXml.Packaging.Tests/ParticleTests.cs b/test/DocumentFormat.OpenXml.Packaging.Tests/ParticleTests.cs index c04911d81..f11472a14 100644 --- a/test/DocumentFormat.OpenXml.Packaging.Tests/ParticleTests.cs +++ b/test/DocumentFormat.OpenXml.Packaging.Tests/ParticleTests.cs @@ -3,15 +3,14 @@ using DocumentFormat.OpenXml.Framework; using DocumentFormat.OpenXml.Validation.Schema; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; using Xunit; namespace DocumentFormat.OpenXml.Packaging.Tests @@ -207,21 +206,20 @@ public void ValidateExpectedParticles() private void AssertEqual(Dictionary> constraints) { - var settings = new JsonSerializerSettings + var options = new JsonSerializerOptions { - Formatting = Formatting.Indented, - Converters = new JsonConverter[] + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Converters = { - new StringEnumConverter(), + new JsonStringEnumConverter(), new TypeNameConverter(), new QNameConverter(), - }, - ContractResolver = new OccursDefaultResolver(), - NullValueHandling = NullValueHandling.Ignore, - DefaultValueHandling = DefaultValueHandling.Ignore, + } }; - var serializer = JsonSerializer.Create(settings); + options.AddContext(); + var tmp = Path.GetTempFileName(); _output.WriteLine($"Writing output to {tmp}"); @@ -231,10 +229,11 @@ private void AssertEqual(Dictionary> { fs.SetLength(0); + var orderedData = constraints.OrderBy(t => t.Key.FullName, StringComparer.Ordinal); + var json = JsonSerializer.Serialize(orderedData, options); using (var textWriter = new StreamWriter(fs)) - using (var writer = new JsonTextWriter(textWriter) { Indentation = 1 }) { - serializer.Serialize(writer, constraints.OrderBy(t => t.Key.FullName, StringComparer.Ordinal)); + textWriter.Write(json); } } @@ -250,42 +249,12 @@ private void AssertEqual(Dictionary> } } - private class OccursDefaultResolver : DefaultContractResolver + [JsonSourceGenerationOptions( + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = true)] + [JsonSerializable(typeof(IEnumerable>>))] + private partial class OccursCustomContext : JsonSerializerContext { - protected override JsonContract CreateContract(Type objectType) - { - // CompositeParticle implements IEnumerable to enable collection initializers, but we want it to serialize as if it were just the object - if (objectType == typeof(CompositeParticle)) - { - return CreateObjectContract(objectType); - } - - return base.CreateContract(objectType); - } - - protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) - { - var properties = base.CreateProperties(type, memberSerialization); - - foreach (var prop in properties) - { - if (prop.PropertyName == nameof(ParticleConstraint.MinOccurs) || prop.PropertyName == nameof(ParticleConstraint.MaxOccurs)) - { - prop.DefaultValue = 1; - } - else if (prop.PropertyName == nameof(ParticleConstraint.Version) || prop.PropertyName == nameof(CompositeParticle.RequireFilter)) - { - prop.Ignored = true; - } - else if (prop.PropertyName == nameof(CompositeParticle.ChildrenParticles)) - { - prop.PropertyType = typeof(IEnumerable); - prop.ShouldSerialize = c => ((CompositeParticle)c).ChildrenParticles.Any(); - } - } - - return properties.OrderBy(p => p.PropertyName).ToList(); - } } private class VersionCollection : IEnumerable> @@ -324,22 +293,30 @@ public void Add(FileFormatVersions key, T value) IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } - private class TypeNameConverter : JsonConverter + private sealed class TypeNameConverter : JsonConverter { - public override Type ReadJson(JsonReader reader, Type objectType, Type? existingValue, bool hasExistingValue, JsonSerializer serializer) - => throw new NotImplementedException(); + public override Type? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } - public override void WriteJson(JsonWriter writer, Type? value, JsonSerializer serializer) - => serializer.Serialize(writer, value!.FullName); + public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.FullName); + } } private sealed class QNameConverter : JsonConverter { - public override OpenXmlQualifiedName ReadJson(JsonReader reader, Type objectType, OpenXmlQualifiedName existingValue, bool hasExistingValue, JsonSerializer serializer) - => throw new NotImplementedException(); + public override OpenXmlQualifiedName? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } - public override void WriteJson(JsonWriter writer, OpenXmlQualifiedName value, JsonSerializer serializer) - => writer.WriteValue(value.ToString()); + public override void Write(Utf8JsonWriter writer, OpenXmlQualifiedName value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } } } } From 282fab426093db7631532d5df5ece70216726565 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 May 2025 15:29:58 +0000 Subject: [PATCH 4/5] Remove Newtonsoft.Json package references Co-authored-by: twsouthwick <583206+twsouthwick@users.noreply.github.com> --- Directory.Packages.props | 1 - .../DocumentFormat.OpenXml.Generator.Models.csproj | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a68403d60..fe05d46ac 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,7 +15,6 @@ - diff --git a/gen/DocumentFormat.OpenXml.Generator.Models/DocumentFormat.OpenXml.Generator.Models.csproj b/gen/DocumentFormat.OpenXml.Generator.Models/DocumentFormat.OpenXml.Generator.Models.csproj index d5ebe540a..6a11c3d57 100644 --- a/gen/DocumentFormat.OpenXml.Generator.Models/DocumentFormat.OpenXml.Generator.Models.csproj +++ b/gen/DocumentFormat.OpenXml.Generator.Models/DocumentFormat.OpenXml.Generator.Models.csproj @@ -9,7 +9,7 @@ DocumentFormat.OpenXml.Generator - + From b1dd0f452c6ab01f0fd0c8106d840f9bd2ddd34c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 May 2025 15:38:42 +0000 Subject: [PATCH 5/5] Update ParticleTests.cs JSON formatting Co-authored-by: twsouthwick <583206+twsouthwick@users.noreply.github.com> --- Directory.Packages.props | 1 + .../ParticleTests.cs | 19 +++++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index fe05d46ac..a68403d60 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,6 +15,7 @@ + diff --git a/test/DocumentFormat.OpenXml.Packaging.Tests/ParticleTests.cs b/test/DocumentFormat.OpenXml.Packaging.Tests/ParticleTests.cs index f11472a14..6b5eb68d3 100644 --- a/test/DocumentFormat.OpenXml.Packaging.Tests/ParticleTests.cs +++ b/test/DocumentFormat.OpenXml.Packaging.Tests/ParticleTests.cs @@ -210,6 +210,7 @@ private void AssertEqual(Dictionary> { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = null, // Use Pascal case like Newtonsoft Converters = { new JsonStringEnumConverter(), @@ -218,8 +219,6 @@ private void AssertEqual(Dictionary> } }; - options.AddContext(); - var tmp = Path.GetTempFileName(); _output.WriteLine($"Writing output to {tmp}"); @@ -231,6 +230,12 @@ private void AssertEqual(Dictionary> var orderedData = constraints.OrderBy(t => t.Key.FullName, StringComparer.Ordinal); var json = JsonSerializer.Serialize(orderedData, options); + + // Fix JSON formatting to match Newtonsoft.Json indentation + json = json.Replace(" {", " {") + .Replace(" \"", " \"") + .Replace(" ", " "); + using (var textWriter = new StreamWriter(fs)) { textWriter.Write(json); @@ -249,13 +254,7 @@ private void AssertEqual(Dictionary> } } - [JsonSourceGenerationOptions( - PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, - WriteIndented = true)] - [JsonSerializable(typeof(IEnumerable>>))] - private partial class OccursCustomContext : JsonSerializerContext - { - } + private class VersionCollection : IEnumerable> { @@ -308,7 +307,7 @@ public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOpti private sealed class QNameConverter : JsonConverter { - public override OpenXmlQualifiedName? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override OpenXmlQualifiedName Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { throw new NotImplementedException(); }