Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deserialize into private fields in .NET 8?

By reading the announcements and documentation regarding .NET 8 serialization I was under the impression that we could serialize into private fields.

I was trying to do the following:

public class User
{
    [JsonInclude]
    [JsonPropertyName("name")]
    private string _name = null!;
    
    public int Age { get; }
            
    [JsonConstructor]
    private User(string name, int age)
    {
        _name = name;
        Age = age;
    }
    
    private User(){}

}

...
var userStr = @"{""name"": ""juan"", ""age"": 31}";
var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true,
    IncludeFields = true,
    IgnoreReadOnlyFields = false
};
var user = JsonSerializer.Deserialize<User>(userStr, options)!;

Which throws

Unhandled exception. System.InvalidOperationException: Each parameter in the deserialization constructor on type 'User' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. Fields are only considered when 'JsonSerializerOptions.IncludeFields' is enabled. The match can be case-insensitive.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_ConstructorParameterIncompleteBinding(Type parentType)...

Changing the private field to a property which exposes a getter like public string Name { get; } works, but the thing is I wanted it to be a private field. In my real scenario I have a private List<T> but i just expose a public IReadOnlyCollection<T> whose getter returns the list as readonly.

Is there a way to tell the serialization to use the private field?

like image 888
Juan Francisco Porto Avatar asked Oct 31 '25 08:10

Juan Francisco Porto


1 Answers

The problem is not with properties/fields but with constructor parameter names, they should match corresponding properties/fields names. Change ctor to:

[JsonConstructor]
private User(string _name, int age)
{
    this._name = _name;
    Age = age;
}

From the Use immutable types and properties: Parameterized constructors doc:

The parameter names of a parameterized constructor must match the property names and types. Matching is case-insensitive, and the constructor parameter must match the actual property name even if you use [JsonPropertyName] to rename a property.

Note that adding the JsonIncludeAttribute should be enough. If you can make your Age property init-only then you can completely skip the constructor:

public class User1
{
    [JsonInclude]
    [JsonPropertyName("name")]
    private string _name = null!;
    
    public int Age { get; init; }

    public override string ToString() => $"{_name}: {Age}";
}

var userStr = @"{""name"": ""juan"", ""age"": 31}";
var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true,
};
var user = JsonSerializer.Deserialize<User1>(userStr, options)!;

Console.WriteLine(user); // prints "juan: 31"
like image 90
Guru Stron Avatar answered Nov 03 '25 00:11

Guru Stron



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!