このページの最終更新日 2020/10/25

DataContractJsonSerializerクラスを使用して保存する

1. DataContract JsonSerializerクラスを利用する

 ここでは、保存したい情報を持ったプロパティを1つのSavedUserSettingクラスに纏め、DataContractJsonSerializerクラスを使ってSavedUserSettingクラスの情報をjson形式にしてファイルへ出力するプログラムのコードを示します。まずは、IDEのアプリケーションの参照を右クリックして「参照の追加」をクリックしていくと、pic.6のようにSystem.Runtime.Serialization名前空間にチェックを入れます。

System.Runtime.Serializationを参照

- pic.6 -

 SavedUserSettingクラスに、保存したい情報をプロパティとして記載します。System.Runtime.Serialization名前空間をインポートし、クラス名の上の行に[DataContract]、保存したいプロパティ名の上に[DataMember]と記述します。先ほどと同じように、String1、Datetime1、というプロパティ名の値を保存しデフォルト値も先ほどと同じにします。

- code.2 - 「SavedUserSetting.cs」
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization;

namespace SaveVariableApp
{
    [DataContract]
    public class SavedUserSetting
    {
        [DataMember]
        public String String1 { get; set; } = "";

        [DataMember]
        public DateTime DateTime1 { get; set; } = DateTime.Parse("2001/01/01");
    }
}

 Code.3の35~49行目WriteUserSettingメソッドはSavedUserSettingクラスのプロパティをjson形式に変換して、プログラムと同一フォルダ内に「user.json」ファイルとして保存するコードです。ファイルパスは3行目で宣言しています。ReadUserSettingメソッドではWriteUserSettingメソッドとは逆にファイルに保存されているjson形式の文字列を読み込んでSavedUserSettingクラスへ変換しています。ファイルを読み込もうとしたときにファイルが存在しない場合は、SavedUserSettingクラスのコンストラクタを呼び出します。このときはクラスのプロパティのデフォルト値が設定されることになります。
 IO.MemoryStream などなど見慣れないものも色々登場してお経のように感じる方も居られるかもしれませんが、意味が分からなくてもまずは頑張って写し書いてみて下さい。

- code.3 -

using IO = System.IO;
using System.Runtime.Serialization.Json;
class Program{
private static string CONFIG_FILE_PATH = IO.Directory.GetParent(System.Reflection.Assembly.GetExecutingAssembly().Location).FullName + @"\user.json";

    static void Main(string[] args)
    {
        var objSavedUserSetting = ReadUserSetting();
        Console.WriteLine("String1 = " + objSavedUserSetting.String1);
        Console.WriteLine("Datetime1 = " + objSavedUserSetting.DateTime1.ToString("yyyy年MM月dd日"));

        objSavedUserSetting.String1 = "コムスコシステムズ";
        objSavedUserSetting.DateTime1 = DateTime.Parse("2018/06/27");
        WriteUserSetting(objSavedUserSetting);

        Console.WriteLine("何かしらのキーを押すと終了します。");
        Console.ReadLine();
    }

    static SavedUserSetting ReadUserSetting()
    {
        if (!IO.File.Exists(CONFIG_FILE_PATH)) { return new SavedUserSetting(); }

        using (var sr = new IO.StreamReader(CONFIG_FILE_PATH, Encoding.UTF8))
        {
            using (var ms = new IO.MemoryStream(Encoding.UTF8.GetBytes(sr.ReadToEnd())))
            {
                var jsonSerial = new DataContractJsonSerializer(typeof(SavedUserSetting));
                return (SavedUserSetting)jsonSerial.ReadObject(ms);
            }
        }
    }
        
