C#のListをJSON形式でシリアライズ・デシリアライズする2。「型を確認する」

C# コンピュータ
C#

前回インターフェイスのデシリアライズに失敗しましたので対策を考えてみたいと思います。

まずListの要素の型を確認します。TはISampleBaseですが各要素に対してGetType()メソッドを実行しどの型になるか確認します。

using System.Text;
using System.Text.Json;

interface ISampleBase
{
    public string ClassName { get; set; }
}

class SampleClassTypeA : ISampleBase
{
    public string ClassName { get; set; } = "SampleClassTypeA";
}

class SampleClassTypeB : ISampleBase
{
    public string ClassName { get; set; } = "SampleClassTypeB";
}

class Program
{
    static void Check(List<ISampleBase> list)
    {
        var result1 = (typeof(ISampleBase) == list[0].GetType());
        Console.WriteLine($"ISampleBase:{result1}"); // False
        var result2 = (typeof(SampleClassTypeA) == list[0].GetType());
        Console.WriteLine($"SampleClassTypeA:{result2}"); // True
        var result3 = (typeof(SampleClassTypeB) == list[0].GetType());
        Console.WriteLine($"SampleClassTypeB:{result3}"); // False

        var result4 = (typeof(ISampleBase) == list[1].GetType());
        Console.WriteLine($"ISampleBase:{result4}"); // False
        var result5 = (typeof(SampleClassTypeA) == list[1].GetType());
        Console.WriteLine($"SampleClassTypeA:{result5}"); // False
        var result6 = (typeof(SampleClassTypeB) == list[1].GetType());
        Console.WriteLine($"SampleClassTypeB:{result6}"); // True
    }
    static void Main()
    {
        List<ISampleBase> sampleList = new( [new SampleClassTypeA(), new SampleClassTypeB(),] );
        Check(sampleList);
    }
}

試した所、インタフェースのISampeBaseではなく派生型のSampleClassTypeAやSampleClassTypeBであると確認出来ます。
(てっきりISampleBaseと判定されると思ったのですが意外です。)
泥臭い方法ですが、各型毎キャストした後シリアライズを行いテキストファイルに保存する際各行ごとに何の型であるか確認できるシグネチャを仕込めばデシリアライズできそうな感じがします。

using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;

interface ISampleBase
{
    public string ClassName { get; set; }
}

class SampleClassTypeA : ISampleBase
{
    public string ClassName { get; set; } = "SampleClassTypeA";
    public int id { get; set; } = 0;
    public string AName { get; set; } = "A";
    public void AMethod() => Console.WriteLine("Call AMethod()");
}

class SampleClassTypeB : ISampleBase
{
    public string ClassName { get; set; } = "SampleClassTypeB";
    public int id { get; set; } = 1;
    public string BName { get; set; } = "B";
    public void BMethod() => Console.WriteLine("Call BMethod()");
}

class Program
{
    static void Check(List<ISampleBase> list)
    {
        var result1 = (typeof(ISampleBase) == list[0].GetType());
        Console.WriteLine($"ISampleBase:{result1}"); // False
        var result2 = (typeof(SampleClassTypeA) == list[0].GetType());
        Console.WriteLine($"SampleClassTypeA:{result2}"); // True
        var result3 = (typeof(SampleClassTypeB) == list[0].GetType());
        Console.WriteLine($"SampleClassTypeB:{result3}"); // False

        var result4 = (typeof(ISampleBase) == list[1].GetType());
        Console.WriteLine($"ISampleBase:{result4}"); // False
        var result5 = (typeof(SampleClassTypeA) == list[1].GetType());
        Console.WriteLine($"SampleClassTypeA:{result5}"); // False
        var result6 = (typeof(SampleClassTypeB) == list[1].GetType());
        Console.WriteLine($"SampleClassTypeB:{result6}"); // True
    }
    static void Save(string path, List<ISampleBase> list)
    {

        // エンコードを指定して保存
        var encoding = Encoding.GetEncoding("utf-8");
        using var writer = new StreamWriter(path, false, encoding);

        foreach(var t in list)
        {
            var jsonStr = "";
            // キャスト・シリアライズ
            var obj = t as SampleClassTypeA;
            if (obj is not null)
                jsonStr = JsonSerializer.Serialize(obj);
            
            var obj2 = t as SampleClassTypeB;
            if (obj2 is not null)
                jsonStr = JsonSerializer.Serialize(obj2);

            if (jsonStr == "")
                throw new Exception("未対応な型");
            
            // 書き込み
            writer.WriteLine(jsonStr);
        }
    }
    static List<ISampleBase> Load(string path)
    {
        List<ISampleBase> resultList = new();

        var encoding2 = Encoding.GetEncoding("utf-8");
        var reader = new StreamReader(path, encoding2);

        string? jsonStr = "";
        while (reader.Peek() >= 0)
        {
            jsonStr = reader.ReadLine();
            if (jsonStr is null) continue;
            MatchCollection results = Regex.Matches(jsonStr, "{\"ClassName\":\"(.+?)\"");
            foreach (Match m in results)
            {
                string className = m.Groups[1].Value;
                if (className == "SampleClassTypeA")
                {
                    var obj = JsonSerializer.Deserialize<SampleClassTypeA>(jsonStr);
                    if (obj is not null)
                        resultList.Add(obj);
                }
                else if (className == "SampleClassTypeB")
                {
                    var obj = JsonSerializer.Deserialize<SampleClassTypeB>(jsonStr);
                    if (obj is not null)
                        resultList.Add(obj);
                }
                else
                {
                    throw new Exception("未対応の型");
                }
            }
        }
        return resultList;
    }
    static void Main()
    {
        string path = "samplejson.txt";

        List<ISampleBase> sampleList = new( [new SampleClassTypeA(), new SampleClassTypeB(),] );
        // Check(sampleList);

        Save(path, sampleList);
        var resultList = Load(path);

        Console.WriteLine($"{resultList[1].ClassName}");

        var obj = resultList[0] as SampleClassTypeA;

        obj?.AMethod();
    }
}

読み込み時に一度文字列として一行ごと読み出します。
正規表現で仕込んでいたClassNameを抽出し、抽出したクラス名でデシリアライズします。

動くことは動きましたが、ISampleBaseの派生クラスを増やすと、増えた数分だけ同じようなコードを増やす必要があります。

似たような問題点として、こちらのルーチンはOpenCVのフィルターを複数実行する手順を記録する方法として考えているのですが、フィルター(ISampleBase)の実行順番としてListをデータソースにする並び順が変更できるListBoxをUIとして考えています。ただ、フィルターのパラメータはフィルターの種類によって異なります(SampleClassTypeAやSampleClassTypeB)ので、パラメータの変更のUIはフィルターごとに別途もつ必要があります。

出来ればフィルターとフィルターに紐づく一連のUIをモジュール化して追加できるような仕組みにしたいのですが、なかなか難しそうです。

コメント