【コピペで使える】C# で YAML ファイルを読み書きするクラス作りました

当ページのリンクには広告が含まれています。

アプリケーションの設定ファイルとしてよく使われるファイルフォーマットにYAMLがあります。今回仕事で使う機会があったので、簡単なクラスにしてみました。

辞書とクラスという2種類のデータに対して、YAMLファイルで読み書きできるようになっています。このままコピペして使えますので、必要な方はご自由にお使いください。

目次

YAMLのフォーマット

YAMLはキーと値をペアにして記述していきます。また、値には配列は辞書を含めることが可能で、かつネストして記述することも出来ます。

Name: John Doe
Age: 32
Tel:
- 080-3333-5555
- 080-3333-6666
- 090-4444-2222
Address:
  Street: 123 Main St
  City: New York
  Country: USA

YAMLの仕様については「YAML基本文法まとめ」が参考になります。

YAML.DotNet のインストール

.NETでYAMLファイルの読み書きを行うには YamlDotNet のインストールが必要になります。下記の通り①~③の手順で、NuGetパッケージ管理の画面を表示します。

検索欄に下記のキーワードを入力して、検索すると YamlDotNet が表示されるので、これをインストールして下さい。

YamlDotNet

尚、YamlDotNetのソースはGitHubのこちらのページで公開されています。

クラスのリファレンス

概要

今回作成したクラスのネームスペースとクラス名は以下の通りです。必要に応じて書き換えてご利用下さい。

ネームスペースCommonLib
クラス名YamlUtil

メソッドは全部で10個です。

  • Write(string filename, Dictionary<string, object> data)
  • Dictionary<string, object> Read(string filename)
  • Write<T>(string filename, T data)
  • T Read<T>(string filename)
  • Dictionary ToDictionary(object array)
  • OrderedDictionary ToOrderedDictionary(object array)
  • (object key, object value)[] ToArray(object array)
  • object[] GetKeys(object array)
  • object[] GetList(object array)

注意点としては、これらいずれのメソッドも、Yamlに記述した順番(登録順)が保証されないという点です。 Read で参照した結果がDictionaryに格納されるため、この時点で登録順が保証されなくなっています。

実際には登録順で帰ってくることの方が多いかもしれませんが、C#の仕様上保証はされていません。

従って、OrderdDictionary、ToArray、GetKeys、GetList の戻り値は、必ずしもYamlに記述した順番にはなりませんのでご注意ください。

リファレンス

それぞれのメソッドの詳細は以下の通りです。

説明指定された辞書形式のデータをYAML形式でファイルに書き込みます。
メソッドWrite(string filename, Dictionary<string, object> data)
パラメータfilename: 書き込みたいYAMLファイル名
data: 書き込みたいDictionary<string, object>型のオブジェクト。
戻り値無し
説明指定されたファイルからYAML形式のデータを読み込み、Dictionary<string, object>
として返します。ファイルが存在しない場合は空のDictionaryを返します。
メソッドDictionary<object, object> Read(string filename)
パラメータfilename: 読み込みたいYAMLファイル名
戻り値Dictionary<object, object>
説明指定されたオブジェクトの中身をYAML形式でファイルに書き込みます。
メソッドWrite<T>(string filename, T data)
パラメータT: 書き込むデータの型。任意のデータ型が指定できます。
filename: 書き込むファイルの名前を表す文字列。
data: 書き込むデータ。
戻り値無し
説明指定されたファイルからYAML形式のデータを読み込み、指定された型(T)
に復元して返します。
メソッドT Read<T>(string filename)
パラメータT: 復元するデータの型。任意のデータ型が指定できます。
filename: 書き込むファイル名
戻り値指定された型(T)のオブジェクト
説明第2階層目以降に辞書として定義された内容を辞書に変換して返します。
メソッドDictionary<object, object> ToDictionary(object array)
パラメータarray: YAMLで定義された辞書
※内部で型変換した結果を return しているだけなので、キャスト
 (Dictionary<object,object>)array しても結果は同じです。
戻り値Dictionary<object,object>のオブジェクト
説明第2階層目以降に辞書として定義された内容をOrderedDictionary
(登録順序が保証された辞書)に変換して返します。
メソッドOrderedDictionary ToOrderedDictionary(object array)
パラメータarray: YAMLで定義された辞書
戻り値OrderedDictionaryのオブジェクト
説明第2階層目以降に辞書として定義された内容をTuple配列に変換して返します。
メソッド(object key, object value)[] ToArray(object array)
パラメータarray: YAMLで定義された辞書
戻り値(object key, object value)[] Tuple配列
説明第2階層目以降に辞書として定義された内容からキーの一覧を取り出し、
オブジェクト配列として返します。
メソッドobject[] GetKeys(object array)
パラメータarray: YAMLで定義された辞書
戻り値object[] オブジェクト配列
説明第2階層目以降に辞書として定義された内容から値の一覧を取り出し、
オブジェクト配列として返します。
メソッドobject[] GetValue(object array)
パラメータarray: YAMLで定義された辞書
戻り値object[] オブジェクト配列
説明配列として定義された内容をListに変換して返します。
メソッドList<object> GetList(object array)
パラメータarray: YAMLの書式で記述された配列
戻り値List<objecct> オブジェクトのリスト

