Given this model class:
using System.Text.Json;
public class QueueMessage
{
public int MerchantId { get; }
public string Message { get; }
}
When I try to deserialize a json string into the type QueueMessage, the fields are set to default. 0 and null. This is how I've tried to deserialize it:
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
I'm using .Net 5 by the way.
What have I tried Well i try to use my ol' buddy Newtonsoft.Json
dynamic message = JsonConvert.DeserializeObject(jsonString);
dynamic mercId = message.MerchantId.Value; //THIS gives the expected value of 2
However,
QueueMessage msg = JsonConvert.DeserializeObject<QueueMessage>(jsonString);
Gives the 0, null result
Now, the question is why does deserializing to a typed object fail?
CodePudding user response:
You need to add setters to your properties, otherwise the deserializer can't assign values to those properties
public class QueueMessage
{
public int MerchantId { get; set; }
public string Message { get; set; }
}
CodePudding user response:
As an alternative to Ziv's answer, and if you value the benefits of constructed types and/or immutability, recent versions of
System.Text.Json
now support\[JsonConstructor\]
, so you can now use constructed DTO types.In my opinion you should always also specify an explicit
[JsonPropertyName]
to protect against DTO/JSON breakages caused by renamed properties or changing project-widecamelCase
vsPascalCase
JSON serialization settings.- It's also my opinion that JSON object members should always use
camelCase
, notPascalCase
as that's the capitalization convention used by JavaScript/TypeScript, which is the usual target for JSON types (unfortunately STJ defaults toPascalCase
(*grrrr*))
- It's also my opinion that JSON object members should always use
Example 1: With a simple constructor
- ...though this isn't a good example as it doesn't show the ctor actually doing anything useful.
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; }
}
Example 2: With validating constructor
- Because the constructor can make assertions and validate parameters it means the ctor can prevent
QueueMessage.Message
from ever beingnull
, so theString?
property can be changed toString
which makes consuming the DTO nicer - and it can also validateMerchantId
too:
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 >= 1.</summary>
[JsonPropertyName("merchantId")] public int MerchantId { get; }
/// <summary>Never null.</summary>
[JsonPropertyName("message") ] public string Message { get; }
}
Example 3: With record class
- To keep things simpler, you could also just use a
record class
.- Though this loses the validation logic, so
Message
is nowString?
again.
- Though this loses the validation logic, so
- Use the syntax
[property: ]
to applyJsonPropertyName
and other attributes torecord class
properties via their ctor parameter names.
public record class QueueMessage(
[property: JsonPropertyName("merchantId")] int MerchantId,
[property: JsonPropertyName("message") ] string? Message
);