Home > Software engineering >  C# Why does System.Text.Json doesnt deserialise string
C# Why does System.Text.Json doesnt deserialise string

Time:03-14

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-wide camelCase vs PascalCase JSON serialization settings.

    • It's also my opinion that JSON object members should always use camelCase, not PascalCase as that's the capitalization convention used by JavaScript/TypeScript, which is the usual target for JSON types (unfortunately STJ defaults to PascalCase (*grrrr*))

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 being null, so the String? property can be changed to String which makes consuming the DTO nicer - and it can also validate MerchantId 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 &gt;= 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 now String? again.
  • Use the syntax [property: ] to apply JsonPropertyName and other attributes to record class properties via their ctor parameter names.
public record class QueueMessage(
    [property: JsonPropertyName("merchantId")] int     MerchantId,
    [property: JsonPropertyName("message")   ] string? Message
);
  • Related