使い方

YamlUtil()のインスタンスをnew で生成し、後はメソッドを呼ぶだけです。

保存の場合は第一引数にファイル名、第2引数に辞書形式のデータ、もしくはクラスのオブジェクトを指定します。

    .Write(ファイル名, 辞書形式のデータ又はクラスのオブジェクト

読み込みたい場合はReadの後にデータ型を付けるか否かで動作が変わります。

    .Read(ファイル名) ⇒辞書形式のデータを返す

    .Read<クラスの型>(ファイル名)  ⇒ 指定したクラスのオブジェクトとして返す

辞書形式データを保存/復元するサンプル

using System.Xml.Linq;
using System.Xml.Serialization;
using CommonClass;

//テストデータを定義
var data = new Dictionary<string, object>
        {
            { "Name", "John Doe" },
            { "Age", 30 },
            { "Tel", new string[]{"080-3333-5555","080-3333-6666","090-4444-2222"} },
            { "Address", new Dictionary<string, object>
                {
                    { "Street", "123 Main St" },
                    { "City", "New York" },
                    { "Country", "USA" }
                }
            }
        };

//YamlUtilクラスのインスタンスを生成
var yaml = new YamlUtil();

//テストデータをYAML形式で保存
yaml.Write(@"p:\data.yaml", data);

//テストデータの中身をクリア
data.Clear();

//YAMLファイルを読み込んでテストデータを復元
data = yaml.Read(@"p:\test.yaml");

任意のクラスのオブジェクトを保存/復元するサンプル

using System.Xml.Linq;
using System.Xml.Serialization;
using CommonClass;

//Dataクラスのインスタンスを生成
var data = new Data();
data.Name = "John Doe";
data.Age = 32;
data.Tel = new string[] { "080-3333-5555", "080-3333-6666", "090-4444-2222" };
data.Address = new Dictionary<string, object>()
               {
                    { "Street", "123 Main St" },
                    { "City", "New York" },
                    { "Country", "USA" }
               };

//YamlUtilクラスのインスタンスを生成
var yaml = new YamlUtil();

//DataクラスのオブジェクトをYAML形式で保存
yaml.Write(@"p:\data.yaml", data);

data = null;

//YAMLファイルを読み込んでDataクラスのオブジェクトを復元
data = yaml.Read<Data>(@"p:\data.yaml");

//Dataクラスの定義
class Data
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string[] Tel { get; set; }
    public Dictionary<string, object> Address { get; set; }
}

第2階層目以降に辞書として定義された内容を取り出す

第2階層目以降に辞書として定義された内容とは、次の形式で定義されたものになります。

Address:
  Street: 123 Main St
  City: New York
  Country: USA

これをReadメソッドで参照すると、辞書として返ってきます。

 [0]: {[Street, 123 Main St]}
 [1]: {[City, New York]}
 [2]: {[Country, USA]}

辞書として取り出す場合は、単純に型変換するだけで問題ありません。

var data = yaml.Read(@"p:\test.yaml");
var address = (Dictionary<object, object>)data["Address"];
var street = address["Street"]
var city= address["City"]

ただし、この方法だとYAMLの定義順に取得したい場合、順序が保証されません。そこで、1つづつ取り出して OrderedDictionary 又は タプルの配列で返すメソッドを用意しました。

var dic = yaml.ToOrderedDictionary(data["Address"]); //順序が保証された辞書として返す
var arr = yaml.ToArray(data["Address"]); // タプルの配列として返す

 以下はおまけのメソッドです。

var dic = yaml.ToDictionary(data["Address"]); // 辞書として返す。(内部でCASTしているだけ)
var keys = yaml.GetKeys(data["Address"]);  // キーの一覧を配列で返す
var values = yaml.GetKeys(data["Address"]); // 値の一覧を配列で返す

第2階層目以降に配列として定義された内容を取り出す

第2階層目以降に配列として定義された内容とは、次の形式で定義されたものになります。

Tel:
- 080-3333-5555
- 080-3333-6666
- 090-4444-2222

この場合は List<object> 又は object[] に型変換するだけで取り出せます。

var data = yaml.Read(@"p:\test.yaml");
var (List<object>)data["Tel"];

以下はおまけのメソッドです。メソッドにしておくと、備忘録として役立ちます。

yaml.GetList(data["Tel"])

クラスのソースコード

下記がクラスのソースコードです。 .NET 6.0 のコンソールアプリで動作確認を行っています。Framework 4.x を使用する場合、コンパイルエラーが発生するかもしれません。その場合は、エラー発生個所付近に記述されているビックリマーク( ! )を削除して下さい。

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YamlDotNet.Core.Tokens;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

namespace CommonClass
{
    public class YamlUtil
    {
        /// <summary>
        /// 指定されたデータをYAML形式でファイルに書き込みます。
        /// </summary>
        /// <param name="filename">ファイル名</param>
        /// <param name="data">書き込むデータ(Dictionary<string, object>)</param>
        public void Write(string filename, Dictionary<object, object> data)
        {
            // YAMLシリアライザを作成
            var serializer = new SerializerBuilder()
                .WithNamingConvention(CamelCaseNamingConvention.Instance)
                .Build();

            // データをYAMLにシリアライズ
            var yaml = serializer.Serialize(data);

            // ファイルにYAMLを書き込み
            File.WriteAllText(filename, yaml);
        }

        /// <summary>
        /// 指定されたファイルからYAML形式のデータを読み込み、Dictionary<string, object>として返します。
        /// ファイルが存在しない場合は空のDictionaryを返します。
        /// </summary>
        /// <param name="filename">ファイル名</param>
        /// <returns>読み込んだYAMLデータを表すDictionary<string, object></returns>
        public Dictionary<object, object> Read(string filename)
        {
            // ファイルが存在しない場合は空のDictionaryを返す
            if (!File.Exists(filename))
            {
                return new Dictionary<object, object>();
            }

            // YAMLデシリアライザを作成
            var deserializer = new DeserializerBuilder()
                .WithNamingConvention(CamelCaseNamingConvention.Instance)
                .Build();

            // ファイルからYAMLを読み込み、Dictionary<string, object>にデシリアライズして返す
            var yaml = File.ReadAllText(filename);
            return deserializer.Deserialize<Dictionary<object, object>>(yaml);
        }

        /// <summary>
        /// 指定されたデータをYAML形式でファイルに書き込みます。
        /// </summary>
        /// <typeparam name="T">書き込むデータの型</typeparam>
        /// <param name="filename">ファイル名</param>
        /// <param name="data">書き込むデータ</param>
        public void Write<T>(string filename, T data)
        {
            // YAMLシリアライザを作成
            var reserializer = new SerializerBuilder().Build();

            // データをYAMLにシリアライズ
            var yaml = reserializer.Serialize(data);

            // ファイルにYAMLを書き込み
            File.WriteAllText(filename, yaml);
        }

        /// <summary>
        /// 指定されたファイルからYAML形式のデータを読み込み、指定された型(T)にデシリアライズして返します。
        /// </summary>
        /// <typeparam name="T">デシリアライズするデータの型</typeparam>
        /// <param name="filename">ファイル名</param>
        /// <returns>デシリアライズされたデータ</returns>
        public T Read<T>(string filename)
        {
            // YAMLデシリアライザを作成
            var reserializer = new DeserializerBuilder().Build();

            // ファイルからYAMLを読み込み、指定された型(T)にデシリアライズして返す
            var yaml = File.ReadAllText(filename);
            return reserializer.Deserialize<T>(yaml);
        }

        /// <summary>
        /// YAMLの配列を辞書に変換する
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>

        public Dictionary<object, object> ToDictionary(object array)
        {
            return (Dictionary<object, object>) array;
        }

        /// <summary>
        /// YAMLの配列をOrderedDictionary(登録順序が保証された辞書)に変換する
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>

        public OrderedDictionary ToOrderedDictionary(object array)
        {
            OrderedDictionary res = new OrderedDictionary();
            foreach (KeyValuePair<object, object> kvp in (Dictionary<object, object>)array)
            {
                res.Add(kvp.Key, kvp.Value);
            }
            return res;
        }

        /// <summary>
        /// YAMLの配列をTupleの配列に変換する
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        public (object key, object value)[] ToArray(object array)
        {
            List<(object, object)> res = new List<(object, object)>();

            foreach (KeyValuePair<object, object> kvp in (Dictionary<object, object>)array)
            {
                res.Add((kvp.Key, kvp.Value));
            }

            return res.ToArray();
        }

        /// <summary>
        /// YAMLの配列からキーの一覧を取り出す
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        public object[] GetKeys(object array)
        {
            return ToArray(array).Select(i => i.key.ToString()).ToArray()!;
        }

        /// <summary>
        /// YAMLの配列から値の一覧を取り出す
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        public object[] GetValues(object array)
        {
            return ToArray(array).Select(i => i.value).ToArray()!;
        }

        /// <summary>
        /// 配列として定義されている内容をリストとして取り出す
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        public List<object> GetList(object array)
        {
            return (List<object>)array;
        }
    }
}

まとめ

今回は C# で YAMLファイルの読み書きするクラスを作成しましたので、それについて解説しました。

今回は YamlUtil というクラス名で、「辞書形式データ」と、「任意のクラスのオブジェクト」の2種類が扱える、ReadとWriteのメソッドを用意しましたので、これでおおよそのことが出来るかと思います。

.NET でYAML を扱う場合、Yaml dot net をNuGetでインストールする必要があります。今回紹介したクラスをご自身のソースに組み込む場合は、併せてインストールして下さい。

今回の記事が皆様のお役に立てれば光栄です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次