17

Consider the code...

using System;
using System.Text.Json;

public class Program
{
    public static void Main()
    {
        int id = 9;
        string str = "{\"id\": " + id + "}";
        var u = JsonSerializer.Deserialize<User>(str);
        Console.WriteLine($"User ID: {u.Id}, Correct: {id == u.Id}");  // always 0/init/default value
    }
}


public class User {
    public int Id { get; set; }
}

Why isn't the data being deserialized properly into the User object? I also verified the behavior via DotNetFiddle in case it was an issue local to my system. No exception is thrown.

My actual implementation is read from an [ApiController]'s [HttpPost] action after I return Created("user", newUser). It is called in my MVC/Razor project via _httpClient.PostAsync. I verified the values are correct when Created is returned to the PostAsync call, but no matter what, the value parsed from the response body contains only default values (the actual ID is a Guid).

I initially thought it might have been an UTF8 related issue, as that is the encoding for the StringContent I post to the ApiController. UTF8 deserialization is referenced here, but I had trouble getting from the IO.Stream of the HttpContent to a ReadOnlySpan or Utf8JsonReader.

I found this project while searching, which makes me think it should work as I expected.

dbc
  • 104,963
  • 20
  • 228
  • 340
t.j.
  • 1,227
  • 3
  • 16
  • 30
  • 1
    It is always a good idea to serialize a sample first to see a sample. I did it and found out that it was looking for `Id` instead of `id` it couldn't find it so it takes the default value `0` – Nin Feb 08 '20 at 03:00

4 Answers4

44

Your problem is that System.Text.Json is case-sensitive by default, so "id": 9 (all lowercase) is not mapped to the Id property. From the docs:

Case-insensitive property matching

By default, deserialization looks for case-sensitive property name matches between JSON and the target object properties. To change that behavior, set JsonSerializerOptions.PropertyNameCaseInsensitive to true:

Note: The web default is case-insensitive.

var options = new JsonSerializerOptions
{
   PropertyNameCaseInsensitive = true,
};
var weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString, options);

So you need to do that also:

var u = JsonSerializer.Deserialize<User>(str, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

Demo fiddle #1 here.

(If the difference is entirely due to camel casing and not more general differences in case, you can instead configure the serializer to use camel case as shown in this answer by t.j..)

You can configure the option on startup in ASP.NET Core 3.0 as shown in How to set json serializer settings in asp.net core 3?:

services.AddControllers().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
});

Alternatively you could apply [JsonPropertyName("id")] to your model:

public class User {
    [JsonPropertyName("id")]
    public int Id { get; set; }
}

Demo fiddle #2 here.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • 1
    correct, plus extra useful info regarding config... marking as solution – t.j. Feb 08 '20 at 03:00
  • 1
    If you expect all properties in the JSON to be camel-cased instead, an alternative to using `PropertyNameCaseInsensitive` is to set the `PropertyNamingPolicy` to `CamelCase` explicitly and leave the comparison as the default/case-sensitive. – ahsonkhan Feb 08 '20 at 04:49
  • 1
    Using `JsonNamingPolicy.CamelCase` is generally more performant (with case sensitive comparison) compared to opting for case insensitive comparison. Otherwise, setting both, like asp.net core does, works as well. – ahsonkhan Feb 08 '20 at 04:58
  • 1
    in startup this should be like this or will give error options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; – Ali Karaca Feb 02 '21 at 23:30
  • 1
    I've lost 3 hours trying to understand why it doesn't work, and it was all for case sensitivity . Thanks man – Андрей Голубцов Feb 03 '21 at 20:34
  • In my case i removed getter and setters if that help anyone! cheers – Haseeb Mir May 16 '21 at 20:09
  • So, I'm an idiot. I had this same problem but turns out I had `private set` on the properties I was trying to update while attempting deserialization from a different class! – UndeadBob Jul 27 '22 at 16:45
3

Thanks to mr5 who suggested it was a casing issue via chat.

Changing the string to use TitleCase ("Id") solves the issue.

I was in the process of submitting a ticket, and one of the possibly related issues comments lead me to another issue, which lead to the documentation, which has a solution

var options = new JsonSerializerOptions();
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;

Using the options, solves the problem...

string str = "{\"id\": " + id + "}";
var options = new JsonSerializerOptions();
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
var u = JsonSerializer.Deserialize<User>(str, options);

Leaving this all up in case it helps someone else.

t.j.
  • 1,227
  • 3
  • 16
  • 30
  • The doc pages might be helpful as well. Specifically https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#case-insensitive-deserialization Regarding the discussion from the chat (quoted here: "weird that it isnt an issue elsewhere ([FromBody] handles it just fine"), that's because of the default options are configured differently within aspnet: https://github.com/dotnet/aspnetcore/blob/a6bc6ce23dad5a9d02596cf2e91e3c4f965c61bc/src/Mvc/Mvc.Core/src/JsonOptions.cs#L15-L29 – ahsonkhan Feb 08 '20 at 04:54
1

You can also read this microsoft documentation JSON Serialization, for the configuration you can use the following:

JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);
var serialized = JsonSerializer.Serialize(data, _jsonOptions);
var deserialized = JsonSerializer.Deserialize<TEntity>(serialized , _jsonOptions);

JsonSerializerDefaults.Web it is a predefined enum setting like "camelCase value", "case-insensitive property names", etc, check this JsonSerializerDefaults.? , if you want to customize you would check the documentation again here

Kenny Kanp
  • 175
  • 2
  • 5
-1

In ConfigureServices at Startup.cs

services.AddControllers()
        .AddJsonOptions(o => {
            o.JsonSerializerOptions.PropertyNamingPolicy=JsonNamingPolicy.CamelCase;
            o.PropertyNameCasInsensitive=true
        });
Will Marcouiller
  • 23,773
  • 22
  • 96
  • 162
Rdwan Alali
  • 149
  • 2
  • 1
  • PropertyNameCasInsensitive has a typo and isn't found directly in o but rather in JsonSerializerOptions so it should be o.JsonSerializerOptions.PropertyNameCaseInsensitive – Marc Roussel Feb 01 '21 at 18:44