Skip to content
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

Behavior difference in JsonSerializer between .NET7 and .NET8 p4 #86419

Closed
meziantou opened this issue May 18, 2023 · 5 comments
Closed

Behavior difference in JsonSerializer between .NET7 and .NET8 p4 #86419

meziantou opened this issue May 18, 2023 · 5 comments
Assignees
Labels
area-System.Text.Json breaking-change Issue or PR that represents a breaking API or functional change over a prerelease. documentation Documentation bug or enhancement, does not impact product or test code
Milestone

Comments

@meziantou
Copy link
Contributor

While testing .NET 8, I got an exception when deserializing a json payload. Here's a repro code:

var obj = JsonSerializer.Deserialize<Sample>("""{ "A": 1 }""");
Console.WriteLine(obj);

record Sample
{
    public int A { get; set; }

    // Encoding is not serializable, but it is never set in the json data
    public Encoding? Dummy { get; set; }
}
  • .NET 7: The data is deserialized Sample { A = 1, Dummy = }
  • .NET 8: System.InvalidOperationException: The type 'System.ReadOnlySpan`1[System.Byte]' of property 'Preamble' on type 'System.Text.Encoding' is invalid for serialization or deserialization because it is a pointer type, is a ref struct, or contains generic parameters that have not been replaced by specific types.
