Skip to content

Mark JsonNode.WriteTo as obsolete with SYSLIB0060 #111392

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

Closed
wants to merge 2 commits into from
Closed
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
1 change: 1 addition & 0 deletions docs/project/list-of-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ The PR that reveals the implementation of the `<IncludeInternalObsoleteAttribute
| __`SYSLIB0057`__ | Loading certificate data through the constructor or Import is obsolete. Use X509CertificateLoader instead to load certificates. |
| __`SYSLIB0058`__ | KeyExchangeAlgorithm, KeyExchangeStrength, CipherAlgorithm, CipherAlgorithmStrength, HashAlgorithm and HashStrength properties of SslStream are obsolete. Use NegotiatedCipherSuite instead. |
| __`SYSLIB0059`__ | SystemEvents.EventsThreadShutdown callbacks are not run before the process exits. Use AppDomain.ProcessExit instead. |
| __`SYSLIB0060`__ | JsonNode.WriteTo are obsolete. Use JsonSerializer.Serialize{Async} instead. |

## Analyzer Warnings

Expand Down
3 changes: 3 additions & 0 deletions src/libraries/Common/src/System/Obsoletions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ internal static class Obsoletions
internal const string SystemEventsEventsThreadShutdownMessage = "SystemEvents.EventsThreadShutdown callbacks are not run before the process exits. Use AppDomain.ProcessExit instead.";
internal const string SystemEventsEventsThreadShutdownDiagId = "SYSLIB0059";

internal const string JsonNodeWriteToMessage = "JsonNode.WriteTo are obsolete. Use JsonSerializer.Serialize{Async} instead.";
internal const string JsonNodeWriteToDiagId = "SYSLIB0060";

// When adding a new diagnostic ID, add it to the table in docs\project\list-of-diagnostics.md as well.
// Keep new const identifiers above this comment.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ internal override void GetPath(ref ValueStringBuilder path, JsonNode? child)
}

/// <inheritdoc/>
[Obsolete(Obsoletions.JsonNodeWriteToMessage, DiagnosticId = Obsoletions.JsonNodeWriteToDiagId)]
public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
{
if (writer is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ public string ToJsonString(JsonSerializerOptions? options = null)
Utf8JsonWriter writer = Utf8JsonWriterCache.RentWriterAndBuffer(writerOptions, defaultBufferSize, out PooledByteBufferWriter output);
try
{
#pragma warning disable SYSLIB0060 // Type or member is obsolete
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not comfortable with all the suppressions this change is forcing. Per my comment in #108643 I don't believe the method should be obsoleted as such. Perhaps EB.Never is a better approach?

cc @stephentoub

Copy link
Member

@stephentoub stephentoub Jun 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should only obsolete things if we don't want them used. If we're using them and believe the usage is correct and desirable, that's a clear sign we should not obsolete the method. Why was obsoletion recommended in the first place?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was recommended in API review as a way for directing users to JsonSerializer APIs instead. I wouldn't say I'm particularly on board with that decision.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do that with documentation. Obsoletion is a really large hammer, and an improperly used one if we're not willing to stop using the API ourselves.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Unless you think we should mark the API EB.Never then we should just close this PR and create a new one against dotnet-api-docs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless you think we should mark the API EB

Do we actively want to discourage use of this? I guess I'm missing what the detractor is. Is it just that there's another API that does the same thing and stylistically we'd encourage folks to use the other one?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not discourage as much as promote use of JsonSerializer instead. The only real issue with WriteTo is that it's an easily discoverable API that makes users assume it's the canonical way for serializing JsonNode values. It is not: it doesn't account for null and only supports writing to Utf8JsonWriter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like we should just address it with docs. If you think it's also valuable to EBNever it, we could, too.

WriteTo(writer, options);
#pragma warning restore SYSLIB0060 // Type or member is obsolete
writer.Flush();
return JsonHelpers.Utf8GetString(output.WrittenMemory.Span);
}
Expand Down Expand Up @@ -58,7 +60,9 @@ public override string ToString()
Utf8JsonWriter writer = Utf8JsonWriterCache.RentWriterAndBuffer(new JsonWriterOptions { Indented = true }, JsonSerializerOptions.BufferSizeDefault, out PooledByteBufferWriter output);
try
{
#pragma warning disable SYSLIB0060 // Type or member is obsolete
WriteTo(writer);
#pragma warning restore SYSLIB0060 // Type or member is obsolete
writer.Flush();
return JsonHelpers.Utf8GetString(output.WrittenMemory.Span);
}
Expand All @@ -76,6 +80,7 @@ public override string ToString()
/// The <paramref name="writer"/> parameter is <see langword="null"/>.
/// </exception>
/// <param name="options">Options to control the serialization behavior.</param>
[Obsolete(Obsoletions.JsonNodeWriteToMessage, DiagnosticId = Obsoletions.JsonNodeWriteToDiagId)]
public abstract void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ public bool TryGetPropertyValue(string propertyName, out JsonNode? jsonNode)
}

/// <inheritdoc/>
[Obsolete(Obsoletions.JsonNodeWriteToMessage, DiagnosticId = Obsoletions.JsonNodeWriteToDiagId)]
public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
{
if (writer is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ static JsonElement ToJsonElement(JsonNode node, out JsonDocument? backingDocumen

try
{
#pragma warning disable SYSLIB0060 // Type or member is obsolete
node.WriteTo(writer);
#pragma warning restore SYSLIB0060 // Type or member is obsolete
writer.Flush();
Utf8JsonReader reader = new(output.WrittenMemory.Span);
backingDocument = JsonDocument.ParseValue(ref reader);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ public override bool TryGetValue<TypeToConvert>([NotNullWhen(true)] out TypeToCo
return false;
}

[Obsolete(Obsoletions.JsonNodeWriteToMessage, DiagnosticId = Obsoletions.JsonNodeWriteToDiagId)]
public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
{
if (writer is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public JsonValueCustomized(TValue value, JsonTypeInfo<TValue> jsonTypeInfo, Json
private protected override JsonValueKind GetValueKindCore() => _valueKind ??= ComputeValueKind();
internal override JsonNode DeepCloneCore() => JsonSerializer.SerializeToNode(Value, _jsonTypeInfo)!;


[Obsolete(Obsoletions.JsonNodeWriteToMessage, DiagnosticId = Obsoletions.JsonNodeWriteToDiagId)]
public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
{
if (writer is null)
Expand All @@ -52,7 +54,9 @@ private JsonValueKind ComputeValueKind()
Utf8JsonWriter writer = Utf8JsonWriterCache.RentWriterAndBuffer(options: default, JsonSerializerOptions.BufferSizeDefault, out PooledByteBufferWriter output);
try
{
#pragma warning disable SYSLIB0060 // Type or member is obsolete
WriteTo(writer);
#pragma warning restore SYSLIB0060 // Type or member is obsolete
writer.Flush();
Utf8JsonReader reader = new(output.WrittenMemory.Span);
bool success = reader.Read();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ internal override bool DeepEqualsCore(JsonNode otherNode)
return base.DeepEqualsCore(otherNode);
}

[Obsolete(Obsoletions.JsonNodeWriteToMessage, DiagnosticId = Obsoletions.JsonNodeWriteToDiagId)]
public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
{
if (writer is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ public override void Write(Utf8JsonWriter writer, JsonArray? value, JsonSerializ
return;
}

#pragma warning disable SYSLIB0060 // Type or member is obsolete
value.WriteTo(writer, options);
#pragma warning restore SYSLIB0060 // Type or member is obsolete
}

public override JsonArray? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ public override void Write(Utf8JsonWriter writer, JsonNode? value, JsonSerialize
}
else
{
#pragma warning disable SYSLIB0060 // Type or member is obsolete
value.WriteTo(writer, options);
#pragma warning restore SYSLIB0060 // Type or member is obsolete
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ public override void Write(Utf8JsonWriter writer, JsonObject? value, JsonSeriali
return;
}

#pragma warning disable SYSLIB0060 // Type or member is obsolete
value.WriteTo(writer, options);
#pragma warning restore SYSLIB0060 // Type or member is obsolete
}

public override JsonObject? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ public override void Write(Utf8JsonWriter writer, JsonValue? value, JsonSerializ
return;
}

#pragma warning disable SYSLIB0060 // Type or member is obsolete
value.WriteTo(writer, options);
#pragma warning restore SYSLIB0060 // Type or member is obsolete
}

public override JsonValue? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ public static partial class JsonSerializer
}
else
{
#pragma warning disable SYSLIB0060 // Type or member is obsolete
node.WriteTo(writer, options);
#pragma warning restore SYSLIB0060 // Type or member is obsolete
}
}

Expand All @@ -186,7 +188,9 @@ public static partial class JsonSerializer
}
else
{
#pragma warning disable SYSLIB0060 // Type or member is obsolete
node.WriteTo(writer, options);
#pragma warning restore SYSLIB0060 // Type or member is obsolete
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public static void FromElement_WrongNodeTypeThrows(string json)
Assert.Throws<InvalidOperationException>(() => JsonArray.Create(document.RootElement));
}

#pragma warning disable SYSLIB0060 // Type or member is obsolete
[Fact]
public static void WriteTo_Validation()
{
Expand Down Expand Up @@ -104,6 +105,7 @@ public static void WriteTo_Options()
json = Encoding.UTF8.GetString(stream.ToArray());
Assert.Equal("[\"42\"]", json);
}
#pragma warning restore SYSLIB0060 // Type or member is obsolete

[Fact]
public static void Clear()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ public static void FromElement_WrongNodeTypeThrows(string json)
}
}

#pragma warning disable SYSLIB0060 // Type or member is obsolete
[Fact]
public static void WriteTo_Validation()
{
Expand All @@ -346,6 +347,7 @@ public static void WriteTo()
string json = Encoding.UTF8.GetString(stream.ToArray());
Assert.Equal(Json, json);
}
#pragma warning restore SYSLIB0060 // Type or member is obsolete

[Fact]
public static void CopyTo()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ public static void FromElement_WrongNodeTypeThrows(string json)
Assert.Throws<InvalidOperationException>(() => JsonValue.Create(document.RootElement));
}

#pragma warning disable SYSLIB0060 // Type or member is obsolete
[Fact]
public static void WriteTo_Validation()
{
Expand All @@ -308,6 +309,7 @@ public static void WriteTo()
string json = Encoding.UTF8.GetString(stream.ToArray());
Assert.Equal(Json, json);
}
#pragma warning restore SYSLIB0060 // Type or member is obsolete

[Fact]
public static void DeepCloneNotTrimmable()
Expand Down
Loading