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?
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"
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With