Skip to content

Support safe trimmability for System.Text.Json.Nodes.JsonValue #52773

@steveharter

Description

@steveharter

Background and Motivation

The existing JsonValue class has a factory method like:

public static JsonValue? Create<T>(T? value, JsonNodeOptions? options = null);

When called with a primitive such as an Int32 or string this method is safe for trimming. However, this method also supports any serializable CLR type including custom data types, anonymous types and even POCOs and collections, so it is unsafe for trimming since it needs to call the serializer.

Separating the safe vs. unsafe creation is necessary to avoid having to add unsafe trimming attributes to this method.

See this issue for more information: #52002

Proposed API

namespace System.Text.Json.Nodes
{
  public partial class JsonValue
  {
    // Here is the safe version that supports JsonTypeInfo with source-generated types:
+  public static JsonValue? Create<T>(T? value, JsonTypeInfo<T> jsonTypeInfo, JsonNodeOptions? options = null)

    // Here is the unsafe version with a name that makes that clear:
-  public static JsonValue? Create<T>(T? value, JsonNodeOptions? options = null)
+  public static JsonValue? CreateFromSerializableValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)]T>(T? value, JsonNodeOptions? options = null)
  }

    // To maintain compatibility with the removed `Create<T>`, here are safe overloads for each supported primitive value:
+  public static JsonValue Create(bool value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue Create(byte value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue Create(char value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue Create(System.DateTime value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue Create(System.DateTimeOffset value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue Create(decimal value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue Create(double value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue Create(System.Guid value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue Create(short value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue Create(int value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue Create(long value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(bool? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(byte? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(char? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(System.DateTimeOffset? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(System.DateTime? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(decimal? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(double? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(System.Guid? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(short? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(int? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(long? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  [System.CLSCompliantAttribute(false)]
+  public static JsonValue? Create(sbyte? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(float? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(System.Text.Json.JsonElement? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  [System.CLSCompliantAttribute(false)]
+  public static JsonValue? Create(ushort? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  [System.CLSCompliantAttribute(false)]
+  public static JsonValue? Create(uint? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  [System.CLSCompliantAttribute(false)]
+  public static JsonValue? Create(ulong? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  [System.CLSCompliantAttribute(false)]
+  public static JsonValue Create(sbyte value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue Create(float value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(string? value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  public static JsonValue? Create(System.Text.Json.JsonElement value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  [System.CLSCompliantAttribute(false)]
+  public static JsonValue Create(ushort value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  [System.CLSCompliantAttribute(false)]
+  public static JsonValue Create(uint value, JsonNodeOptions? options = default(JsonNodeOptions?));
+  [System.CLSCompliantAttribute(false)]
+  public static JsonValue Create(ulong value, JsonNodeOptions? options = default(JsonNodeOptions?));
}

In addition, operators are added to support JsonElement which is the only supported type that did not have operators yet:

namespace System.Text.Json.Nodes
{
  public partial class JsonNode
  {
+  public static implicit operator JsonNode? (System.Text.Json.JsonElement value);
+  public static implicit operator JsonNode? (System.Text.Json.JsonElement? value);
+  public static explicit operator System.Text.Json.JsonElement (JsonNode value);
+  public static explicit operator System.Text.Json.JsonElement? (JsonNode? value);
  }
}

Note that JsonElement is handled specially: an internal value of JsonValueKind.Null is treated as a null JsonValue. This is required since JsonValue is based on JsonElement after a Parse() and treats null JSON values as a null CLR value.

Alternative Designs

Only supporting primitive types and not custom data types, anonymous types, etc would prevent the need for CreateFromSerializableValue() but we would still likely want the various new Create() overloads for each supported value type to make the expected types known at compile-time (instead of throwing at run-time). If we did not support custom data types, etc then users would need to:

  • Use the serializer and create POCOs (not always feasible)
  • Create helper methods to generate JSON from a custom data type, etc.

These may include invoking the non-trimmable serializer APIs and thus doesn't really help from a safe trimability perspective.

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.Text.JsonblockingMarks issues that we want to fast track in order to unblock other important worklinkable-frameworkIssues associated with delivering a linker friendly framework

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions