C Sharpの基礎 - YAML

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

YAML (YAML Ain't Markup Language) は、人間にとって読み書きし易いデータシリアライゼーション形式である。

C#でYAMLファイルを扱う場合は、一般的には、サードパーティ製ライブラリであるYamlDotNetライブラリ等を使用する。

YAMLファイルの基本構造は、キーと値のペアで構成されている。
これは階層構造を持つことができ、インデントを使用して表現する。

 name: John Doe
 age: 30
 address:
   street: 123 Main St
   city: Anytown


YAMLのメリットとして、JSONやXMLと比べて冗長性が低く、より直感的な記述が可能である。
また、コメントを含められるため、設定ファイルとして使用する場合に説明を加えやすいという特徴がある。

C#でYAMLファイルを使用する一般的なケースとしては、
アプリケーション設定の管理、データのシリアライゼーション / デシリアライゼーション、構造化されたデータの保存等が挙げられる。

YAMLの使用する場合は、インデントに注意が必要である。
スペースの数が正確でない場合は、意図しない構造になる可能性がある。
また、複雑なデータ構造を扱う場合は、カスタムクラスを定義してデシリアライズすると、より型安全なコードを記述することができる。

YAMLは柔軟性が高いため、様々なデータ形式に対応できるが、セキュリティ上の懸念から、信頼できないソースからのYAMLデータを扱う場合は注意が必要である。


YAMLファイルの構造

YAMLファイルは、階層的なデータ構造を表現するのに適しており、様々な要素を組み合わせて複雑なデータを表現することができる。

YAMLファイルの構造は、インデントとコロン (:) を使用して表現される。
これにより、人間にとって読みやすく、また機械的な解析も容易になっている。

スカラー値

文字列や数値等の単一の値を表す。

 name: John Doe
 age: 30
 is_student: false


マッピング (オブジェクト)

キーと値のペアを使用して、関連するデータをグループ化する。

 person:
   name: Jane Smith
   age: 28
   occupation: Engineer


シーケンス (配列)

ダッシュ (-) を使用してリストを表現する。

 fruits:
   - apple
   - banana
   - orange


組み合わせ

上記セクションの要素を組み合わせて、より複雑な構造を作成することができる。

 # sample.yamlファイル
 
 company:
   name: Tech Solutions Inc.
   founded: 2010
   employees:
     - name: Alice Johnson
       position: CEO
       skills:
         - leadership
         - strategic planning
     - name: Bob Williams
       position: CTO
       skills:
         - programming
         - system architecture
   offices:
     headquarters:
       address: 123 Main St, Techville
       phone: 555-1234
     branch:
       address: 456 Innovation Ave, Codetown
       phone: 555-5678


データの再利用

YAMLファイルでは、アンカー (&) と エイリアス (*) を使用して、データの再利用が可能である。

以下の例では、defaultsで定義された値がdevelopmentとproductionに継承され、必要に応じて上書きしている。

 defaults: &defaults
   timeout: 30
   retries: 3
 
 development:
   <<: *defaults
   server: dev.example.com
 
 production:
   <<: *defaults
   server: prod.example.com
   timeout: 60


複数のドキュメントを1つのYAMLファイルに含める

複数のYAMLファイルを1つのYAMLファイルに含めることも可能である。
ドキュメントの区切りには、---を使用する。

 ---
 # Document 1
 name: First Configuration
 ...
 
 ---
 # Document 2
 name: Second Configuration
 ...



YamlDotNetライブラリ

YamlDotNetライブラリとは

YamlDotNetライブラリは、開発者にとってYAMLファイルを処理を簡単にする強力なツールである。
このライブラリを使用することにより、YAMLファイルの読み書きやYAMLデータとC#オブジェクト間の変換が容易になる。

高性能な設計により、YamlDotNetライブラリは大規模なYAMLファイルも効率的に処理できる。
また、柔軟性も兼ね備えており、様々なYAML形式に対応している。
C#の静的型付けのメリットを活かして、データを強く型付けされたオブジェクトに変換する機能も提供している。

YamlDotNetの基本的な使用方法は比較的簡単であり、シリアライザ / デシリアライザを構築して、YAMLとC#オブジェクト間の変換を行う。

例えば、YAMLガイルからC#オブジェクトへのデシリアライズは、DeserializerBuilderを使用して行う。
逆に、C#オブジェクトからYAMLファイルへのシリアライズにはSerializerBuilderを使用する。
また、これらのビルダーを使用することで、変換プロセスをカスタマイズすることも可能である。

YamlDotNetライブラリは複雑なデータ構造も扱うことができる。
ネストされたオブジェクト、リスト、ディクショナリ等を含むクラスも適切に変換できるため、多様なデータモデルに対応できる。

高度なカスタマイズも本ライブラリの特徴の1つである。
カスタムタグの処理、特定のプロパティの無視、別名の使用等が可能であり、これらは属性やカスタムコンバータを通じて実現できる。

セキュリティ面では、YamlDotNetライブラリはデフォルトで安全なデシリアライズを行う。
ただし、信頼できないソースからのYAMLファイルを扱う場合は、追加のセキュリティ対策を検討することを推奨する。

YamlDotNetライブラリは活発に開発が続けられており、最新のYAML仕様にも対応している。
また、充実したドキュメントとサンプルコードが提供されているため、開発者は迅速に学習・実装を進めることができる。

YamlDotNetライブラリを使用することで、開発者はYAMLを効率的に扱い、設定ファイルの管理やデータ交換等、様々なシナリオで活用することができる。

YamlDotNetライブラリのライセンスは、MITライセンスに準拠している。


YamlDotNetライブラリのインストール

RiderまたはVisual StudioからNuGetを使用して、YamlDotNetライブラリをインストールする。

  • Riderの場合
    1. プロジェクトを開く。
    2. [ツール]メインメニュー - [Nuget] - [ソリューション の Nuget パッケージを管理] (または、[<プロジェクト名> の Nuget パッケージを管理])を選択する。
    3. メイン画面下部にある[パッケージ]タブから YamlDotNet と入力して検索する。
    4. メイン画面下部の右にある[+]ボタンを押下して、YamlDotNetライブラリをインストールする。

  • Visual Studioの場合
    1. プロジェクトを開く。
    2. NuGetパッケージマネージャーを開く。
      • [ツール]メインメニュー - [NuGetパッケージマネージャー]を選択して、[ソリューションのNuGetパッケージの管理]を選択する。
      • または、ソリューションエクスプローラーでプロジェクトを右クリックして、コンテキストメニューから[NuGetパッケージの管理]を選択する。
    3. YamlDotNetライブラリを検索する。
      NuGetパッケージマネージャーの検索ボックスに YamlDotNet と入力して検索する。
    4. YamlDotNetライブラリのインストール
      検索結果からYamlDotNetライブラリを選択して、[インストール]ボタンを押下する。
    5. インストールの確認ダイアログが表示されるので、[OK]ボタンを押下してインストールを完了する。
    6. 参照の確認
      インストールが完了した後、プロジェクトの参照にYamlDotNetライブラリが追加されていることを確認する。

  • パッケージマネージャーコンソールからインストールする場合
    1. プロジェクトを開く。
    2. [表示]メインメニュー - [その他のウィンドウ] - [パッケージマネージャーコンソール]を選択して、パッケージマネージャーコンソールを開く。
    3. パッケージマネージャーコンソールから、YamlDotNetライブラリをダウンロードしてインストールする。
      Install-Package YamlDotNet
    4. ソリューションエクスプローラーのプロジェクトの参照において、YamlDotNetライブラリが追加されていることを確認する。

  • dotnetコマンドを使用する場合
    1. ターミナルを開く。
    2. プロジェクトのルートディレクトリに移動する。
    3. YamlDotNetライブラリをインストールする。
      最新の安定版をインストールする場合
      dotnet add package YamlDotNet

      バージョンを指定してインストールする場合
      dotnet add package YamlDotNet --version <バージョン>

    ※注意
    プロジェクトがGit等のバージョン管理システムを使用している場合、これらの変更がトラッキングされることを確認すること。
    プロジェクトを再ビルドして、新しく追加されたパッケージが正しく統合されていることを確認することを推奨する。


プロジェクトにおいて、YamlDotNetライブラリを使用する場合は、ソースコードファイルの先頭にusingステートメントを追加する。
これにより、名前空間を使用することで、YAMLのシリアライズ / デシリアライズに関する主要な機能にアクセスすることができる。

 using YamlDotNet.Serialization;
 using YamlDotNet.Serialization.NamingConventions;


また、大きなサイズなYAMLファイルを扱う場合は、ストリーミング処理を使用することを推奨する。

YAMLファイルの読み込み

以下の例では、YamlDotNetライブラリを使用して、指定されたYAMLファイルを非同期で読み込み・解析している。

  • StreamReaderクラスを使用してファイルを開く。
  • YamlStreamクラスのLoadAsyncメソッドを使用して、ファイルを非同期で読み込む。
  • 解析したYAMLデータのルートノードを返す。
  • ReadYamlFileAsyncメソッドは、ファイルを非同期で読み込み、YAMLデータを解析する。
  • ProcessYamlDataAsyncメソッドは、解析したYAMLデータを処理する。
    会社名、設立年、従業員情報、オフィス情報を順に取得する。
    各セクションでYAMLノードを適切な型 (YamlMappingNode、YamlSequenceNode) にキャストして処理する。


 # 使用するYAMLファイル
 
 company:
   name: Tech Solutions Inc.
   founded: 2010
   employees:
     - name: Alice Johnson
       position: CEO
       skills:
         - leadership
         - strategic planning
     - name: Bob Williams
       position: CTO
       skills:
         - programming
         - system architecture
   offices:
     headquarters:
       address: 123 Main St, Techville
       phone: 555-1234
     branch:
       address: 456 Innovation Ave, Codetown
       phone: 555-5678


 using System;
 using System.IO;
 using System.Threading.Tasks;
 using YamlDotNet.RepresentationModel;
 
 class Program
 {
    static async Task Main(string[] args)
    {
       string filePath = "sample.yaml";  // YAMLファイルのパス
 
       try
       {
          // YAMLファイルを非同期で読み込み、YAMLデータを解析
          var yaml = await ReadYamlFileAsync(filePath);
 
          // 解析したYAMLデータを処理
          await ProcessYamlDataAsync(yaml);
       }
       catch (Exception ex)
       {
          Console.WriteLine($"エラーが発生: {ex.Message}");
       }
    }
 
    // YAMLファイルを非同期で読み込む
    static async Task<YamlMappingNode> ReadYamlFileAsync(string filePath)
    {
       using (var streamReader = new StreamReader(filePath))
       {
          // YAMLストリームを作成
          var yaml = new YamlStream();
             
          // ファイルを非同期で読み込み、YAMLデータを解析
          await yaml.LoadAsync(streamReader);
 
          // ルートノードを取得して、YamlMappingNodeにキャスト
          return (YamlMappingNode)yaml.Documents[0].RootNode;
       }
    }
 
    // 解析したYAMLデータを処理
    static async Task ProcessYamlDataAsync(YamlMappingNode root)
    {
       // 会社情報を取得
       var company = (YamlMappingNode)root["company"];
 
       // 会社名と設立年を出力
       Console.WriteLine($"会社名: {company["name"]}");
       Console.WriteLine($"設立年: {company["employees"]}");
 
       // 従業員情報を取得
       var employees = (YamlSequenceNode)company["employees"];
 
       Console.WriteLine("従業員一覧:");
       foreach (var employee in employees.Children)
       {
          var employeeNode = (YamlMappingNode)employee;
          Console.WriteLine($"名前: {employeeNode["name"]}");
          Console.WriteLine($"役職: {employeeNode["position"]}");
 
          var skills = (YamlSequenceNode)employeeNode["skills"];
          Console.WriteLine("スキル:");
          foreach (var skill in skills)
          {
             Console.WriteLine($"- {skill}");
          }
       }
 
       // オフィス情報を取得
       var offices = (YamlMappingNode)company["offices"];
 
       Console.WriteLine("オフィス情報:");
       foreach (var office in offices.Children)
       {
          Console.WriteLine($"{office.Key}:");
          var officeNode = (YamlMappingNode)office.Value;
          Console.WriteLine($"住所: {officeNode["address"]}");
          Console.WriteLine($"電話: {officeNode["phone"]}");
       }
 
       // 非同期処理をシミュレートするために1秒待機
       await Task.Delay(1000);
    }
 }


YAMLファイルの書き込み

以下の例では、YamlDotNetライブラリを使用して、指定されたYAMLファイルを非同期で書き込んでいる。

  1. CreateCompanyDataメソッドは、YAMLに変換するためのデータ構造を生成する。
    ネストされたDictionaryとListを使用して、YAMLの階層構造を表現している。
    これにより、複雑なYAML構造を柔軟に生成する。

  2. WriteYamlFileAsyncメソッドは、データをYAMLに変換して、ファイルに非同期で書き込む。
    SerializerBuilderを使用してYAMLシリアライザを設定する。
    ここでは、CamelCaseNamingConventionを使用しているが、必要に応じて変更可能である。
    SerializeメソッドでデータをYAML文字列に変換する。


 # 書き込むYAMLファイル
 
 company:
   name: Tech Solutions Inc.
   founded: 2010
   employees:
     - name: Alice Johnson
       position: CEO
       skills:
         - leadership
         - strategic planning
     - name: Bob Williams
       position: CTO
       skills:
         - programming
         - system architecture
   offices:
     headquarters:
       address: 123 Main St, Techville
       phone: 555-1234
     branch:
       address: 456 Innovation Ave, Codetown
       phone: 555-5678


 using System;
 using System.IO;
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using YamlDotNet.Serialization;
 using YamlDotNet.Serialization.NamingConventions;
 
 class Program
 {
    static async Task Main(string[] args)
    {
       string filePath = "sample.yaml"; // 書き込むYAMLファイルのパス
 
       try
       {
          // 会社データを作成
          var companyData = CreateCompanyData();
 
          // YAMLファイルを非同期で書き込む
          await WriteYamlFileAsync(filePath, companyData);
       }
       catch (Exception ex)
       {
          Console.WriteLine($"エラーが発生: {ex.Message}");
       }
    }
 
    // companyデータを作成
    static Dictionary<string, object> CreateCompanyData()
    {
       return new Dictionary<string, object>
       {
          ["company"] = new Dictionary<string, object>
          {
             ["name"] = "Tech Solutions Inc.",
             ["founded"] = 2010,
             ["employees"] = new List<Dictionary<string, object>>
             {
                new Dictionary<string, object>
                {
                   ["name"] = "Alice Johnson",
                   ["position"] = "CEO",
                   ["skills"] = new List<string> { "leadership", "strategic planning" }
                },
                new Dictionary<string, object>
                {
                   ["name"] = "Bob Williams",
                   ["position"] = "CTO",
                   ["skills"] = new List<string> { "programming", "system architecture" }
                }
             },
             ["offices"] = new Dictionary<string, object>
             {
                ["headquarters"] = new Dictionary<string, string>
                {
                   ["address"] = "123 Main St, Techville",
                   ["phone"] = "555-1234"
                },
                ["branch"] = new Dictionary<string, string>
                {
                   ["address"] = "456 Innovation Ave, Codetown",
                   ["phone"] = "555-5678"
                }
             }
          }
       };
    }
 
    // YAMLファイルを非同期で書き込む
    static async Task WriteYamlFileAsync(string filePath, Dictionary<string, object> data)
    {
       // YAMLシリアライザの設定
       var serializer = new SerializerBuilder()
                            .WithNamingConvention(CamelCaseNamingConvention.Instance)
                            .Build();
 
       // YAMLにシリアライズ
       var yaml = serializer.Serialize(data);
 
       // ファイルに非同期で書き込み
       await File.WriteAllTextAsync(filePath, yaml);
    }
 }


YAMLファイルの修正

以下の例では、YamlDotNetライブラリを使用して、指定されたYAMLファイルを非同期で修正している。

YAMLファイルにおいて、修正する箇所を以下に示す。


  1. ReadYamlFileAsyncメソッド
    YAMLファイルを非同期で読み込む。
    DeserializerBuilderを使用して、YAMLデータをDictionary<string, object>型にデシリアライズする。
    CamelCaseNamingConventionを使用して、YAMLのキーをC#のプロパティ名に合わせている。

  2. ModifyCompanyDataメソッド
    LINQのFirstOrDefaultメソッドを使用して、特定の従業員を検索する。

    Alice Johnsonのskillに"Hardware Develop"を追加する。
    Bob Williamsのpositionを"COO"に変更する。
    新しい従業員"Taro Yamada"を追加する。

  3. WriteYamlFileAsyncメソッド
    修正されたデータを再度YAMLフォーマットにシリアライズする。
    File.WriteAllTextAsyncを使用して、YAML文字列をファイルに非同期で書き込む。


 # 変更前のYAMLファイル
 
 company:
   name: Tech Solutions Inc.
   founded: 2010
   employees:
     - name: Alice Johnson
       position: CEO
       skills:
         - leadership
         - strategic planning
     - name: Bob Williams
       position: CTO
       skills:
         - programming
         - system architecture
   offices:
     headquarters:
       address: 123 Main St, Techville
       phone: 555-1234
     branch:
       address: 456 Innovation Ave, Codetown
       phone: 555-5678


 using System;
 using System.IO;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
 using YamlDotNet.Serialization;
 using YamlDotNet.Serialization.NamingConventions;
 
 class Program
 {
    static async Task Main(string[] args)
    {
       string filePath = "sample.yaml";  // 修正するYAMLファイルのパス
 
       try
       {
          // YAMLファイルを非同期で読み込む
          var companyData = await ReadYamlFileAsync(filePath);
 
          // データを修正
          ModifyCompanyData(companyData);
 
          // 修正したデータを非同期で書き込む
          await WriteYamlFileAsync(filePath, companyData);
       }
       catch (Exception ex)
       {
          Console.WriteLine($"エラーが発生: {ex.Message}");
       }
    }
 
    // YAMLファイルを非同期で読み込む
    static async Task<Dictionary<string, object>> ReadYamlFileAsync(string filePath)
    {
       var deserializer = new DeserializerBuilder()
                              .WithNamingConvention(CamelCaseNamingConvention.Instance)
                              .Build();
 
       using (var reader = new StreamReader(filePath))
       {
          var yaml = await reader.ReadToEndAsync();
          return deserializer.Deserialize<Dictionary<string, object>>(yaml);
       }
    }
 
    // 会社データを修正
    static void ModifyCompanyData(Dictionary<string, object> data)
    {
       var company = (Dictionary<string, object>)data["company"];
       var employees = (List<object>)company["employees"];
 
       // Alice Johnsonのskillに"Hardware Develop"を追加
       var alice = employees.FirstOrDefault(e => ((Dictionary<string, object>)e)["name"].ToString() == "Alice Johnson") as Dictionary<string, object>;
       if (alice != null)
       {
          var skills = (List<object>)alice["skills"];
          skills.Add("Hardware Develop");
       }
 
       // Bob Williamsのpositionを"COO"に変更
       var bob = employees.FirstOrDefault(e => ((Dictionary<string, object>)e)["name"].ToString() == "Bob Williams") as Dictionary<string, object>;
       if (bob != null)
       {
          bob["position"] = "COO";
       }
 
       // 新しい従業員"Taro Yamada"を追加
       var taro = new Dictionary<string, object>
       {
          ["name"] = "Taro Yamada",
          ["position"] = "CTO",
          ["skills"] = new List<string> { "monitoring", "programming" }
       };
 
       employees.Add(taro);
    }
 
    // YAMLファイルを非同期で書き込む
    static async Task WriteYamlFileAsync(string filePath, Dictionary<string, object> data)
    {
       var serializer = new SerializerBuilder()
                            .WithNamingConvention(CamelCaseNamingConvention.Instance)
                            .Build();
 
       var yaml = serializer.Serialize(data);
 
       await File.WriteAllTextAsync(filePath, yaml);
    }
 }


 # 変更後のYAMLファイル
 
 company:
   name: Tech Solutions Inc.
   founded: 2010
   employees:
     - name: Alice Johnson
       position: CEO
       skills:
         - leadership
         - strategic planning
         - Hardware Develop
     - name: Bob Williams
       position: COO
       skills:
         - programming
         - system architecture
     - name: Taro Yamada
       position: CTO
       skills:
         - monitoring
         - programming
   offices:
     headquarters:
       address: 123 Main St, Techville
       phone: 555-1234
     branch:
       address: 456 Innovation Ave, Codetown
       phone: 555-5678