DataContractJsonSerializerクラスで使うjson用クラスをrecordでつくる

もくじ

目次(WPF/xaml/C#/C++関連メモ) - tera1707’s blog

やりたいこと

jsonファイルを読み込むときに、以前調べたDataContractJsonSerializerクラスをよく使うがjsonを読み込んだときにデータを保存するためのクラスをいちいち書くのがめんどくさい。あと長い。なにか短くかける方法がないか調べたい。

やったこと

C #9.0で追加されたrecord型を使う。

前提

下記で実験。

VisualStudio2022/.NET6/C#10

コード

classでつくる

以前DataContractJsonSerializerの実験で作ったjsonデータを格納するためのクラスは、下記の通り。

public class Rootobject
{
    public int data1 { get; set; }
    public string data2 { get; set; }
    public bool data3 { get; set; }
    public Arraydata[] arraydata { get; set; }
}

public class Arraydata
{
    public int data11 { get; set; }
    public float[] small { get; set; }
    public float[] large { get; set; }
}

recordでつくる

これと同じものをrecordで作ると下記のようになる。

こうすれば、前回作ったジェネリクスでjsonを読み込むコードも、そのまま使える。

[DataContract]
record Rootobject(
    [property: DataMember] int data1,
    [property: DataMember] string data2,
    [property: DataMember] bool data3,
    [property: DataMember] Arraydata[] arraydata
);

[DataContract]
record Arraydata(
    [property: DataMember] int data11,
    [property: DataMember] float[] small,
    [property: DataMember] float[] large
);

注意

試した限り、classだと、[DataContract]のアトリビュートをつけなくてもうまく動いたが、recordだとそれらを付けてやらないとdata = (T?)serializer.ReadObject(stream);でデシリアライズするときに例外が起きた。([property: DataMember]は、つけなくても動くのは動いた。) f:id:tera1707:20220117231200p:plain

注意点

但し、このままだと、例えば

  • jsonファイルを読み込んで、
  • data1の値だけ書き換えて
  • それを別のjsonファイルに保存しなおす

ということができない。

これは、上のようにrecordでRootobjectを作成すると、data1について、コンパイラが裏で

    [property: DataMember]
    public int data1 { get; init; }

のようにしてくれていて、data1の値をコンストラクタでしか入れることができないようになっているため。 これを、

[DataContract]
record Rootobject( [property: DataMember] string data2, [property: DataMember] bool data3, [property: DataMember] Arraydata[] arraydata)
{
    [property: DataMember]
    public int data1 { get; set; }
}

と書いてあげると、data1だけ編集することができるようにできるが、 そういうややこしいことをするなら、全部classで作った方がよいような気がする。

考察

「短くかけたかどうか?」という視点だけだと「{get;set}がなくなったくらい?」と思ってしまうが、 こちらのページにあるように、「jsonのデータ」を扱うためのものとしてこのrecordを見ると、比較したりするうえではrecordの方が、だいぶ扱いやすくなっていると思った。

追記 プライマリコンストラクタを使わなければ、classと同じような書き方でrecordが使える

上のように、プライマリコンストラクタ("()"の中にプロパティを並べて書くやつ)を使わずに、jsonで使いたいとき(≒値を編集したいとき)はクラスと同じ書き方で書けば、値を書き換えることができつつ、recordの便利さを生かしつつ、見た目にもわかりやすい(classと書き方が同じという意味で)ようにかける。

public record Rootobject()
{
    public int Data1 { get; set; }
    public string? Data2 { get; set; }
    public bool Data3 { get; set; }
    public Arraydata[]? Arraydata { get; set; }
}


public record Arraydata 
{
    public int Data11 { get; set; }
    public float[]? Small { get; set; }
    public float[]? Large { get; set; }
}

※難しく考えすぎていたかも。

参考

record型の詳しい説明。 ここを見ればすべてわかる。 ここの「プライマリ コンストラクター引数への属性付与」の項目に、Attributeの付け方が詳しく書かれている。

ufcpp.net

レコードのプロパティへのアトリビュートの設定の仕方

docs.microsoft.com

レコードのプロパティへのアトリビュートの設定の仕方2

stackoverflow.com

レコードのプロパティへのアトリビュートの設定の仕方3

zenn.dev