    static void WriteUserSetting(SavedUserSetting objSavedUserSetting)
    {
        using (var ms = new IO.MemoryStream())
        {
            var jsonSerial = new DataContractJsonSerializer(typeof(SavedUserSetting));
            jsonSerial.WriteObject(ms, objSavedUserSetting);
            
            using(var sw = new IO.StreamWriter(CONFIG_FILE_PATH, false, Encoding.UTF8))
            {
                byte[] byteJson = ms.ToArray();
                sw.WriteLine(Encoding.UTF8.GetString(byteJson, 0, byteJson.Length));
            }
        }
    }
}

 このコードを実行すると、最初に実行した場合はpic.3と同じようにデフォルト値が表示されますが、次に実行した場合はjsonファイルの更新された値を読み取ってpic.4と同じ出力結果になります。出力されるjsonファイルも次に示します。

 「user.json」
{"DateTime1":"\/Date(1530025200000+0900)\/","String1":"コムスコシステムズ"}
 

 上の例のように2つのプロパティを保存するだけだと苦労の割には得られるものが少ないですね。しかしこの方法の便利なところはリストだろうが、タプルだろうが、あるいは別のクラスをプロパティとして持っていても、その構造をそのままの形でjson形式のファイルとやりとり出来るところです。

 では実際に、Code.4のようにSavedUserSettingクラスにリスト(20行目)、タプル(23行目)、クラス(26行目)を定義し、これが適切に保存され、また取り出すことが出来ることを確かめてみます。また、SavedUserSettingクラスのプロパティ値を出力出来るようにToStringメソッドをオーバーライドして、プロパティの値を列挙していきます。

- code.4 - 「SavedUserSetting.cs」
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization;

namespace SaveVariableApp
{
    [DataContract]
    public class SavedUserSetting
    {
        [DataMember]
        public String String1 { get; set; } = "";

        [DataMember]
        public DateTime DateTime1 { get; set; } = DateTime.Parse("2001/01/01");

        [DataMember]
        public List<bool> ListBool1 { get; set; } = new List<bool>();

        [DataMember]
        public Tuple<double, char> TupleDoubleChar1 { get; set; } = Tuple.Create(-1.0, 'X');
 
        [DataMember]
        public SavedUserSetting InnerSavedUserSetting1 = null;


        public override string ToString()
        {
            var classInfomation = new StringBuilder();
            classInfomation.Append("String1 = " + this.String1 + "\r\n");
            classInfomation.Append("Datetime1 = " + this.DateTime1.ToString("yyyy年MM月dd日") + "\r\n");
            if (this.ListBool1.Count > 0){ classInfomation.Append("ListBool1 = " + string.Join(",", this.ListBool1.Select(m => m.ToString())) + "\r\n"); }
            classInfomation.Append("TupleDoubleChar1[double] = " + this.TupleDoubleChar1.Item1 + "\r\n");
            classInfomation.Append("TupleDoubleChar1[char] = " + this.TupleDoubleChar1.Item2 + "\r\n");

            if (this.InnerSavedUserSetting1 != null)
            {
                classInfomation.Append("***** InnerSavedUserSetting1 *****\r\n");
                classInfomation.Append(this.InnerSavedUserSetting1.ToString());
            }

            return classInfomation.ToString();
        }
    }
}

 メイン部分のReadUserSettingメソッドと、WriteUserSettingメソッドの部分はCode.3と変化していません。SavedUserSettingクラスにプロパティが追加されたため、追加されたプロパティの値を変更するようなコードも追加しています。8行目のように Console.WriteLineにクラスオブジェクトをそのまま引数として渡すと、クラスのToStringメソッドで返される文字列が出力されるようになります。

- code.5 -
class Program
{
    private static string CONFIG_FILE_PATH = IO.Directory.GetParent(System.Reflection.Assembly.GetExecutingAssembly().Location).FullName + @"\user.json";

    static void Main(string[] args)
    {
        var objSavedUserSetting = ReadUserSetting();
        Console.WriteLine(objSavedUserSetting);

        objSavedUserSetting.String1 = "コムスコシステムズ";
        objSavedUserSetting.DateTime1 = DateTime.Parse("2018/06/27");
        objSavedUserSetting.ListBool1 = new List<bool>{true, false, true, false, false};
        var innerSavedUserSetting = new SavedUserSetting();
        innerSavedUserSetting.ListBool1 = new List<bool>{ false, true, true, true };
         innerSavedUserSetting.TupleDoubleChar1 = Tuple.Create(-11.0 / 7.0, 'V');
        objSavedUserSetting.InnerSavedUserSetting1 = innerSavedUserSetting;
        WriteUserSetting(objSavedUserSetting);

        Console.WriteLine("何かしらのキーを押すと終了します。");
        Console.ReadLine();
    }

