.NET Core v3.x 以降の MVC や Web API アプリで .NET オブジェクトを JSON 文字列にシリアライズすると、以下の画像のように {"name":"value"} の "name" の先頭の文字が小文字になってしまうこと、さらにそれによりにデシリアライズに問題が出ること、その対処方法について書きます。
.NET Core MVC や Web API では、Core v3.x 以降 System.Text.Json 名前空間の JsonSerializer クラスが JSON のシリアライズ/デシリアライズに用いられるようになったそうです。Core v2.x 以前は Newtonsoft.Json が使われていたという違いがあります。
上の画像の例ではシリアライズ対象のオブジェクトのクラス定義は以下のようになっており、プロパティ名の先頭は大文字ですが、JsonSerializer クラスを使ってシリアライズした結果の JSON 文字列では上の画像の通り "name" が "firstName", "lastName" と先頭の文字が小文字になっている点に注目してください。
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
(上の画像で "value" が "\u592A\u90CE", "\u65E5\u672C" となっているのは、それぞれ "太郎", "日本" の Unicode が Unicode Escape Sequence という形にエスケープされた結果です。詳しくは先の記事「ASP.NET Core MVC の JSON シリアライズ」を見てください)
先頭文字が小文字になるのは Camel Casing と言って、変数などを camelCasing というように記述するものだそうです。(先頭が大文字、即ち CamelCasing というように書く場合もあるはずですがそこはちょっと置いときます)
なお、コンソールアプリで JsonSerializer クラスを使って以下のようにした場合は "name" は "FirstName", "LastName" となり、先頭文字が小文字になることはありません。ということは、MVC や Web API のフレームワークで Camel Casing にする設定がされているようです。
namespace ConsoleAppJson
{
class Program
{
static void Main(string[] args)
{
var person = new Person
{
FirstName = "太郎",
LastName = "日本"
};
string json = JsonSerializer.Serialize(person);
Console.WriteLine(json);
}
}
}
"name" の先頭文字が小文字になると、例えば firstName となると困るのは、JSON 文字列を JavaScript オブジェクトにデシリアライズした後 "太郎" という値を取得するには <javascript object>.firstName というようにしなければならず、それを忘れて FirstName を使うと結果は undefined になってしまうことです。
さらに困るのは、 JSON 文字列を元の Person クラス(プロパティの先頭文字が大文字)にデシリアライズする場合です。JsonSerializer クラスのデシリアライザはデフォルトでは大文字小文字の区別をするのでデシリアライズできないという結果になります。(エラーは出ません。Person クラスを例に取ると FirstName, LastName プロパティが null になります)
シリアライズの際 Camel Casing を避ける("name" の先頭文字が小文字にならないようにする)方法ですが、Startup.cs で JsonSerializerOptions の PropertyNamingPolicy プロパティを null に設定してやることで可能です。
具体的には、Visual Studio のテンプレートで ASP.NET Core MVC / Web API プロジェクトを作成すると自動生成される Startup.cs のコードの中に services.AddControllersWithViews(); が含まれているはずなので、それに以下のように .AddJsonOptions(opttions => ... 以下のコードを追加します。
services.AddControllersWithViews().AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
これで以下のような MVC アクションメソッドの Controller.Json メソッドによる JSON シリアライズでも、
public IActionResult Json()
{
var person = new Person
{
FirstName = "太郎",
LastName = "日本"
};
return Json(person);
}
以下のような Web API のアクションメソッドによる JSON シリアライズでも、
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
public Person GetPerson()
{
var person = new Person
{
FirstName = "太郎",
LastName = "日本"
};
return person;
}
}
シリアライズされた結果の JSON 文字列 {"name":"value"} の "name" は、元の Person クラスのプロパティ名通り "FirstName", "LastName" となります。
(アクションメソッド個別に対応する場合は、MVC の場合は Controller.Json メソッドの第 2 引数に JsonSerializerOptions を設定することで可能です。Web API の場合は JsonSerializer.Deserialize メソッドを使って第 2 引数に JsonSerializerOptions を設定することで可能です)
さらに、"value" が Unicode Escape Sequence という形になるのを避けたいのであれば、options の設定に以下を追加すれば OK です。(セキュリティ上それが良いのかどうかの話は置いといてですが)
options.JsonSerializerOptions.Encoder =
JavaScriptEncoder.Create(UnicodeRanges.All);
最後にもう一つ、上に書いたデシリアライズする際の大文字小文字の区別の問題を回避する方法を書いておきます。これも JsonSerializerOptions の設定で可能で、以下のようにすれば大文字小文字の違いは無視します。(Newtonsoft.Jason と同じ結果になります)
Person person = JsonSerializer.Deserialize<Person>(json,
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});