full stacktrace
System.InvalidOperationException: The type 'System.ReadOnlySpan`1[System.Byte]' of property 'Preamble' on type 'System.Text.Encoding' is invalid for serialization or deserialization because it is a pointer type, is a ref struct, or contains generic parameters that have not been replaced by specific types.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_CannotSerializeInvalidType(Type typeToConvert, Type declaringType, MemberInfo memberInfo)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.CreatePropertyInfo(JsonTypeInfo typeInfo, Type typeToConvert, MemberInfo memberInfo, JsonSerializerOptions options, Boolean shouldCheckForRequiredKeyword)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.AddMembersDeclaredBySuperType(JsonTypeInfo typeInfo, Type currentType, Boolean shouldCheckMembersForRequiredMemberAttribute, PropertyHierarchyResolutionState& state)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.PopulateProperties(JsonTypeInfo typeInfo)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.CreateTypeInfoCore(Type type, JsonConverter converter, JsonSerializerOptions options)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.GetTypeInfo(Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoNoCaching(Type type)
   at System.Text.Json.JsonSerializerOptions.CachingContext.CreateCacheEntry(Type type, CachingContext context)
--- End of stack trace from previous location ---
   at System.Text.Json.JsonSerializerOptions.CachingContext.CacheEntry.GetResult()
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable`1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.Configure()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.ConfigureProperties()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|165_0()
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable`1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoForRootType(Type type, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.JsonSerializer.GetTypeInfo[T](JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   at Program.<Main>$(String[] args) in C:\Users\dummy\source\repos\ConsoleApp1\ConsoleApp1\Program.cs:line 6

The fix is to decorate the unused property with [JsonIgnore].

I don't know if this is a bug or expected behavior (both would make sense). If this is the expected behavior, it would be worth adding a note in the Breaking change section.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label May 18, 2023
@ghost
Copy link

ghost commented May 18, 2023

Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis
See info in area-owners.md if you want to be subscribed.

Issue Details

While testing .NET 8, I got an exception when deserializing a json payload. Here's a repro code:

var obj = JsonSerializer.Deserialize<Sample>("""{ "A": 1 }""");
Console.WriteLine(obj);

record Sample
{
    public int A { get; set; }

    // Encoding is not serializable, but it is never set in the json data
    public Encoding? Dummy { get; set; }
}
  • .NET 7: The data is deserialized Sample { A = 1, Dummy = }
  • .NET 8: System.InvalidOperationException: The type 'System.ReadOnlySpan`1[System.Byte]' of property 'Preamble' on type 'System.Text.Encoding' is invalid for serialization or deserialization because it is a pointer type, is a ref struct, or contains generic parameters that have not been replaced by specific types.
full stacktrace
System.InvalidOperationException: The type 'System.ReadOnlySpan`1[System.Byte]' of property 'Preamble' on type 'System.Text.Encoding' is invalid for serialization or deserialization because it is a pointer type, is a ref struct, or contains generic parameters that have not been replaced by specific types.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_CannotSerializeInvalidType(Type typeToConvert, Type declaringType, MemberInfo memberInfo)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.CreatePropertyInfo(JsonTypeInfo typeInfo, Type typeToConvert, MemberInfo memberInfo, JsonSerializerOptions options, Boolean shouldCheckForRequiredKeyword)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.AddMembersDeclaredBySuperType(JsonTypeInfo typeInfo, Type currentType, Boolean shouldCheckMembersForRequiredMemberAttribute, PropertyHierarchyResolutionState& state)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.PopulateProperties(JsonTypeInfo typeInfo)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.CreateTypeInfoCore(Type type, JsonConverter converter, JsonSerializerOptions options)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.GetTypeInfo(Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoNoCaching(Type type)
   at System.Text.Json.JsonSerializerOptions.CachingContext.CreateCacheEntry(Type type, CachingContext context)
--- End of stack trace from previous location ---
   at System.Text.Json.JsonSerializerOptions.CachingContext.CacheEntry.GetResult()
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable`1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.Configure()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.ConfigureProperties()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|165_0()
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable`1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoForRootType(Type type, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.JsonSerializer.GetTypeInfo[T](JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   at Program.<Main>$(String[] args) in C:\Users\dummy\source\repos\ConsoleApp1\ConsoleApp1\Program.cs:line 6

The fix is to decorate the unused property with [JsonIgnore].

I don't know if this is a bug or expected behavior (both would make sense). If this is the expected behavior, it would be worth adding a note in the Breaking change section.

Author: meziantou
Assignees: -
Labels:

area-System.Text.Json

Milestone: -

@meziantou meziantou changed the title Change of behavior in JsonSerializer between .NET7 and .NET8 p4 Behavior difference in JsonSerializer between .NET7 and .NET8 p4 May 18, 2023
@eiriktsarpalis
Copy link
Member

This was introduced intentionally in #81576, in order to support #71933. This essentially forces eager evaluation of the entire type graph before serialization begins, irrespective of what data is present in the deserialized payload. I had not anticipated that users might have taken a dependency on that behavior, since it should be pointed out that serializing such a type will always fail even in .NET 7.

I don't know if this is a bug or expected behavior (both would make sense). If this is the expected behavior, it would be worth adding a note in the Breaking change section.

That's an excellent point, I'll make sure that this is noted adequately in the upcoming release's documentation.

@eiriktsarpalis eiriktsarpalis added breaking-change Issue or PR that represents a breaking API or functional change over a prerelease. and removed untriaged New issue has not been triaged by the area owner labels May 18, 2023
@eiriktsarpalis eiriktsarpalis added this to the 8.0.0 milestone May 18, 2023
@eiriktsarpalis eiriktsarpalis self-assigned this May 18, 2023
@ghost ghost added the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label May 18, 2023
@ghost
Copy link

ghost commented May 18, 2023

Added needs-breaking-change-doc-created label because this issue has the breaking-change label.

  1. Create and link to this issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.

Tagging @dotnet/compat for awareness of the breaking change.

@krwq
Copy link
Member

krwq commented May 18, 2023

I suggest to use JsonConverter for encoding (i.e. you can serialize it as int - code page, or string - encoding name, i.e. en-US).

@eiriktsarpalis
Copy link
Member

eiriktsarpalis commented Sep 11, 2023

Breaking change issue filed, we can now close this issue.

dotnet/docs#37041

@eiriktsarpalis eiriktsarpalis removed the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label Sep 11, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Oct 11, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Text.Json breaking-change Issue or PR that represents a breaking API or functional change over a prerelease. documentation Documentation bug or enhancement, does not impact product or test code
Projects
None yet
Development

No branches or pull requests

4 participants