    static SavedUserSetting ReadUserSetting()
    {
        if (!IO.File.Exists(CONFIG_FILE_PATH)) { return new SavedUserSetting(); }

        using (var sr = new IO.StreamReader(CONFIG_FILE_PATH, Encoding.UTF8))
        {
            using (var ms = new IO.MemoryStream(Encoding.UTF8.GetBytes(sr.ReadToEnd())))
            {
                var jsonSerial = new DataContractJsonSerializer(typeof(SavedUserSetting));
                return (SavedUserSetting)jsonSerial.ReadObject(ms);
            }
        }
    }

    static void WriteUserSetting(SavedUserSetting objSavedUserSetting)
    {
        using (var ms = new IO.MemoryStream())
        {
            var jsonSerial = new DataContractJsonSerializer(typeof(SavedUserSetting));
            jsonSerial.WriteObject(ms, objSavedUserSetting);
            
            using(var sw = new IO.StreamWriter(CONFIG_FILE_PATH, false, Encoding.UTF8))
            {
                byte[] byteJson = ms.ToArray();
                sw.WriteLine(Encoding.UTF8.GetString(byteJson, 0, byteJson.Length));
            }
        }
    }
}

 - Code.5 -を最初に実行したとき、コンソールへの出力は変更されたプロパティの値が保存される前の段階であるため、SavedUserSettingクラスのデフォルト値が出力されています(pic.7)。

jsonファイルに保存される前

- pic.7 -

 - Code.5 -を2回目以降に実行した場合は、変更されたプロパティの値を「user.json」ファイルから読み取ってコンソールへ出力します。pic.8のように、リスト、タプル、再帰的に内蔵した自身のクラスの情報も適切に取り出せていますね。

jsonファイルに保存される前

- pic.8 -

 jsonファイルの中身について記載しておくことにします。

 「user.json」
{"DateTime1":"\/Date(1530025200000+0900)\/","InnerSavedUserSetting1":{"DateTime1":"\/Date(978274800000+0900)\/","InnerSavedUserSetting1":null,"ListBool1":[false,true,true,true],"String1":"","TupleDoubleChar1":{"m_Item1":-1.5714285714285714,"m_Item2":"V"}},"ListBool1":[true,false,true,false,false],"String1":"コムスコシステムズ","TupleDoubleChar1":{"m_Item1":-1,"m_Item2":"X"}}
 

2. Singletonパターンを使ってユーザー設定情報を纏める

 アプリケーション外部に保存されるような設定情報というのは、たいがいにグローバルな変数になってきます。なるべく変数のスコープは小さくすべきだ、グローバルな変数は使うべきではないなどと口を酸っぱくして言われますが、外部に保存した設定情報はアプリケーションのあちこちで使用される変数であることも多く難しい問題です。
 第2節では設定情報を1つのクラスのプロパティに纏めていましたが、このままでは複数クラスが生成されてアプリケーション全体で統一されるはずの設定情報が、複数の異なる値をとることが出来てしまい少し問題のあるコードと言えます。そこで静的(static)な変数としてアプリケーション全体で唯一1つの値を取るようにしておくべきでしょう。ここでは、singtetonパターンを採用して変数の統一を図ることにします。
 節2では保存すべき変数がごちゃごちゃしてきたので、節1に戻ってString1プロパティとDatetime1プロパティの2つだけが保存されるようにしておきます。また、ReadUserSettingメソッドやWriteUserSettingはSavedUserSettingクラスの中に押し込んでしまいましょう。

 sinletonパターンの詳しい解説はデザインパターンの教科書に任せてCode.6の説明だけしておくと、15行目でクラス内部に自分自分のクラスのオブジェクトを静的メンバとして定義しておきます。ここでは _singleton と変数名を付けることにします。そして17行目でコンストラクタをprivateにして外部からクラスを作成出来ないようにします。また、21~40行目のように _singletonがnull値でなければ_singletonオブジェクトそのものを返し、null値の場合は何かしらの方法で_singletonオブジェクトを作成してから外部へ_singletonオブジェクトを返すようにします。このような方法を取ることでSavedUserSettingクラスはアプリケーションでただ1つしか生成されないことが保証されるようになります。
 ただ、singletonパターンについては人によって色々考えがあり、Code.6の例のように単にグローバルな変数として扱う場合はsingtetonパターンを使用すべきではなく、各々のプロパティを静的(static)にすべきであるという考え方もあります。確かにsingletonパターンを採用することでコードが複雑化することもあります。執筆者の意見としては singleton を敢えて採用している利点は、クラス内のプロパティの初期化が単純に行えることです。Code.6で言えば _singleton = new SavedUserSetting();と1行書けばクラス内のプロパティが全て初期値に戻ります。プロパティの各々をstaticにした場合は、それぞれのプロパティについて宣言とは別にプロパティを初期化するための1文を加える必要があります。ただし、null値でない_singletonが既に存在する状態で _singleton = new SavedUserSetting(); なんてことをするとアプリケーション全体で唯一の状態を取るクラスであることが保証されなくなるのが頭を抱えるところですし、そもそもそれをsingletonパターンと呼んでも良いのかという突っ込みも受けそうです。

