-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
In the current design, JsonValue<TValue> can take any TValue type, and it passes the value into JsonSerializer.Serialize. However, it doesn't allow for a source generated JsonTypeInfo to be passed into Serialize.
This is a problem because using JsonSerializer.Serialize without a JsonTypeInfo is not trim-compatible. This is because of the recursive use of Reflection that the trimming tool can't reason about.
In most cases, JsonValue is for primitive types, which are intrinsically handled by the serializer without needing reflection. However, because we don't limit the types that can be passed into JsonValue<T>, we need to mark the JsonValue<TValue>.WriteTo method as RequiresUnreferencedCode:
runtime/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonValueOfT.cs
Lines 85 to 92 in f372f6b
| if (_value is JsonElement jsonElement) | |
| { | |
| jsonElement.WriteTo(writer); | |
| } | |
| else | |
| { | |
| JsonSerializer.Serialize(writer, _value, _value!.GetType(), options); | |
| } |
But we can't just mark WriteTo as unsafe, because this method is called from the ToString():
runtime/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.To.cs
Lines 30 to 56 in f372f6b
| public override string ToString() | |
| { | |
| // Special case for string; don't quote it. | |
| if (this is JsonValue) | |
| { | |
| if (this is JsonValue<string> jsonString) | |
| { | |
| return jsonString.Value; | |
| } | |
| if (this is JsonValue<JsonElement> jsonElement && | |
| jsonElement.Value.ValueKind == JsonValueKind.String) | |
| { | |
| return jsonElement.Value.GetString()!; | |
| } | |
| } | |
| using (var output = new PooledByteBufferWriter(JsonSerializerOptions.BufferSizeDefault)) | |
| { | |
| using (var writer = new Utf8JsonWriter(output, new JsonWriterOptions { Indented = true })) | |
| { | |
| WriteTo(writer); | |
| } | |
| return JsonHelpers.Utf8GetString(output.WrittenMemory.ToArray()); | |
| } | |
| } |
Which we can't mark ToString() as RequiresUnreferencedCode, since that would mean the base Object.ToString() and every overridden ToString() method would need to be annotated the same.
To resolve this, I can see the following options:
- Limit
JsonValue<TValue>to only primitive types. Don't allow for custom defined types. This would allow us to suppress the warning, since primitive types can be serialized safely in a trimmed application. - We make constructing
JsonValue<TValue>with a custom defined type asRequiresUnreferencedCode. This way, it lets callers who createJsonValueinstances know that they are using a type that is not trim compatible. Then, we can specific APIs for primitive types, and also add an API to create aJsonValue<TValue>that takes aJsonTypeInfoto use when serializing the value. These will be considered "trim compatible", since it doesn't use the reflection code path. The only thing that will beRequiresUnreferencedCodeis creating aJsonValue<TValue>with a custom type, without passing in aJsonTypeInfo. This mirrors theJsonSerializerAPIs.