C# 为什么 System.Text.Json 不反序列化字符串

C# Why does System.Text.Json doesnt deserialise string

鉴于此模型 class:

    using System.Text.Json;
    public class QueueMessage
    {
        public int MerchantId { get; }
        public string Message { get; }
    }

当我尝试将 json 字符串反序列化为 QueueMessage 类型时,字段设置为默认值。 0 和空。 这就是我试图反序列化它的方式:

var jsonString = "{\"MerchantId\":2,\"Message\":\"Message 2\"}";
QueueMessage message  = JsonSerializer.Deserialize<QueueMessage>(jsonString, new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true
        });

message.Message is null
message.MerchantId is 0

顺便说一句,我正在使用.Net 5。

我尝试了什么 好吧,我尝试使用我的好友 Newtonsoft.Json

dynamic message = JsonConvert.DeserializeObject(jsonString);
dynamic mercId = message.MerchantId.Value; //THIS gives the expected value of 2

然而,

QueueMessage msg = JsonConvert.DeserializeObject<QueueMessage>(jsonString);

给出 0,空结果

现在,问题是为什么反序列化为类型对象会失败?

您需要为您的属性添加 setter,否则反序列化器无法为这些属性赋值

public class QueueMessage
{
    public int MerchantId { get; set; }
    public string Message { get; set; }
}
  • 作为 Ziv 答案的替代方案,如果您重视构造类型 and/or 不变性的好处,System.Text.Json now support \[JsonConstructor\], so you can now use constructed DTO types.[=35 的最新版本=]

  • 在我看来,你应该 always 也指定一个明确的 [JsonPropertyName] 来防止 DTO/JSON 由重命名属性或更改引起的损坏project-wide camelCase 对比 PascalCase JSON 序列化设置。

    • 我还认为 JSON 对象成员应该始终使用 camelCase,而不是 PascalCase,因为这是 JavaScript/TypeScript 使用的大写约定,这是通常的目标对于 JSON 类型(unfortunately STJ defaultsPascalCase (*grrrr*))

示例 1:使用简单的构造函数

  • ...虽然这不是一个很好的例子,因为它没有显示 ctor 实际上做了任何有用的事情。
using System.Text.Json;

public class QueueMessage
{
    [JsonConstructor]
    public QueueMessage(
        int     merchantId,
        string? message
    )
    {
        this.MerchantId = merchantId;
        this.Message    = message;
    }

    [JsonPropertyName("merchantId")] public int     MerchantId { get; }
    [JsonPropertyName("message")   ] public string? Message    { get; }
}

示例 2:使用验证构造函数

  • 因为构造函数可以进行断言和验证参数,这意味着 ctor 可以防止 QueueMessage.Message 成为 null,所以 String? 属性 可以更改为String 这使得使用 DTO 更好 - 它也可以验证 MerchantId
using System.Text.Json;

public class QueueMessage
{
    [JsonConstructor]
    public QueueMessage(
        int    merchantId,
        string message
    )
    {
        this.MerchantId = merchantId > 0 ? merchantId : throw new ArgumentOutOfRangeException( paramName: nameof(merchantId), actualValue: merchantId, message: "Value must be positive and non-zero." );
        this.Message    = message ?? throw new ArgumentNullException(nameof(message));
    }

    /// <summary>Always &gt;= 1.</summary>
    [JsonPropertyName("merchantId")] public int    MerchantId { get; }

    /// <summary>Never null.</summary>
    [JsonPropertyName("message")   ] public string Message    { get; }
}

示例 3:使用 record class

  • 为了简单起见,您也可以只使用 record class
    • 虽然这失去了验证逻辑,所以 Message 现在又是 String?
  • 使用语法 [property: ] 通过构造函数参数名称将 JsonPropertyName 和其他属性应用于 record class 属性。
public record class QueueMessage(
    [property: JsonPropertyName("merchantId")] int     MerchantId,
    [property: JsonPropertyName("message")   ] string? Message
);