- code.6 - 「SavedUserSetting.cs」
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using IO = System.IO;

namespace SaveVariableApp
{
    [DataContract]
    public class SavedUserSetting
    {
        private static SavedUserSetting _singleton;

        private SavedUserSetting() { }

        private static string CONFIG_FILE_PATH = IO.Directory.GetParent(System.Reflection.Assembly.GetExecutingAssembly().Location).FullName + @"\user.json";

        public static SavedUserSetting GetSavedUserSettign()
        {
            if (_singleton != null) { return _singleton; }

            if (!IO.File.Exists(CONFIG_FILE_PATH))
            {
                _singleton = new SavedUserSetting();
                return _singleton;
            }

            using (var sr = new IO.StreamReader(CONFIG_FILE_PATH, Encoding.UTF8))
            {
                using (var ms = new IO.MemoryStream(Encoding.UTF8.GetBytes(sr.ReadToEnd())))
                {
                    var jsonSerial = new DataContractJsonSerializer(typeof(SavedUserSetting));
                    _singleton = (SavedUserSetting)jsonSerial.ReadObject(ms);
                    return _singleton;
                }
            }
        }

        public void WriteUserSetting()
        {
            using (var ms = new IO.MemoryStream())
            {
                var jsonSerial = new DataContractJsonSerializer(typeof(SavedUserSetting));
                jsonSerial.WriteObject(ms, _singleton);

                using (var sw = new IO.StreamWriter(CONFIG_FILE_PATH, false, Encoding.UTF8))
                {
                    byte[] byteJson = ms.ToArray();
                    sw.WriteLine(Encoding.UTF8.GetString(byteJson, 0, byteJson.Length));
                }
            }
        }

        [DataMember]
        public String String1 { get; set; } = "";

        [DataMember]
        public DateTime DateTime1 { get; set; } = DateTime.Parse("2001/01/01");


        public override string ToString()
        {
            var classInfomation = new StringBuilder();
            classInfomation.Append("String1 = " + this.String1 + "\r\n");
            classInfomation.Append("Datetime1 = " + this.DateTime1.ToString("yyyy年MM月dd日") + "\r\n");
            return classInfomation.ToString();
        }
    }
}
- code.7 -
class Program
{
    static void Main(string[] args)
    {
        var objSavedUserSetting = SavedUserSetting.GetSavedUserSettign();
        Console.WriteLine(objSavedUserSetting);

        objSavedUserSetting.String1 = "コムスコシステムズ";
        objSavedUserSetting.DateTime1 = DateTime.Parse("2018/06/27");
        objSavedUserSetting.WriteUserSetting();

        Console.WriteLine("何かしらのキーを押すと終了します。");
        Console.ReadLine();
    }
}