WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

JsonSerializer の Camel Casing (CORE)

by WebSurfer 2021年1月18日 21:18

.NET Core v3.x 以降の MVC や Web API アプリで .NET オブジェクトを JSON 文字列にシリアライズすると、以下の画像のように {"name":"value"} の "name" の先頭の文字が小文字になってしまうこと、さらにそれによりにデシリアライズに問題が出ること、その対処方法について書きます。

JSON 文字列

.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
                });

Tags: , , ,

CORE

Web API と要求ヘッダの application/xml

by WebSurfer 2020年6月7日 14:12

.NET Framework 版の ASP.NET Web API を要求する際、要求ヘッダの Accept に application/xml が含まれると応答は JSON ではなく XML になります。

ASP.NET Web API の応答

知ってました? 実は自分は知らなかったです。(汗) ちなみに、MVC の Json メソッド、および Core 3.1 Web API と MVC の Json メソッドは、要求ヘッダの Accept に application/xml が含まれていても JSON が返ってきます。

Web API の応答をチェックするには Postman などのツールを使うのが一般的なようです。でも、GET 要求ならブラウザのアドレスバーに直接 URL を入力し���応答を見ることでも十分だったので自分は IE11 を使ってそうしてました。それで期待した通り JSON 文字列が返ってきてました。

ところが、Chrome, Edge, Firefox では応答が JSON ではなく XML になってしまいます。理由は要求ヘッダの Accept に application/xml が含まれるからのようです。ハマったのは上の画像のようなサーバーエラーとなってしまったことです(HTTP 500 応答が返ってきます。上の画像は形式は XML ですが中身はエラーメッセージです)。

エラーメッセージ "The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'." によるとシリアライズに失敗したということです。

IE11 や jQuery ajax を使った検証ではシリアライズには問題はないことは確認しており、ブラウザに Chrome などを使ったからと言ってそれがシリアライズの部分に影響があるとは考えられなかったです。何故なのでしょう?

循環参照の問題でした。

上の画像の InnerException のエラーメッセージにある Blog_E36679EB... D5AD0 という文字列が、先の記事「JSON シリアライズの際の循環参照エラー」にもあったのを思い出して気が付きました。

.NET Framework 版の ASP.NET Web API の JSON シリアライザは Newtonsoft の Json.NET のもので、JsonIgnoreAttribute クラスという属性が利用できます。なので、問題のプロパティに [JsonIgnore] を付与してシリアライズの際の循環参照エラーは回避していました。

でも [JsonIgnore] が有効なのは JSON にシリアライズするときのみで、XML にシリアライズするときは循環参照の問題は回避できません。

Chrome, Edge などを使った時は、ASP.NET Web API は要求ヘッダの Accept に application/xml が含まれるのを見て、XML にシリアライズしようとして循環参照の問題に陥ったということのようです。

Tags: , , ,

Web API

コレクションを表現した JSON のデシリアライズ

by WebSurfer 2020年6月3日 15:49

以下のようなコレクション(配列)を表現した JSON 文字列を C# のオブジェクトにデシリアライズするときにハマった話を書きます。

こんなことにハマるのは無知だからだと言われそうですが、また無駄な時間を費やさないように備忘録として書いておくことにしました。

[
  {
    "id":1,
    "text":"project #1",
    "start_date":"2020-05-06",
    "end_date":"2020-05-26",
    "user":3,
    "duration":20,
    "parent":0,
    "progress":0
  },
  {
    "id":2,
    "text":"Task #1",
    "start_date":"2020-05-06",
    "end_date":"2020-05-16",
    "user":0,
    "duration":10,
    "parent":1,
    "progress":0
  },
  {
    "id":3,
    "text":"Task #2",
    "start_date":"2020-05-16",
    "end_date":"2020-05-26",
    "user":0,
    "duration":10,
    "parent":1,
    "progress":0
  }
]

先の記事「JSON 文字列から C# のクラス定義生成」で書きましたように、Visual Studio を使って JSON 文字列から C# のオブジェクトのクラス定義を生成することができます。

上の JSON 文字列から Visual Studio で C# のクラス定義を生成すると以下の通りとなります。

public class Rootobject
{
    public Class1[] Property1 { get; set; }
}

public class Class1
{
    public int id { get; set; }
    public string text { get; set; }
    public string start_date { get; set; }
    public string end_date { get; set; }
    public int user { get; set; }
    public int duration { get; set; }
    public int parent { get; set; }
    public int progress { get; set; }
}

Newtonsoft.Json と JavaScriptSerializer で上に書いた JSON 文字列を Rootobject にデシリアライズしてみます。コード例は以下の通りです。

// Newtonsoft.Json
var result1 = JsonConvert.DeserializeObject<Rootobject>(jsonText);

// JavaScriptSerializer
var serializer = new JavaScriptSerializer();
var result2 = serializer.Deserialize<Rootobject>(jsonText);

そうすると、Newtonsoft.Json では JsonSerializationException 例外が、JavaScriptSerializer では InvalidOperationException 例外がスローされます。Newtonsoft.Json が表示するエラーメッセージは以下の通りでした。

Newtonsoft.Json.JsonSerializationException: 'Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'ConsoleAppJson.Rootobject' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly. To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array. Path '', line 1, position 1.'

これによると Rootobject ではダメで、List<Class1> にデシリアライズしろということのようです。やってみたら確かに List<Class1> にはデシリアライズできました。

なお、上のような配列だけの JSON 文字列ではなくて、例えば { "data": [ { ... },{ ... },{ ... } ] } のような形の JSON 文字列にした場合は、Visual Studio により生成される C# のクラス定義はほぼ同じになりますが、Rootobject にデシリアライズできます。

[ { ... },{ ... },{ ... } ] という形の JSON 文字列だけが要注意のようです。

.NET Framework 版の ASP.NET MVC は JavaScriptSerializer を、ASP.NET Web API は Newtonsoft.Json を使っているので、上のような JSON 文字列を送信してアクションメソッドに渡す場合が自分的に要注意だと思いました。

Tags: , ,

.NET Framework

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar