Skip to content

Remove usage of Newtonsoft.Json in favor of System.Text.Json #1932

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

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
</PropertyGroup>

<PropertyGroup>
<LangVersion>13</LangVersion>
<LangVersion>12</LangVersion>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<QName>
{
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<string>(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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TypedQName>
{
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<string>(reader) ?? string.Empty;
var str = reader.GetString() ?? string.Empty;
var split = str.Split('/');

if (split.Length != 2)
Expand All @@ -26,7 +28,7 @@ internal class TypedQNameConverter : JsonConverter<TypedQName>
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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<RootNamespace>DocumentFormat.OpenXml.Generator</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" PrivateAssets="all" />
<PackageReference Include="System.Text.Json" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Collections.Immutable" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(string? content) => content is null ? default : JsonConvert.DeserializeObject<T>(content, _settings);
public static T? Deserialize<T>(string? content) => content is null ? default : JsonSerializer.Deserialize<T>(content, _options);

public ImmutableArray<NamespaceInfo> KnownNamespaces { get; init; } = ImmutableArray.Create<NamespaceInfo>();

Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "9.0.100",
"version": "8.0.100",
"rollForward": "feature"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -157,7 +157,15 @@ private static OpenXmlPart InitializePart(Type type)
using (var reader = new StreamReader(stream!))
{
#nullable disable
return JsonConvert.DeserializeObject<ConstraintData[]>(reader.ReadToEnd(), new StringEnumConverter())
var options = new JsonSerializerOptions
{
Converters =
{
new JsonStringEnumConverter(),
}
};

return JsonSerializer.Deserialize<ConstraintData[]>(reader.ReadToEnd(), options)
.ToDictionary(t => t.Name, StringComparer.Ordinal);
#nullable enable
}
Expand Down
94 changes: 35 additions & 59 deletions test/DocumentFormat.OpenXml.Packaging.Tests/ParticleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -207,21 +206,19 @@ public void ValidateExpectedParticles()

private void AssertEqual(Dictionary<Type, VersionCollection<ParticleConstraint>> constraints)
{
var settings = new JsonSerializerSettings
var options = new JsonSerializerOptions
{
Formatting = Formatting.Indented,
Converters = new JsonConverter[]
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = null, // Use Pascal case like Newtonsoft
Converters =
{
new StringEnumConverter(),
new JsonStringEnumConverter(),
new TypeNameConverter(),
new QNameConverter(),
},
ContractResolver = new OccursDefaultResolver(),
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore,
}
};

var serializer = JsonSerializer.Create(settings);
var tmp = Path.GetTempFileName();

_output.WriteLine($"Writing output to {tmp}");
Expand All @@ -231,10 +228,17 @@ private void AssertEqual(Dictionary<Type, VersionCollection<ParticleConstraint>>
{
fs.SetLength(0);

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))
using (var writer = new JsonTextWriter(textWriter) { Indentation = 1 })
{
serializer.Serialize(writer, constraints.OrderBy(t => t.Key.FullName, StringComparer.Ordinal));
textWriter.Write(json);
}
}

Expand All @@ -250,43 +254,7 @@ private void AssertEqual(Dictionary<Type, VersionCollection<ParticleConstraint>>
}
}

private class OccursDefaultResolver : DefaultContractResolver
{
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<JsonProperty> 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<ParticleConstraint>);
prop.ShouldSerialize = c => ((CompositeParticle)c).ChildrenParticles.Any();
}
}

return properties.OrderBy(p => p.PropertyName).ToList();
}
}

private class VersionCollection<T> : IEnumerable<KeyValuePair<FileFormatVersions, T>>
{
Expand Down Expand Up @@ -324,22 +292,30 @@ public void Add(FileFormatVersions key, T value)
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

private class TypeNameConverter : JsonConverter<Type>
private sealed class TypeNameConverter : JsonConverter<Type>
{
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<OpenXmlQualifiedName>
{
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());
}
}
}
}
Loading